线性表
线性表可以用数学方式来定义 (a1, ... ai-1, ai, ai+1, ... an)
,其中 ai+1
是 ai
的前驱,ai-1
是 ai
的后缀。当 n=0
的时候,称为空表
。
线性结构:顺序存储,比如数组。
链表结构:乱序存储,里面有一个指针指向下一项元素地址,分为数据域(如data1)和指针域(next 指针)如下图:
线性表的优缺点:
优点
:
- 快速存取表中任意位置的元素。
- 无需为表示表中元素之间的关系而增加额外的存储空间。
缺点
:
- 插入和删除操作需要移动大量的元素。
- 当线性表变化比较大的时候,难以确定存储空间的容量。
- 容易造成存储空间碎片。
链表
数据域存储数据,指针域指向下一个储存节点的地址:
1 | typedef struct Node { |
时间复杂度
在操作单链表的过程中,由于算法的时间复杂度取决于 i 的值,因此最坏情况下的时间复杂度为 O(n),最好的情况下为 O(1)。对于插入或者删除数据越频繁的操作,单链表的效率优势就越明显,因为其操作的是指针。
单链表结构与顺序存储结构时间性能对比:
单链表 | 顺序结构 | |
---|---|---|
查找 | O(n) | O(1) |
删除 | O(1) | O(n) |
插入 | O(1) | O(n) |
空间性能
- 顺序结构需要分配连续的存储空间,空间分大了造成浪费,分小了会造成溢出。
- 单链表不需要分配存储空间,元素个数也不受限制。
选取单链表还是顺序结构?
- 若线性表需要频繁查找,很少进行插入和删除操作,采用顺序存储结构。
- 若需要频繁的插入和删除时,采用单链表结构。
单链表应用
快速找到未知长度单链表的中间节点。
可以通过遍历单链表的方式来获取其中间节点。其时间复杂度为 O(3L/2)
思路如下:
- 先遍历一遍链表,获取其长度 L。
- 再次循环到 L/2 处,获取其中间节点。
还有一种更巧妙的方式来解决这个问题,其时间复杂度为O()
,利用快慢指针的原理,思路如下:
- 设置两个指针
*search
、*mid
,都指向单链表的头结点。 - 其
*search
的移动速度是*mid
的两倍。这样当*search
移动到末尾结点的时候,*mid
指针刚好就在中间了。
1 | Status GetMidNode(LinkList L, ElemType *e) |