1.简述
线性表,全名为线性存储结构。使用线性表存储数据的方式可以这样理解,即“把所有数据按照顺序(线性)的存储结构方式,存储在物理空间”。
按照空间分类:
顺序存储结构:数据依次存储在连续的物理空间中(顺序表);
链式存储结构:数据分散存储在不连续物理空间中,通过某种指向关系,来维持它们之间的逻辑关系(链表);
2.顺序表(数组)
在Java中,我们通常使用数组来定义顺序表,同时,数组也是最基本的数据结构。它由相同类型的元素组成,并且是使用一块连续的内存来存储。使用下标来访问数组中的元素,首元素的下标从0开始,最后一个元素的下标是数组长度-1。
2.1.优缺点
内存地址连续,数组元素进行遍历时,速度快。
根据下标,查找指定位置的元素时,速度快。
长度固定,使用前需要预估长度。
插入删除元素时,时间复杂度相对较高。
2.2.时间复杂度
数组的长度为 n。
- 访问特定位置的元素,时间复杂度为O(1)
- 插入元素的时间复杂度为O(n) :插入元素时的最坏情况,新元素插入到数组的头部,需要移动n个元素。
- 删除元素的时间复杂度为O(n) : 删除元素时的最坏情况,删除数组的的头部元素,需要移动n-1个元素
3.链表
3.1.结构
由以下两部分组成:
- 1数据元素本身,其所在的区域称为数据域;
- 2指向直接后继元素的指针,所在的区域称为指针域;
在链表中,实际存储的是一个一个的节点,真正的数据元素包含在这些节点中,所以链表是一种在物理上非连续的数据结构,由若干个节点(Node)组成的一种线性存储结构。
3.2.优点
- 不需要提前预估长度,可以克服数组需要预先知道数据长度的缺点
- 使用不连续内存空间,充分利用计算机内存空间,实现灵活的内存动态管理
3.3.缺点
- 会占用更多的空间,因为每个节点中,除了存放数据,还有存放指向其他节点的指针
- 不能随机读取元素(RandomAccess)
- 遍历和查找元素的速度比较慢
3.4.时间复杂度
链表的插入和删除操作的复杂度为 O(1),查找一个节点或者访问特定位置的节点的时候复杂度为 O(n),最坏情况下需要遍历整个链表。
3.5.链表分类
单项链表
每一个节点Node中包含1个数据域和1个指针域。数据域是存放数据的变量data,指针域是指向下一个节点的指针next,所以单向链表只有一个方向。
//单向链表的节点
static class Node<E>{
E data; // 数据域
Node<E> next; // 后继节点(指针域)
public Node(E data) {
this.item = data;
}
}
循环链表
双向链表
每个节点Node中包含2个指针域和1个数据域,指针域next指向后一个节点, prev 指向前一个节点,数据域item用于存储数据。
// 双向链表的节点
public static class Node<E> {
E item; // 数据域
Node<E> next; // 后继节点
Node<E> prev; // 前驱节点
// 构造方法
Node(Node<E> prev, E element, Node<E> next) {
this.prev = prev; // 参数1:前驱节点
this.item = element; // 参数2:数据域
this.next = next;// 参数3:后继节点
}
}
双向循环链表
4.顺序表和链表的区别
4.1.适用场景
顺序表(数组)可以通过下标快速定位元素,遍历速度快,适合读多写少的应用场景;
链表插入和删除元素效率高,适合读少写多的应用场景;
顺序表(数组)的长度固定,如果声明的数组过小,需要另外申请更大的内存空间+拷贝原数组进行扩容。而链表支持动态扩容。
4.2.开辟空间的方式
顺序表(数组)存储数据实行的是 "一次开辟,永久使用",即存储数据之前先开辟好足够的存储空间,空间一旦开辟后期无法改变大小(使用动态数组的情况除外)。
链表存储数据时一次只开辟存储一个节点的物理空间,如果后期需要,还可以再申请。
所以:若只从空间角度去考虑,当存储数据的个数无法提前确定,又或是物理空间无法一次性申请到足够大小的空间时,使用链表更有助于问题的解决。
4.3.空间利用率
顺序表(数组)的空间利用率显然要比链表高。
- 链表每次申请空间相对随机,容易产生空间碎片,一定程序上造成了空间浪费。
- 链表由于链表中每个数据元素都必须携带至少一个指针保证线性关系。
4.4.各种操作的时间复杂度
查找 | 修改 | 插入 | 删除 | |
数组 | O(1) | O(1) | O(n) | O(n) |
链表 | O(n) | O(1) | O(1) | O(1) |