在编写程序的过程中,我们常常需要「找到」一个位置来存放「中间数据」。此时,我们常常定义变量来存储他们。当创建变量时,计算机会为其创建一个单独的「空间」来存放特定类型的数据。
如下所示,我们创建了一个score变量来存放一位同学的成绩。
var score = 80;
计算机会在内存中单独开辟一片空间来存放数据,并且告诉程序相应的内存地址。如图所示,该变量的内存地址是0x00000005。
当我们需要存储多个相同类型的数据时,似乎使用变量就显得不太「靠谱」了
var score1 = 80;
var score2 = 90;
var score3 = 59;
var score4 = 66;
var score5 = 11;
var score6 = 99;
var score7 = 55;
var score8 = 73;
如果有 200 个学生,难不成定义 200 个变量来存储学生成绩?这时可以使用数组和链表来存储。
数组
使用数组时,计算机会在内存中开辟一整段连续的空间用于存放数据。依然以学生成绩为例。
var scores = new int[]{80, 90, 59, 66, 11, 99, 55, 73};
如图所示,计算机为学生成绩分别开辟了 8 份空间用来分别存放 8 位学生的考试成绩。然而这里又出现了一个问题,如果想要添加第 9 位学生成绩时会怎么样?
答案就是,计算机会重新在内存中找一份空间用于存放这 9 位学生成绩。这也是为什么数组一旦创建就不可变。
数组的操作
在数组中,针对一个元素的位置有一个专业术语称为索引(又称下标)。我们通常通过索引(下标)来获取、修改数组元素
数组的索引由 0 开始,最大值为数组元素总数 - 1。
数组元素数据的获取
数组元素数据通过数组名[索引]来获取。如以下代码展示了如何遍历一个数组。
var scores = new int[]{80, 90, 59, 66, 11, 99, 55, 73};
for(var i = 0; i < scores.length; i++){
System.out.println(scores[i]);
}
数组元素数据的修改
数组元素数据可以通过数组名[索引] = 新数据来修改。如以下代码展示了将scores数组中第 4 号元素(11分)修改为81分。
var scores = new int[]{80, 90, 59, 66, 11, 99, 55, 73};
scores[4] = 81;
数组元素的插入
如果需要在数组之间的某个位置插入一个元素,则需要将其后面的元素整体向后挪一位。当数组空间不足时,你需要重新创建一个空间来存放新的数组。
数组元素的删除
数组中一个元素删除时,通常使得其右边的元素向左挪一位。
链表
与数组不同,链表中的元素可以在任意的内存位置中。
依然以上面的学生成绩为例,其在内存中如图所示。
一个简易链表的实现如下:
链表的操作
在链表中,我们通常将元素称为一个节点(node),一个节点保存了其上下节点的地址。
通常节点的设计如下:
class Node {
int score;
Node prev;
Node next;
public Node(int score) {
this.score = score;
}
}
链表元素数据的获取
在链表中,一个元素数据需要由上一个元素逐步向下寻找。具体代码实现可以参照下面的get(int index)方法。
链表元素数据的修改
在链表中,想要修改一个元素的数据首先要找到该元素,然后对其进行修改。具体代码实现可以参照下面的update(int index, int newData)方法。
链表元素的插入
在链表中,想要插入一个元素可以将上一个节点的next对象以及下一个节点的prev对象指向新的元素。具体代码实现可以参照下面的insert(int index, int data)方法。
链表元素的删除
在链表中,想要删除一个元素只需要确保没有任何一个元素指向该节点即可。具体代码实现可以参照下面的remove(int index)方法。
简易链表的实现
public class MyLinkedList {
/**
* 头节点指针
*/
private Node head;
/**
* 尾节点指针
*/
private Node last;
/**
* 链表实际长度
*/
private int size;
/**
* 链表插入元素
*
* @param data 插入元素
* @param index 插入位置
*/
public void insert(int index, int data) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("超出链表节点范围!");
}
final var insertedNode = new Node(data);
insertedNode.prev = last;
if (size == 0) {
// 空链表
head = insertedNode;
last = insertedNode;
} else if (index == 0) {
// 插入头部
insertedNode.next = head;
head = insertedNode;
} else if (size == index) {
// 插入尾部
last.next = insertedNode;
last = insertedNode;
} else {
// 插入中间
final var prevNode = get(index - 1);
insertedNode.next = prevNode.next;
prevNode.next = insertedNode;
}
size++;
}
/**
* 修改数据元素
*
* @param index 修改元素索引
* @param newData 修改值
*/
public void update(int index, int newData) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("超出链表节点范围!");
}
final var node = get(index);
node.data = newData;
}
/**
* 链表删除元素
*
* @param index 删除位置
*/
public Node remove(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("超出链表节点范围!");
}
Node removedNode = null;
if (index == 0) {
// 删除头节点
removedNode = head;
head = head.next;
head.prev = null;
} else if (index == size - 1) {
// 删除尾节点
final var prevNode = get(index - 1);
removedNode = prevNode.next;
prevNode.next = null;
last = prevNode;
} else {
// 删除中间节点
final var prevNode = get(index - 1);
final var nextNode = prevNode.next.next;
removedNode = prevNode.next;
nextNode.prev = prevNode;
prevNode.next = nextNode;
}
size--;
return removedNode;
}
/**
* 链表查找元素
*
* @param index 查找位置
*/
public Node get(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("超出链表节点范围!");
}
var temp = this.head;
for (int i = 0; i < index; i++) {
temp = temp.next;
}
return temp;
}
/**
* 输出链表
*/
public void output() {
var temp = this.head;
while (temp != null) {
System.out.println(temp.data);
temp = temp.next;
}
}
/**
* 链表节点
*/
private static class Node {
Node prev;
int data;
Node next;
Node(int data) {
this.data = data;
}
}
public static void main(String[] args) throws Exception {
final var myLinkedList = new MyLinkedList();
myLinkedList.insert(0, 3);
myLinkedList.insert(1, 7);
myLinkedList.insert(2, 9);
myLinkedList.insert(3, 5);
myLinkedList.output();
System.out.println("--------------");
myLinkedList.update(3, 8);
myLinkedList.remove(0);
myLinkedList.output();
}
}
数组和链表的优劣
如下是数组和链表操作运行所需的时间:
| 数组 | 链表 | |
|---|---|---|
| 读取 | O(1) | O(n) |
| 插入 | O(n) | O(1) |
| 删除 | O(n) | O(1) |