数组和链表 Array and Linked-List
数据结构概述 Data Structure Overview
数据结构本质上就是数据的存储方式。常见数据结构分为线性表(Linear List)和非线性表。
线性表
线性表是一种比较简单的数据结构,其中的元素是一对一的关系,每个元素只有前后两个方向。线性表包括数组、链表、队列、栈等。
非线性表
非线性表中的元素是一对多或多对一的关系,元素之间不只有前后两个方向。非线性表包括树和图等。
数组 Array
数组是一种顺序表,属于线性存储结构,存储在一段连续的内存空间。
数组的随机访问
数组是存储在一块连续的内存中,并且数组中元素数据类型相同。因此,可以实现对数组的随机访问。例如对于数组 a[10],要访问 a[i],可以直接得到其地址:首地址 + i * sizeof(int)。
这也是数组这种顺序表的一个优势:高效地访问某个元素,也即随机访问。
数组的插入
由于数组是存储在一块连续的内存中,那么对数组的插入注定是比较低效的,需要做整块数据的搬移。
数组的删除
和插入类似,删除元素也是低效的。
链表 Linked-List
数组是连续存储的,并且元素顺序是通过下标来决定的。而链表可以不是连续存储的,它的元素顺序是由链表中的指针决定。和数组相比较,链表的插入和删除效率很高,而访问第 k 个元素效率较差。
链表的典型结构和数据结构如下,这里需要注意:箭头的目的是指向链表中的一个节点(下同)。
struct ListNode {
int key;
ListNode *prev;
ListNode *next;
ListNode(int x) : val(x), prev(nullptr), next(nullptr) {}
};
链表的分类
双链表:上面给的典型结构就是双链表,next 指向下一个节点, prev 指向前一个节点。
单链表:不包含 prev 指针。
循环链表:一种特殊的单链表,链表的尾结点 next 指向链表的头结点。
双向循环链表:一种特殊的双向链表,链表的表头 prev 指向表尾节点,表尾 next 指向表头节点。
链表的基本操作
以双向链表为例,介绍下链表的基本操作。链表的插入和删除不需要维护存储的连续性,效率很高。但是,元素的访问无法像数组那样随机访问,需要逐个遍历节点。
链表的搜索
链表的搜索指的是查找链表中第一个关键字为 k 的元素,并返回指向该元素的指针。
LIST-SEARCH(L, k)
x = L.head
while x != NIL and x.key != k
x = x.next
return x
链表的插入
链表的插入是指将关键字为 k 的元素 x 插入到链表的前端。
LIST-INSERT(L, x)
x.next = L.head
if L.head != NIL
L.head.prev = x
L.head = x
x.prev = NIL
链表的删除
链表的删除是指将元素 x 从链表中移出。若是是给定的是 key 值,则先调用链表的搜索算法找到 x,再执行下面的删除算法。
LIST-DELETE(L, x)
if x.prev != NIL
x.prev.next = x.next
else L.head = x.next
if x.next != NIL
x.next.prev = x.prev
参考
- 算法导论
- 数据结构和算法之美