-----想必大多数人和我同样,刚开始学数据结构中的单链表仍是蛮吃力的,特别是后面的双链表操做更是如此。还有就是在实践代码操做时,你又会感到无从下手,没有思路。形成这样的原因,仍是没有彻底把链表吃透,今天恰好看书又看到了这里,总结一下,分享给你们,但愿对你们有帮助。node
1、链表引入的原因:
在一开始,不知你们用了这么久的数组,你有没有发现数组存在两个明显的缺陷?1)一个是数组中全部元素的类型必须一致;2)第二个是数组的元素个数必须事先制定而且一旦指定以后不能更改。因而乎为了解决数组的缺陷,先辈们发明的一些特殊方法来解决:a、数组的第一个缺陷靠结构体去解决。结构体容许其中的元素的类型不相同,所以解决了数组的第一个缺陷。因此说结构体是由于数组不能解决某些问题因此才发明的;b、咱们但愿数组的大小可以实时扩展。譬如我刚开始定了一个元素个数是10,后来程序运行时以为不够所以动态扩展为20.普通的数组显然不行,咱们能够对数组进行封装以达到这种目的;咱们还可使用一个新的数据结构来解决,这个新的数据结构就是链表(几乎能够这样理解:链表就是一个元素个数能够实时变大/变小的数组)。程序员
2、什么是链表?
顾名思义,链表就是用锁链链接起来的表。这里的表指的是一个一个的节点(一个节点能够比喻成大楼里面的空房子同样用来存放东西的),节点中有一些内存能够用来存储数据(因此叫表,表就是数据表);这里的锁链指的是连接各个表的方法,C语言中用来链接2个表(其实就是2块内存)的方法就是指针。它的特色是:它是由若干个节点组成的(链表的各个节点结构是彻底相似的),节点是由有效数据和指针组成的。有效数据区域用来存储信息完成任务的,指针区域用于指向链表的下一个节点从而构成链表。ubuntu
3、单链表中的一些细节:
一、单链表的构成:
a、链表是由节点组成的,节点中包含:有效数据和指针。
b、定义的struct node只是一个结构体,自己并无变量生成,也不占用内存。结构体定义至关于为链表节点定义了一个模板,可是尚未一个节点,未来在实际建立链表时须要一个节点时用这个模板来复制一个便可。例如:api
1 struct node{
2 int data;//有效数据
3
4struct node *pNext;//指向下一个节点的指针
5
6 };//构建一个链表的节点
二、堆内存的申请和使用:
a、先了解一下什么是堆:堆(heap)是种内存管理方式,它的特色是:就是自由管理(随时申请,灵活,大小块随意)。堆内存是操做系统规划给堆管理器(操做系统中的的一段代码,属于操做系统的内存管理单元),来管理的,而后向使用者(用户进程)提供api(malloc和free)来使用堆内存。
b、为何要使用堆呢?数组
须要内存容量比较大的时候,须要反复使用及释放时,须要反复使用及释放不少数据结构(譬如链表)的实现都要使用堆内存;它的特色:容量不限(常规使用的需求容量都能知足),申请及释放都须要手工进行,手工进行的含义就是须要程序员写代码明确进行申请malloc及释放free。若是程序员申请内存并使用没有释放,这段内存就丢失了(在堆管理器的记录中,这段内存仍然属于你这个进程,可是进程本身又觉得这段内存已经不用了,再用的时候又会申请新的内存块,这就叫吃内存),称为内存泄漏。数据结构
c、基本概念:
做用域:起做用的区域,也就是能够工做的范围。
代码块:所谓代码块,就是用{}括起来的一段代码。
数据段:数据段存的是数,像全局变量就是存在数据段的
代码段:存的是程序代码,通常是只读的。
栈(stack):先进后出。C语言中局部变量就分配在栈中。
这里顺便也讲一下什么是栈:
栈是一种数据结构,c语言中使用栈来保存局部变量。栈是被发明出来管理内存的;它的特色:是先进后出;而先进先出,它是队列的特色;栈的特色是入口即出口,另一个口是堵死的。因此先进去的必须后出来队列的特色是入口和出口都有,必须从入口进去,从出口出来,因此先进去的必须先出来,不然就堵住后面的。在c 语言中的局部变量是用栈来实现的。咱们在c中定义一个局部变量时(int a ),编译器会在栈中分配一段空间(4字节)给这个局部变量用(分配时栈顶指针会移动给出空间,给局部变量a用的意思就是,将这4字节的栈内存地址和咱们定义的局部变量名a 给关联起来),对应栈的操做时入栈;
-----注意:这里栈指针的移动和内存分配是自动的(栈本身完成,不用咱们写代码去操做);而后等咱们函数退出的时候,局部变量要灭亡。对应栈的操做时出栈。出栈时也是栈顶指针移动将栈空间中与a关联的那4个字节空间释放。这个动做也是自动的,也不用人去写代码去控制。栈的优势:栈管理内存,好处是方便,分配和最后回收都不用程序员操心,c语言自动完成。分析一个细节:c语言中,定义局部变量时若是未初始化,则值时随机的为何?定义局部变量,其实就是在栈中经过移动栈指针来给程序提供一个内存空间和这个局部变量名绑定,由于这段内存空间在栈上,而栈内存是反复使用的(脏的,上次用完没有清零的),因此说使用栈来实现的局部变量定义时若是不显示初始化,值就是脏的。若是你显示初始化会怎样?c语言是经过一个小手段来实现局部变量的初始化的。好比 int a=10;至关于
int a ;
a=10;
栈的缺点:首先,栈是有大小的。因此栈内存大小很差设置,若是过小怕溢出,太大跑浪费内存;因此栈的溢出危害很大,必定避免。因此咱们在c语言中定义局部变量时不能定义太多或者太大(譬如不能定义局部变量时int a[10000])
使用递归来解决问题时必定要注意递归收敛.
d、注意:链表的内存要求比较灵活,不能用栈,也不能用data数据段。只能用堆内存。
使用堆内存来建立一个链表节点的步骤:一、申请堆内存,大小为一个节点的大小(检查申请结果是否正确);二、清理申请到的堆内存;三、把申请到的堆内存看成一个新节点;四、填充你哦个新节点的有效数据和指针区域。
实例:函数
1 #include <stdio.h> 2 #include <strings.h> 3 #include <stdlib.h> 4 int main(void) 5{ 6 //建立一个链表节点 7 struct node *p=(struct node*)malloc(sizeof(struct node)); 8 if(NULL==p) 9 { 10 printf("malloc error.\n"); 11 } 12 //清理申请到的堆内存 13 bzero(p,sizeof(struct node)); 14 //填充节点 15 p->data=1; 16 p->pNext =NULL;//未来要指向下一个节点的首地址;实际操做时将下 一 个节点malloc返回的指针赋值给这个 17}
4、实例演示:学习
一、单链表的实现:spa
1 #include <stdio.h> 2 #include <strings.h> 3 #include <stdlib.h> 4 // 构建一个链表的节点 5 struct node 6 { 7 int data; // 有效数据 8struct node *pNext; // 指向下一个节点的指针 9 }; 10 int main(void) 11 { 12// 定义头指针 13struct node *pHeader = NULL; 14/********************************************************************/ 15// 每建立一个新的节点,把这个新的节点和它前一个节点关联起来 16// 建立一个链表节点 17struct node *p = (struct node *)malloc(sizeof(struct node)); 18if (NULL == p) 19{ 20 printf("malloc error.\n"); 21 return -1; 22} 23// 清理申请到的堆内存 24bzero(p, sizeof(struct node)); 25// 填充节点 26p->data = 1; 27p->pNext = NULL; // 未来要指向下一个节点的首地址 28 // 实际操做时将下一个节点malloc返回的指针赋值给这个 29 30pHeader = p; // 将本节点和它前面的头指针关联起来 31/********************************************************************/ 32/********************************************************************/ 33// 每建立一个新的节点,把这个新的节点和它前一个节点关联起来 34// 建立一个链表节点 35struct node *p1 = (struct node *)malloc(sizeof(struct node)); 36if (NULL == p1) 37{ 38 printf("malloc error.\n"); 39 return -1; 40} 41// 清理申请到的堆内存 42bzero(p1, sizeof(struct node)); 43// 填充节点 44p1->data = 2; 45p1->pNext = NULL; // 未来要指向下一个节点的首地址 46 // 实际操做时将下一个节点malloc返回的指针赋值给这个 47 48 49p->pNext = p1; // 将本节点和它前面的头指针关联起来 50 51 52/********************************************************************/ 53 54/********************************************************************/ 55 56// 每建立一个新的节点,把这个新的节点和它前一个节点关联起来 57 58// 建立一个链表节点 59 60struct node *p2 = (struct node *)malloc(sizeof(struct node)); 61if (NULL == p2) 62{ 63 printf("malloc error.\n"); 64 return -1; 65} 66// 清理申请到的堆内存 67bzero(p2, sizeof(struct node)); 68// 填充节点 69p2->data = 3; 70p1->pNext = p2; // 未来要指向下一个节点的首地址 71 // 实际操做时将下一个节点malloc返回的指针赋值给这个 72/********************************************************************/ 73// 至此建立了一个有1个头指针+3个完整节点的链表。 74 75// 下面是4.9.3节的代码 76// 访问链表中的各个节点的有效数据,这个访问必须注意不能使用p、p一、p2,而只能 77// 使用pHeader。 78 79// 访问链表第1个节点的有效数据 80printf("node1 data: %d.\n", pHeader->data); 81printf("p->data: %d.\n", p->data); // pHeader->data等同于p->data 82 83// 访问链表第2个节点的有效数据 84printf("node2 data: %d.\n", pHeader->pNext->data); 85printf("p1->data: %d.\n", p1->data); 86// pHeader->pNext->data等同于p1->data 87 88// 访问链表第3个节点的有效数据 89printf("node3 data: %d.\n", pHeader->pNext->pNext->data); 90printf("p2->data: %d.\n", p2->data); 91// pHeader->pNext->pNext->data等同于p2->data 92 93return 0; 94}
编译结果以下:操作系统
root@ubuntu-virtual-machine:/mnt/hgfs/day# gcc file2.c 2 root@ubuntu-virtual-machine:/mnt/hgfs/day# ./a.out 3 node1 data: 1. 4 p->data: 1. 5 node2 data: 2. 6 p1->data: 2. 7 node3 data: 3. 8 p2->data: 3.
二、在链表末尾添加元素:
思路:由头指针向后遍历,直到走到原来的最后一个节点。原来最后一个节点里面的pNext是NULL,如今咱们只要将它改为new就能够了。添加了以后新节点就变成了最后一个。代码实例:
1 #include <stdio.h> 2 #include <strings.h> 3 #include <stdlib.h> 4 // 构建一个链表的节点 5 struct node 6{ 7int data; // 有效数据 8struct node *pNext; // 指向下一个节点的指针 9 }; 10// 做用:建立一个链表节点 11// 返回值:指针,指针指向咱们本函数新建立的一个节点的首地址 12struct node * create_node(int data) 13{ 14struct node *p = (struct node *)malloc(sizeof(struct node)); 15if (NULL == p) 16{ 17 printf("malloc error.\n"); 18 return NULL; 19} 20// 清理申请到的堆内存 21bzero(p, sizeof(struct node)); 22// 填充节点 23p->data = data; 24p->pNext = NULL; 25return p; 26 } 27 void insert_tail(struct node *pH, struct node *new) 28 { 29// 分两步来完成插入 30// 第一步,先找到链表中最后一个节点 31struct node *p = pH; 32while (NULL != p->pNext) 33{ 34 p = p->pNext; 35// 日后走一个节点 36} 37// 第二步,将新节点插入到最后一个节点尾部 38p->pNext = new; 39 } 40 int main(void) 41 { 42// 定义头指针 43//struct node *pHeader = NULL; 44// 这样直接insert_tail会段错误。 45struct node *pHeader = create_node(1); 46insert_tail(pHeader, create_node(2)); 47insert_tail(pHeader, create_node(3)); 48insert_tail(pHeader, create_node(4)); 49 /* 50pHeader = create_node(1); 51 // 将本节点和它前面的头指针关联起来 52pHeader->pNext = create_node(432); 53// 将本节点和它前面的头指针关联起来 54 55pHeader->pNext->pNext = create_node(123); 56// 未来要指向下一个节点的首地址 57 58// 至此建立了一个有1个头指针+3个完整节点的链表。 59 */ 60 // 访问链表中的各个节点的有效数据,这个访问必须注意不能使用p、p一、p2,而只能 61 // 使用pHeader。 62// 访问链表第1个节点的有效数据 63printf("node1 data: %d.\n", pHeader->data); 64//printf("p->data: %d.\n", p->data); 65 // pHeader->data等同于p->data 66// 访问链表第2个节点的有效数据 67printf("node2 data: %d.\n", pHeader->pNext->data); 68//printf("p1->data: %d.\n", p1->data); 69// pHeader->pNext->data等同于p1->data 70// 访问链表第3个节点的有效数据 71printf("node3 data: %d.\n", pHeader->pNext->pNext->data); 72//printf("p2->data: %d.\n", p2->data); 73// pHeader->pNext->pNext->data等同于p2->data 74return 0; 75}
编译结果:
1root@ubuntu-virtual-machine:/mnt/hgfs/day# gcc file3.c 2root@ubuntu-virtual-machine:/mnt/hgfs/day# ./a.out 3node1 data: 1. 4node2 data: 2. 5node3 data: 3.
三、在第一个节点插入元素:
a、什么是头指针?
头指针并非节点,而是一个普通指针,只占4字节。头指针的类型是struct node *类型的,因此它才能指向链表的节点。一个典型的链表的实现就是:头指针指向链表的第1个节点,而后第1个节点中的指针指向下一个节点,而后依次类推一直到最后一个节点。这样就构成了一个链
b、什么是头节点?
其实它和通常的节点差很少,只不过要注意的是:第一,它紧跟在头指针后面。第二,头节点的数据部分是空的(有时候不是空的,而是存储整个链表的节点数),指针部分指向下一个节点,也就是第一个节点。
1 #include <stdio.h> 2 #include <strings.h> 3 #include <stdlib.h> 4 // 构建一个链表的节点 5 struct node 6 { 7int data; // 有效数据 8struct node *pNext; // 指向下一个节点的指针 9 }; 10 // 做用:建立一个链表节点 11 // 返回值:指针,指针指向咱们本函数新建立的一个节点的首地址 12 struct node * create_node(int data) 13 { 14struct node *p = (struct node *)malloc(sizeof(struct node)); 15if (NULL == p) 16{ 17 printf("malloc error.\n"); 18 return NULL; 19 } 20// 清理申请到的堆内存 21bzero(p, sizeof(struct node)); 22// 填充节点 23p->data = data; 24p->pNext = NULL; 25return p; 26 } 27 // 思路:由头指针向后遍历,直到走到原来的最后一个节点。原来最后一个节点里面的pNext是NULL,如今咱们只要将它改为new就能够了。添加了以后新节点就变成了最后一个。 28 29 // 计算添加了新的节点后总共有多少个节点,而后把这个数写进头节点中。 30 31void insert_tail(struct node *pH, struct node *new) 32 { 33int cnt = 0; 34// 分两步来完成插入 35// 第一步,先找到链表中最后一个节点 36struct node *p = pH; 37while (NULL != p->pNext) 38{ 39 p = p->pNext; 40 // 日后走一个节点 41 cnt++; 42} 43// 第二步,将新节点插入到最后一个节点尾部 44p->pNext = new; 45pH->data = cnt + 1; 46 } 47void insert_head(struct node *pH, struct node *new) 48{ 49// 第1步: 新节点的next指向原来的第一个节点 50new->pNext = pH->pNext; 51// 第2步: 头节点的next指向新节点的地址 52pH->pNext = new; 53// 第3步: 头节点中的计数要加1 54pH->data += 1; 55 } 56int main(void) 57{ 58// 定义头指针 59//struct node *pHeader = NULL; 60 // 这样直接insert_tail会段错误。 61struct node *pHeader = create_node(0); 62insert_head(pHeader, create_node(1)); 63insert_tail(pHeader, create_node(2)); 64insert_head(pHeader, create_node(3)); 65 /* 66pHeader = create_node(1); 67 68// 将本节点和它前面的头指针关联起来 69pHeader->pNext = create_node(432); 70// 将本节点和它前面的头指针关联起来 71pHeader->pNext->pNext = create_node(123); 72// 未来要指向下一个节点的首地址 73// 至此建立了一个有1个头指针+3个完整节点的链表。 74 */ 75// 访问链表中的各个节点的有效数据,这个访问必须注意不能使用 p、p一、p2,而只能 76// 使用pHeader。 77// 访问链表头节点的有效数据 78printf("beader node data: %d.\n", pHeader->data); 79// 访问链表第1个节点的有效数据 80printf("node1 data: %d.\n", pHeader->pNext->data); 81// 访问链表第2个节点的有效数据 82printf("node2 data: %d.\n", pHeader->pNext->pNext->data); 83// 访问链表第3个节点的有效数据 84printf("node3 data: %d.\n", pHeader->pNext->pNext->pNext->data); 85return 0; 86}
编译结果:
1 root@ubuntu-virtual-machine:/mnt/hgfs/day# gcc file4.c 2 root@ubuntu-virtual-machine:/mnt/hgfs/day# ./a.out 3 beader node data: 3. 4 node1 data: 3. 5 node2 data: 1. 6 node3 data: 2.
5、总结:
经过本次链表的学习,让本身对链表的理解更加深了,接下来双链表的使用会在后面更新,欢迎你们来关注!!