线性表(Linear List)。顾名思义,线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。比如数组,链表、队列、栈等也是线性表结构。
而与它相对立的概念是非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系。
1. 数组
数组(Array
)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
数组寻址
1 | a[i]_address = base_address + i * data_type_size |
以长度为10的int数组(int[] a = new int[10]
)为例:分配了一块连续内存空间 1000~1039
,其中,内存块的首地址为 base_address = 1000
,int
类型的data_type_size
为4
个字节,即a[0]
地址为1000
,a[1]
地址为1004
…
容器是否完全替代数组?
- 容器的优势:对于Java语言,容器封装了数组插入、删除等操作的细节,并且支持动态扩容。
- 对于Java,一些更适合用数组的场景:
- Java的
ArrayList
无法存储基本类型,需要进行装箱操作,而装箱与拆箱操作都会有一定的性能消耗,如果特别注意性能,或者希望使用基本类型,就可以选用数组。 - 若数组大小事先已知,并且对数组只有非常简单的操作,不需要使用到
ArrayList
提供的大部分方法,则可以直接使用数组。 - 多维数组时,使用数组会更加直观。
- Java的
2. 链表
链表(Linked list
)也是一种线性表数据结构。它的内存结构是不连续的内存空间,是将一组零散的内存块串联起来,从而进行数据存储。
链表中每一个内存块称为节点(Node
),节点除了存储数据外,还需存储下一个节点的地址。
- 单链表:每个节点只包含一个指针,即后继指针(
next
);首节点地址表示整条链表,尾节点的后继指针指向空地址null。 - 循环链表:尾节点的后继指针指向首节点,其他与单链表一致。
- 双向链表:每个节点包含两个指针,前驱指针(
prev
)和后继指针(next
),首节点的前驱指针和尾节点后继指针都指向空地址null。 - 双向循环链表:首节点前驱指针指向尾节点,尾节点后继指针指向首节点,其他与双链表一致。
3. 数组vs链表
- 数组中的元素存在一个连续的内存空间中,若数组申请空间足够但不连续也会失败;而链表中的元素可以存在于不连续的内存空间,不过需要额外的内存空间存储指针信息。
- 数组支持随机访问,根据下标随机访问的时间复杂度是
O(1)
;链表随机访问的时间复杂度是O(n)
。 - 链表适合插入、删除操作,时间复杂度为
O(1)
;数组插入、删除操作,时间复杂度为O(n)
。 - 数组大小固定,若存储空间不足,需要进行扩容,扩容就需要数据复制,这非常耗时;链表进行频繁的插入删除操作会导致内存频繁的内存申请和释放,容易造成内存碎片,Java环境还可能造成频繁的
GC
(自动垃圾回收)操作。 - 数组在实现上使用连续的内存空间,可以借助CPU的缓冲机制预读数组中的数据,所以访问效率更高,而链表在内存中并不是连续存储,所以对CPU缓存不友好,没办法预读。
附:双向链表的Java实现
1 | /** |