单链表的操作
双向链表、循环链表、静态链表请点击这里
1.单链表的读取
在线性表的顺序存储结构中,我们要计算任意一个元素的存储位置是很容易的。但在单链表中,由于第i个元素没办法一开始就知道,必须得从头开始找。
获得链表第 i 个数据的算法思路:
声明一个指针 p 指向链表第一个结点,初始化 j 从 1 开始;
当 j < i 时,就遍历链表,让 p 的指针向后移动,不断指向下一个结点,j 累加 1;
若到链表末尾 p 为空,则说明第 i 个结点不存在;
否则查找成功,返回结点 p 的数据。
实现代码算法如下:
/* 初始条件:顺序线性表 L 已经存在,1 <= i <= ListLength(L) */
/* 操作结果:用 e 返回 L 中第 i 个数据元素的值 */
Status GetElem(LinkList L, int i, ElemType *e)
{
int j;
LinkList p; /* 声明一指针 p */
p = L->next; /* 让 p 指向链表 L 的第一个结点(有头结点的话就是指向头结点) */
j = 1; /* j 为计数器 */
/* p 不为空且计数器 j 还没有等于 i 时,循环继续 */
while (p && j < i)
{
p = p->next; /* 让 p 指向下一个结点 */
++j;
}
if (!p || j > i)
return ERROE /* 第 i 个结点不存在 */
*e = p->data; /* 取第 i 个结点的数据 */
return OK;
}
说白了,就是从头开始找,直到第 i 个结点为止。由于这个算法的时间复杂度取决于 i 的位置,当 i = 1 时,则不需遍历,当 i = n 时则遍历 n - 1 次才可以。因此最坏情况的时间复杂度是 O(n)。
由于单链表的结构中没有定义表长,所以不能事先知道要循环多少次,因此也就不方便使用for来控制循环。其主要核心思想就是“工作指针后移”,这其实也是很多算法的常用技术。
2.单链表的插入
假设存储元素 e 的结点为 s,要实现结点 p 、p->next 和 s之间逻辑关系的变化,只需将结点 s 插入到结点 p 和 p->next之间即可。如图:
可以发现,只需让 s->next 和 p->next 的指针做一点改变即可。
s->next = p->next; /* 让p的后继结点改成s的后继结点 */
p->next = s; /* 把结点s变成p的后继结点 */
三遍定律:
两句的顺序不可以交换!
两句的顺序不可以交换!
两句的顺序不可以交换!
那接下来我们看看为啥交换不得:
如果先p->next=s;再s->next=p->next;会怎么样?哈哈,因为此时第一句会使得将p->next给覆盖成s的地址了。那么s->next=p->next,其实就等于s->next=s。这样真正的拥有ai+1数据元素的结点就没了上级。这样的插入操作就是失败的,造成了临场掉链子的尴尬局面。
正确插入后是这样的。如图:
单链表第i个数据插入结点的算法思路:
1.声明一指针 p 指向链表头结点,初始化 j 从1 开始;
2.当 j < i 时,就遍历链表&#