单链表的插入和删除
廖家龙 用心听,不照做

单链表的插入

按位序插入(带头结点):在表L中的第i个位置上插入指定元素e(最好O(1),最坏O(n),平均O(n))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>

typedef struct LNode{ //LNode:结点
int data; //数据域:每个结点存放一个数据元素
struct LNode *next; //指针域:指针指向下一个结点
}LNode,*LinkList; //typedef struct LNode *LinkList; 要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点

//初始化一个单链表(带头结点)
bool InitList(LinkList *L){
(*L)=(LNode *)malloc(sizeof(LNode)); //分配一个头结点,并使得头指针*L指向这个头结点
if((*L) == NULL){ //内存不足,分配失败
return false;
}
(*L)->data=0; //头结点不存储数据
(*L)->next=NULL; //头结点之后暂时还没有结点
return true;
}

//在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList *L,int i,int e){
if(i<1){
return false;
}
LNode *p; //指针p指向当前扫描到的结点
int j=0; //当前p指向的是第几个结点
p=*L; //L指向头结点,头结点是第0个结点(不存数据)

while(p != NULL && j<i-1){ //循环找到第i-1个结点
p=p->next;
j++;
}
if(p == NULL){ //i值不合法
return false;
}

LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next; //这行代码跟下一行代码顺序不能调换
p->next=s; //将结点s连到p之后

return true; //插入成功
}

int main(){

LinkList L; //等价于LNode *L; 声明一个指向单链表的第一个结点的指针,注意此处并没有创建一个结点
InitList(&L); //初始化一个空表

//...后续代码
if(ListInsert(&L, 2, 3)){ //在第2个位置插入元素3
printf("插入成功");
}
else{
printf("插入失败");
}
}

按位序插入(不带头结点):因为不存在“第0个”结点,因此i=1时需要特殊处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>

typedef struct LNode{ //LNode:结点
int data; //数据域:每个结点存放一个数据元素
struct LNode *next; //指针域:指针指向下一个结点
}LNode,*LinkList; //typedef struct LNode *LinkList; 要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点

//初始化一个空的链表(不带头结点)
bool InitList(LinkList *L){
(*L)=NULL; //空表,暂时还没有任何结点,防止脏数据
return true;
}

//在第i个位置插入元素e(不带头结点)
bool ListInsert(LinkList *L,int i,int e){
if(i<1){
return false;
}

//如果不带头结点,则插入、删除第1个元素时,需要更改头指针L
if(i == 1){ //插入第1个结点的操作与其他结点操作不同
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=*L;
*L=s; //头指针指向新结点
return true;
}

LNode *p; //指针p指向当前扫描到的结点
int j=0; //当前p指向的是第几个结点
p=*L; //L指向头结点,头结点是第0个结点(不存数据)

while(p != NULL && j<i-1){ //循环找到第i-1个结点
p=p->next;
j++;
}
if(p == NULL){ //i值不合法
return false;
}

LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next; //这行代码跟下一行代码顺序不能调换
p->next=s; //将结点s连到p之后

return true; //插入成功
}

int main(){

LinkList L; //等价于LNode *L; 声明一个指向单链表的第一个结点的指针,注意此处并没有创建一个结点
InitList(&L); //初始化一个空表

//...后续代码
if(ListInsert(&L, 2, 3)){ //在第2个位置插入元素3
printf("插入成功");
}
else{
printf("插入失败");
}
}

指定结点的后插操作(O(1)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>

typedef struct LNode{ //LNode:结点
int data; //数据域:每个结点存放一个数据元素
struct LNode *next; //指针域:指针指向下一个结点
}LNode,*LinkList; //typedef struct LNode *LinkList; 要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点

//初始化一个单链表(带头结点)
bool InitList(LinkList *L){
(*L)=(LNode *)malloc(sizeof(LNode)); //分配一个头结点,并使得头指针*L指向这个头结点
if((*L) == NULL){ //内存不足,分配失败
return false;
}
(*L)->data=0; //头结点不存储数据
(*L)->next=NULL; //头结点之后暂时还没有结点
return true;
}

//后插操作:在p结点之后插入元素e(带头结点)
bool InsertNextNode(LNode *p,int e){

if(p == NULL){ //i值不合法
return false;
}

LNode *s=(LNode *)malloc(sizeof(LNode));

if(s == NULL){ //内存分配失败,某些情况下有可能分配失败(如内存不足)
return false;
}

s->data=e; //用结点s保存数据元素e
s->next=p->next; //这行代码跟下一行代码顺序不能调换
p->next=s; //将结点s连到p之后

return true; //插入成功
}

int main(){

LinkList L; //等价于LNode *L; 声明一个指向单链表的第一个结点的指针,注意此处并没有创建一个结点
InitList(&L); //初始化一个空表

//...后续代码
if(InsertNextNode(L, 3)){ //在p结点位置后插入元素3
printf("插入成功");
}
else{
printf("插入失败");
}
}

指定结点的前插操作:在p结点之前插入元素e(O(1))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>

typedef struct LNode{ //LNode:结点
int data; //数据域:每个结点存放一个数据元素
struct LNode *next; //指针域:指针指向下一个结点
}LNode,*LinkList; //typedef struct LNode *LinkList; 要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点

//初始化一个单链表(带头结点)
bool InitList(LinkList *L){
(*L)=(LNode *)malloc(sizeof(LNode)); //分配一个头结点,并使得头指针*L指向这个头结点
if((*L) == NULL){ //内存不足,分配失败
return false;
}
(*L)->data=0; //头结点不存储数据
(*L)->next=NULL; //头结点之后暂时还没有结点
return true;
}

//前插操作:在p结点之前插入元素e(带头结点)
bool InsertPriorNode(LNode *p,int e){

if(p == NULL){ //i值不合法
return false;
}

LNode *s=(LNode *)malloc(sizeof(LNode));

if(s == NULL){ //内存分配失败,某些情况下有可能分配失败(如内存不足)
return false;
}

s->next=p->next;
p->next=s; //新结点s连到p之后
s->data=p->data; //将p中元素复制到s中
p->data=e; //p中元素覆盖为e

return true; //插入成功
}

int main(){

LinkList L; //等价于LNode *L; 声明一个指向单链表的第一个结点的指针,注意此处并没有创建一个结点
InitList(&L); //初始化一个空表

//...后续代码
if(InsertPriorNode(L, 3)){ //在p结点位置前插入元素3
printf("插入成功");
}
else{
printf("插入失败");
}
}

单链表的删除

按位序删除(带头结点,最坏、平均O(n),最好O(1)):删除表L中第i个位置的元素,并用e返回删除元素的值(找到第i-1个结点,并将其指针指向第i+1个结点,并释放第i个结点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>

typedef struct LNode{ //LNode:结点
int data; //数据域:每个结点存放一个数据元素
struct LNode *next; //指针域:指针指向下一个结点
}LNode,*LinkList; //typedef struct LNode *LinkList; 要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点

//初始化一个单链表(带头结点)
bool InitList(LinkList *L){
(*L)=(LNode *)malloc(sizeof(LNode)); //分配一个头结点,并使得头指针*L指向这个头结点
if((*L) == NULL){ //内存不足,分配失败
return false;
}
(*L)->data=0; //头结点不存储数据
(*L)->next=NULL; //头结点之后暂时还没有结点
return true;
}

//按位序删除(带头结点)
bool ListDelete(LinkList *L,int i,int *e){

if(i<1){
return false;
}
LNode *p; //指针p指向当前扫描到的结点
int j=0; //当前p指向的是第几个结点
p=*L; //L指向头结点,头结点是第0个结点(不存数据)

while(p != NULL && j<i-1){ //循环找到第i-1个结点
p=p->next;
j++;
}
if(p == NULL){ //i值不合法
return false;
}

if(p->next == NULL){ //在第i-1个结点之后已无其他结点
return false;
}

LNode *q=p->next; //令q指向被删除结点
*e=q->data; //用e返回元素的值
p->next=q->next; //将*q结点从链中断开
free(q); //释放结点的存储空间

return true; //删除成功
}

int main(){

LinkList L; //等价于LNode *L; 声明一个指向单链表的第一个结点的指针,注意此处并没有创建一个结点
InitList(&L); //初始化一个空表

//...后续代码
int e=-1; //用变量e把删除的元素“带回来”
if(ListDelete(&L, 2, &e)){
printf("已删除第2个元素,删除元素值为%d\n",e);
}
else{
printf("删除失败\n");
}
}

指定结点的删除(O(1)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>

typedef struct LNode{ //LNode:结点
int data; //数据域:每个结点存放一个数据元素
struct LNode *next; //指针域:指针指向下一个结点
}LNode,*LinkList; //typedef struct LNode *LinkList; 要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点

//初始化一个单链表(带头结点)
bool InitList(LinkList *L){
(*L)=(LNode *)malloc(sizeof(LNode)); //分配一个头结点,并使得头指针*L指向这个头结点
if((*L) == NULL){ //内存不足,分配失败
return false;
}
(*L)->data=0; //头结点不存储数据
(*L)->next=NULL; //头结点之后暂时还没有结点
return true;
}

//删除指定结点p(带头结点)
//如果p是最后一个结点,这段代码就有点问题了,解决办法只能从表头开始依次寻找p的前驱,时间复杂度为O(n)
//单链表的局限性:无法逆向检索,有时候不太方便
bool DeleteNode(LNode *p){

if(p == NULL){
return false;
}

LNode *q=p->next; //令q指向*p的后继结点
p->data=p->next->data; //和后继结点交换数据域
p->next=q->next; //将*q结点从链中“断开”
free(q); //释放后继结点的存储空间

return true; //删除成功
}

int main(){

LinkList L; //等价于LNode *L; 声明一个指向单链表的第一个结点的指针,注意此处并没有创建一个结点
InitList(&L); //初始化一个空表

//...后续代码

if(DeleteNode(L)){
printf("删除成功\n");
}
else{
printf("删除失败\n");
}
}