我们先来看下单来链表的插入,假设存储元素 e 的结点为 s ,要实现结点 p ,p -> next 和 s 之间逻辑关系的变化,大家参考下图思考一下:
1. 我们思考后发觉根本用不着惊动其他结点,只需要让 s -> next 和 p -> next 的指针做一点改变;
s -> next = p -> next;
p -> next = s;
2. 我们通过图片来解读一下这两句代码 ->
1.声明一结点 p 指向链表头结点,初始化 j 从 1 开始;
2. 当 j < 1 时,就遍历链表,让 p 的指针向后移动,不断指向下一结点,j 累加 1;
3. 若到链表末尾 p 为空,则说明第 i 个元素不存在;
4. 否则查找成功,在系统中生成一个空结点 s;
5. 将数据元素 e 赋值给 s -> data;
6. 单链表的插入刚才两个标准语句;
7. 返回成功;
- Status ListInsert (LinkList* L , int i , ElemType e) {
- int j;
- LinkList p , s;
- p = *L;
- j = 1;
- while() {
- p = p -> next;
- j++;
- }
- if(!p || j > i) {
- return ERROR;
- }
- s = (LinkList)malloc(sizeof(Node));
- s -> data = e;
- s -> next = p -> next;
- p -> next = s;
- return OK;
- }
1. 假设元素 a2 的结点为 q ,要实现结点 q 删除单链表的操作,其实就是将它的前驱结点的指针绕过指向后继结点即可;
2. 那我们所要做的,实际上就是一步:
可以这样: p -> next = p -> next -> next;
也可以是:q = p -> next;p -> next = q -> next;
1. 声明结点 p 指向链表第一个结点,初始化 j = 1;
2. 当 j < 1 时,就遍历链表,让 p 的指针向后移动,不断指向下一结点,j 累加 1;
3. 若到链表末尾 p 为空,则说明第 i 个元素不存在;
5.否则查找成功,将欲删除结点 p -> next 赋值给 q ;
6. 单链表的删除标准语句 p -> next = q -> next;
7. 将 q 结点中的数据赋值给 e ,作为返回;
8. 释放q 结点;
- Status ListDelete (LinkList* L , int i , ElemType* e) {
- int j;
- LinkList p , q;
- p = *L;
- j = 1;
- while(p->next && j < i) {
- p = p -> next;
- ++j;
- }
- if(!(p -> next) || j > i) {
- return ERROR;
- }
- q = p -> next;
- p -> next = q -> next;
- *e = q -> data;
- free(q);
- return OK;
- }

1. 我们最后的环节是效率 PK ,我们发现无论是单链表插入还是删除算法,他们其实都是由两个部分组成:第一部分就是遍历查找第 i 个元素,第二部分就是实现插入和删除元素;
2.从整个算法来说,我们很容易可以推出他们的时间复杂度都是 O ( n );
3. 再详细点分析:如果在我们不知道第 i 个元素的指针位置,单链表数据结构在插入和删除操作上,与线性表的顺序存储结构是没有太大优势的;
4. 但如果,我们希望从第 i 个位置开始,插入连续 10 个元素,对于顺序存储结构意味着,每一次插入都需要移动 n - i 个位置,所以每次都是 O ( n );
5. 而单链表,我们只需要在第一次时,找到第 i 个位置的指针,此时为 O ( n ),接下来只是简单地通过赋值移动指针而已,时间复杂度都是 O ( 1 );
6. 显然,对于插入或删除数据越频繁的操作,单链表的效率优势就越明显;