目录
一、数组介绍
1、数组是什么?
- 数组(Array),是有限个相同类型的变量所组成的有序集合,数组中每一个变量被称为元素。数组是最简单、常用的数据结构。
- 特点:在内存中顺序存储,实现逻辑上的顺序表。数组的每个元素,都存储在各自的内存单元中,元素之间紧密排列,不能打乱元素的存储顺序,也不能跳过某个存储单元进行存储。
2、如何操作数组?
从增删改查四个角度介绍
(1)查
对于数组来说,读取最为简单。因为数组中的元素在内存中顺序存储,故只需要指定数组下标即可获取相对应的元素。根据下标读取元素的方式又叫做随机读取。
int[] arr = new int[]{5, 2, 3, 1};
System.out.println(arr[2]);//输出:3
读取元素时间复杂度为O(1)
(2)改
对于数组来说,要改一个数组中的元素也很简单。只需要给指定的元素赋新值即可。
int[] arr = new int[]{5, 2, 3, 1};
System.out.println(arr[2]);//输出3
arr[2] = 100;
System.out.println(arr[2]);//输出100
更新元素的时间复杂度为O(1)
(3)删
对于数组来说,删除一个位于中间索引上的元素,这个索引就变成了空,需要将此索引后的元素全部前移一位,保证数组的顺序连续性。
看代码实现。
/**
* @author chengyanqi
* @date 2020/7/23 21:45
*/
public class ArrayDemo {
private static Integer[] arr;
private static int size;
public static void main(String[] args) throws Exception {
size = 5;
arr = new Integer[size];
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
arr[3] = 3;
arr[4] = 4;
size = arr.length;
System.out.println(Arrays.toString(arr));
int element = deleteElementByIndex(3);
System.out.println("删除元素的索引:" + element);
System.out.println(Arrays.toString(arr));
}
/**
* 删除数组中的元素
* @param index 删除的元素的索引
*/
public static int deleteElementByIndex(int index) throws Exception {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("数组越界异常");
}
int deleteElement = arr[index];
for (int i = index; i < size - 1; i++) {
arr[i] = arr[i + 1];
}
arr[size - 1] = null;
size--;
return deleteElement;
}
}
[0, 1, 2, 3, 4]
删除元素的索引:3
[0, 1, 2, 4, null]
删除操作,涉及到元素的移动,遍历。故时间复杂度为O(n)
(4)增
对于数组来说,插入元素较为复杂。
首先针对数组元素少于数组长度的情况。分为了两种插入:1、尾部插入。2、中间插入(包括头部插入)
- 尾部插入。和更新类似,直接放到尾部空闲位置即可。
- 中间插入。需要考虑到插入索引位置后的元素移动。插入索引的元素后,后面的元素都要后移一位。
/**
* @author chengyanqi
* @date 2020/7/23 22:33
*/
public class ArrayDemo2 {
private static int[] arr;
private static int size;
public static void main(String[] args) throws Exception {
initArr(10);
insert(0,5);
insert(1,5);
insert(2,5);
insert(3,5);
insert(4,5);
System.out.println(Arrays.toString(arr));
}
//初始化数组
public static void initArr(int size) {
arr = new int[size];
ArrayDemo2.size = 0;
}
/**
* 数组插入元素
* @param index 索引
* @param element 元素
*/
public static void insert(int index, int element) throws Exception{
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("数组越界异常");
}
//由于起始数组为空,不能从左向右遍历移动。否则抛出数组越界异常。
//从右向左循环,大于index的元素向右移动一位
for (int i = size - 1; i >= index; i--) {
arr[i + 1] = arr[i];
}
arr[index] = element;
size++;
}
}
[5, 5, 5, 5, 5, 0, 0, 0, 0, 0]
插入需要移动、遍历。时间复杂度为O(n)
(5)扩容
如果考虑到,插入元素超过数组长度。这时候就涉及到数组的扩容了。我们可以新定义一个数组长度为原数组两倍的新数组,将旧数组内的元素移动到新数组中,这样就实现了数组的扩容。
修改上面插入元素的代码。
/**
* @author chengyanqi
* @date 2020/7/23 22:33
*/
public class ArrayDemo2 {
private static int[] arr;
private static int size;
public static void main(String[] args) throws Exception {
initArr(3);
insert(0,5);
insert(1,5);
insert(2,5);
insert(3,5);
insert(4,5);
System.out.println(Arrays.toString(arr));
}
//初始化数组
public static void initArr(int size) {
arr = new int[size];
ArrayDemo2.size = 0;
}
/**
* 数组插入元素
* @param index 索引
* @param element 元素
*/
public static void insert(int index, int element) throws Exception{
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("数组越界异常");
}
//如果插入元素index达到数组容量上限,则进行扩容
if (index >= arr.length) {
System.out.println("扩容前数组:" + Arrays.toString(arr));
expansion();
System.out.println("扩容后数组:" + Arrays.toString(arr));
}
//由于起始数组为空,不能从左向右遍历移动。否则抛出数组越界异常。
//从右向左循环,大于index的元素向右移动一位
for (int i = size - 1; i >= index; i--) {
arr[i + 1] = arr[i];
}
arr[index] = element;
size++;
}
public static void expansion() {
int[] newArr = new int[arr.length * 2];
//赋值老数组中的元素进入新数组
System.arraycopy(arr, 0, newArr, 0, arr.length);
arr = newArr;
}
}
扩容前数组:[5, 5, 5]
扩容后数组:[5, 5, 5, 0, 0, 0]
[5, 5, 5, 5, 5, 0]
插入需要移动、遍历,时间复杂度为O(n)。扩容时间复杂度为O(n)。综合来说,插入并扩容时间复杂度为O(n)
(6)数组小结
数组具有高效的随机访问。对于查找、更新而言,只要根据索引下标,就可以用O(1)的时间复杂度找到元素。
数组的插入、删除操作涉及到元素的被迫移动,影响效率。
总结:读操作时间复杂度为O(1),写操作时间复杂度为O(n)。数组适合于读多写少的场景。
二、链表介绍
1、链表是什么?
链表(linked list)是一种在物理上非连续、非顺序的数据结构。由若干个节点node组成。第一个节点又被称为头节点,最后一个节点被称为尾节点。整体是头节点的next指向下一个节点,下一个节点的next指向下下一个节点,直到指向尾部节点。
单向链表,每个节点包含两个部分,一个是存储数据的变量data,一个是指向下一个节点的next。public class Node { Node next; int data; }
双向连边,每个节点由三部分组成,指向上个节点的pre,存储变量的data,指向下个节点的next。
public class Node { Node pre; Node next; int data; }
2、如何操作链表?
(1)查
查找链表不像数组那样通过下标快速查找到需要的元素,只能从头部开始next、next、next…直到找到所需要的节点。
private Node head;
private Node last;
private int size;
public Node get(int index) throws Exception {
if (index < 0 || index >= size) {
throw new Exception("超出链表范围");
}
Node node = head;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
查找链表的时间复杂度,考虑最坏情况为O(n)
(2)改
如果不考虑查找链表的过程,更新链表比较简单,和数组一样,直接由新数据替换旧数据即可。
(3)增
新增节点分为三种情况,1、尾部插入。2、头部插入。3、中间插入
尾部插入:把最后一个节点的next指向新插入的节点即可。
头部插入:将新节点的next指向原本的头部节点,再将新节点变为链表的头部节点
中间插入:新节点的next指向插入位置的节点,插入位置前的节点的next指向新插入的节点。
private Node head;
private Node last;
private int size;
public void insert(int data,int index) throws Exception{
if (index < 0 || index > size) {
throw new Exception("超出链表范围");
}
Node insertNode = new Node(data);
if (size == 0) {
head = insertNode;
last = insertNode;
} else if (index == 0) {
insertNode.next = head;
head = insertNode;
} else if (index == size) {
last.next = insertNode;
last = insertNode;
} else {
Node preNode = get(index-1);
insertNode.next = preNode.next;
preNode.next = insertNode;
}
size++;
}
链表的插入,不需要考虑节点的扩容问题。不考虑插入前的查找过程,时间复杂度为O(1)。
(4)删
删除节点同样分为三种情况,1、尾部删除。2、头部删除。3、中间删除。
尾部删除:直接把到数第二个节点指向null即可。
头部删除:将头节点head设置为原先头节点的next节点即可。
中间删除:将被删除节点的前一个节点的next指向被删除节点的后一个节点即可。
private Node head;
private Node last;
private int size;
public Node remove(int index) throws Exception {
if (index < 0 || index >= size) {
throw new Exception("超出链表范围");
}
Node removeNode = null;
if (index == 0) {
removeNode = head;
head = head.next;
} else if (index == size - 1) {
Node preLastNode = get(index - 1);
removeNode = preLastNode.next.next;
preLastNode.next = null;
last = preLastNode;
}else {
Node preNode = get(index - 1);
Node lastNode = preNode.next.next;
removeNode = preNode.next;
preNode.next = lastNode;
}
size--;
return removeNode;
}
链表删除节点的时间复杂度同样为O(1)。Java中有垃圾回收机制,不被引用的节点会自动被回收。
(5)小结
链表更适用于读少写多的场景。读O(n),增删改O(1)。
三、总结
数据结构 | 增 | 删 | 改 | 查 |
---|---|---|---|---|
数组 | O(1) | O(1) | O(n) | O(n) |
链表 | O(n) | O(1) | O(1) | O(1) |
由表格不难看出,数组和链表各有优势
数组更适用于查找的场景,链表更适用于插入和删除操作多的场景。