目录
1. 单链表概述
1.1 为什么要有单链表
系统定义的数据类型有整型、字符型、指针型,这些数据类型都是在代码编译时就确定了内存位置,编译后占用内存是固定的。意思是在每次程序运行时,程序就在内存中开辟了一段内存,这段内存中就放代码中这些变量。这就存在一个问题,如果需要定义一个班的学生,那我在开始时就需要确定这个班级有多少学生,确定数量后定义创建需要的内存。后面数量就能修改,这是不是缺少灵活性。
针对这种不灵活性就有的 动态内存分配 机制(malloc申请、free释放),就是编译时不知道程序运行时需要开辟多少内存,内存占用多少由用户自己定义,如班级有30个学生,在程序运行后开辟30的学生的内存占用,有100个学生,在程序运行后开辟100的学生的内存占用.通过用户输入来确定内容占用。但是这也有一个缺点就是一次需要定义一整块内存,这就对系统不是那么友好。
针对这种需要一次申请一整块内存的 动态内存分配 的缺点计算机专家就创建了链表形式。链表形式在编译时不指定内存占用,由用户使用时进行 动态内存分配,分配大小是按照单个struct结构占用的内存大小进行分配的,这样可以有效的使用内存。如还是学生的例子,每次需要创建新的学生时就 分配一个学生的结构体大小的内存,增加一个学生就创建一个学生需要的内存,而不需要一次定义30个或者100个学生的内存。
1.2 单链表形式
单链表是通过struct结构体实现的,特点是在结构体中包含一个本身结构体类型的指针,通过这个指针指向下一个结构体的地址。
5 struct node
6 {
7 int num;
8 struct node* next;
9 };
在main函数中使用这个结构体时
106 struct node nod1;
107 nod1.num = 100;
108 nod1.next = NULL;
109 printf("nod1.num = %d, nod1.next = %#lx\n", nod1.num, nod1.next);
通过GDB调试打印结构体变量名中的内容时如下图所示
从GDB调试信息可以看出,结构体名中获取的是整个结构体中各变量对应的各种类型值,类似于整型变量名中存放的是整型值。如这里的结构体中int num = 100,struct node* next = 0x0.
从GDB调试信息还可以看出,结构体名中不是地址,有时会把它看做跟字符数组一样以为变量名可以当地址使用,其实结构体名不是地址。
2. 增加节点
2.1 文字解释
需要增加节点首先需要知道最开始的节点是在哪里,这里称最开始的节点称为头结点,这个头节点并不是实际中对应的数据,如创建一个学生的链表会定义一个头结点,这个头结点不是对应某个真实学生的信息。那为什需要定义头结点呢?因为在结构体中有一个包含下一个节点地址的结构体指针变量,通过这个指针可以知道下一个节点存放在哪里,如第一个学生信息的结构体存放在结构体的哪个内存中。然后第一个结构体有包含第二个结构体地址,第二个包含第三个结构体地址,这样一直下去就可以知道所有的学生信息存放的位置了。
说了头结点下面是真实节点需要如何创建:
- 首先节点需要通过malloc函数动态内存分配内存;
- 然后根据需要创建的信息,将信息赋值给结构体中的变量;
- 最后需要将创建的节点地址给到上一个结构体的结构体指针中;
2.2 代码逻辑
11 lnode* head = NULL;
12
13 lnode* creathead()
14 {
15 lnode* p = (lnode*) malloc(sizeof(lnode));
16 p->num = 10;
17 p->next = NULL;
18
19 return p;
20 }
21
22 void insert(int id)
23 {
24 lnode* p = (lnode*) malloc(sizeof(lnode));
25 p->num = id;
26 p->next = NULL;
27
28 lnode* current = head;
29 while(current->next != NULL)
30 {
31 current = current->next;
32 }
33 current->next = p;
34 }
如上面 的代码所示:
- 先通过creathead函数创建头结点,头结点中的num值是随意设置的不是真实的数据。
- 创建节点函数insert中需要传入非结构体指针值,如这里的num值。在insert函数中通过malloc函数先动态分配内存,分配好后就可以在这片内存中给结构体赋值。
- 通过节点变量从头结点开始往下依次去检查节点中指向下一个节点的指针中的内容是否为空 current->next != NULL,如果不为空那就再检查下一个,直到下一个节点是空,因为这个节点就是最后一个节点。这时需要把刚才创建的节点地址给的放到最后一个节点中 current->next = p,这个新的节点就成为了新的最后一个节点
3. 查找节点
3.1 文字解释
查找节点其实就是查找结构体中某个变量的值,根据用户输入的查找条件进行整个链表中所有结构体都查询一遍。这里定义的结构体比较简单只有 int num数据和结构体指针,只有int num是可以查询的。
3.2 代码逻辑
47 int find(int id)
48 {
49 lnode* current = head;
50 while(current->next != NULL)
51 {
52 if(current->num == id)
53 {
54 printf("find node which id = %d\n",id);
55 return 1;
56 }
57 current = current->next;
58 }
59 printf("not have node which id = %d\n",id);
60 return 0;
61 }
如上面 的代码所示:
- 先将头结点赋值给结构体指针
- 结构体指针开始遍历用whil( current->next != NULL) 判断是否到了最后一个节点,如果没有到最后可以那就继续在结构体中循环查找,如果到了最后说明没有找到要找的数据。 current->num == id 判断是否是需要查找的数据。current = current->next 不断变换到下一个节点,直到找到需要查找的内容为止。
注意点:
起始赋值时可以是lnode* current = head;
也可以是lnode* current = head->next;
但这是有区别的,前者是从需要配合的是 while(current->next != NULL)
后者需要配合的是 while(current != NULL)
注意这些细节区别
4. 删除节点
4.1 文字解释
删除节点其实分为两部分,第一部分先找到节点,第二部分将节点删除。找节点跟第3节 的内容一致,不做复述。删除节点的逻辑是如有1、2、3个节点,2节点是需要删除的,那只需要将1号节点中存放的2号节点的地址改成3号节点的地址就可以了。为了防止内存泄漏所以需要通过free将2号节点占用的内存释放掉。
4.2 代码逻辑
63 int delete(int id)
64 {
65 lnode* current = head;
66 lnode* p = NULL;
67 while(current->next != NULL)
68 {
69 if(current->next->num == id)
70 {
71 printf("find node which id = %d and delete this node\n",id);
72 p = current->next;
73 current->next = p->next;
74 free(p);
75 return 1;
76 }
77 current = current->next;
78 }
79
80 printf("not have node which id = %d and not delete \n",id);
81 return 0;
82 }
这里不同于其他模块是定义了两个结构体指针,第二个结构体指针的作用是在找到需要的节点(2号节点)时把找到的节点赋值个第二个指针,然后通过这个指针知道下一个节点(3号节点),这里有个细节是通第一个节点(1号节点)指向的下一个节点(2号节点)的数值判断是否是要找到值 current->next->num == id
这样的好处是第一个节点还在,第二、三个节点也知道。
5. 修改节点
5.1 文字解释
修改节点其实分也为两部分,第一部分先找到节点,第二部分将节点中的值进行修改。找节点跟第3节 的内容一致,不做复述。修改值只是将结构体中的值替换即可
5.2 代码逻辑
84 int alter(int befordata, int nowdata )
85 {
86 lnode* current = head->next;
87 while(current->next != NULL)
88 {
89 if(current->num == befordata)
90 {
91 printf("find node which id = %d and alter this node num = %d\n", befordata, nowdata);
92 current->num = nowdata;
93 return 1;
94 }
95
96 current = current->next;
97 }
98
99 printf("not have node which id = %d and not alter \n",befordata);
100 return 0;
101 }
6. 显示节点
6.1 文字解释
显示节点也是通过不断遍历将结构体中的内容进行显示,这里不是应该比较好理解不做解释。显示节点的函数可以在每次有数据变化时进行显示
6.2 代码逻辑
36 void show()
37 {
38
39 lnode* current = head->next;
40 while(current->next != NULL)
41 {
42 printf("lnode.num = %d\n", current->num);
43 current = current->next;
44 }
45 }
7. 项目代码
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 struct node
5 {
6 int num;
7 struct node* next;
8 };
9
10 typedef struct node lnode;
11 lnode* head = NULL;
12
13 lnode* creathead()
14 {
15 lnode* p = (lnode*) malloc(sizeof(lnode));
16 p->num = 10;
17 p->next = NULL;
18
19 return p;
20 }
21
22 void insert(int id)
23 {
24 lnode* p = (lnode*) malloc(sizeof(lnode));
25 p->num = id;
26 p->next = NULL;
27
28 lnode* current = head;
29 while(current->next != NULL)
30 {
31 current = current->next;
32 }
33 current->next = p;
34 }
35
36 void show()
37 {
38
39 lnode* current = head->next;
40 while(current->next != NULL)
41 {
42 printf("lnode.num = %d\n", current->num);
43 current = current->next;
44 }
45 }
46
47 int find(int id)
48 {
49 lnode* current = head;
50 while(current->next != NULL)
51 {
52 if(current->num == id)
53 {
54 printf("find node which id = %d\n",id);
55 return 1;
56 }
57 current = current->next;
58 }
59 printf("not have node which id = %d\n",id);
60 return 0;
61 }
62
63 int delete(int id)
64 {
65 lnode* current = head->next;
66 lnode* p = NULL;
67 while(current->next != NULL)
68 {
69 if(current->next->num == id)
70 {
71 printf("find node which id = %d and delete this node\n",id);
72 p = current->next;
73 current->next = p->next;
74 free(p);
75 return 1;
76 }
77 current = current->next;
78 }
79
80 printf("not have node which id = %d and not delete \n",id);
81 return 0;
82 }
83
84 int alter(int befordata, int nowdata )
85 {
86 lnode* current = head->next;
87 while(current->next != NULL)
88 {
89 if(current->num == befordata)
90 {
91 printf("find node which id = %d and alter this node num = %d\n", befordata, nowdata);
92 current->num = nowdata;
93 return 1;
94 }
95
96 current = current->next;
97 }
98
99 printf("not have node which id = %d and not alter \n",befordata);
100 return 0;
101 }
102
103 int main()
104 {
105 struct node nod1;
106 nod1.num = 100;
107 nod1.next = NULL;
108 printf("nod1.num = %d, nod1.next = %#lx\n", nod1.num, nod1.next);
109
110 head = creathead();
111 printf("head->num = %d, head->next = %#lx\n", head->num, head->next);
112
113 insert(10); insert(20); insert(30);
114 insert(40); insert(50); insert(60);
115 show();
116
117 find(20);
118
119 delete(20);
120 show();
121
122 alter(30,70);
123 show();
124 }
总结:
单链表主要需要理解在结构体中有包含下一个结构体的地址,通过不断的遍历进行需要进行从操作。所展示的代码其实还不是很完善,有些细节还欠考虑。如查找中如果要找到在第一个节点上,而链表只有一个节点时是不是没有正确显示,这里只是提醒下。