0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

单链表学习的总结(一)

电子设计 来源:电子设计 作者:电子设计 2020-12-24 17:35 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

想必大多数人和我一样,刚开始学数据结构中的单链表还是蛮吃力的,特别是后面的双链表操作更是如此。还有就是在实践代码操作时,你又会感到无从下手,没有思路。造成这样的缘由,还是没有完全把链表吃透,今天刚好看书又看到了这里,总结一下,分享给大家,希望对大家有帮助。

一、链表引入的缘由:

在一开始,不知大家用了这么久的数组,你有没有发现数组存在两个明显的缺陷?1)一个是数组中所有元素的类型必须一致;2)第二个是数组的元素个数必须事先制定并且一旦指定之后不能更改。于是乎为了解决数组的缺陷,先辈们发明的一些特殊方法来解决:a、数组的第一个缺陷靠结构体去解决。结构体允许其中的元素的类型不相同,因此解决了数组的第一个缺陷。所以说结构体是因为数组不能解决某些问题所以才发明的;b、我们希望数组的大小能够实时扩展。譬如我刚开始定了一个元素个数是10,后来程序运行时觉得不够因此动态扩展为20.普通的数组显然不行,我们可以对数组进行封装以达到这种目的;我们还可以使用一个新的数据结构来解决,这个新的数据结构就是链表(几乎可以这样理解:链表就是一个元素个数可以实时变大/变小的数组)。

二、什么是链表?

顾名思义,链表就是用锁链连接起来的表。这里的表指的是一个一个的节点(一个节点可以比喻成大楼里面的空房子一样用来存放东西的),节点中有一些内存可以用来存储数据(所以叫表,表就是数据表);这里的锁链指的是链接各个表的方法,C语言中用来连接2个表(其实就是2块内存)的方法就是指针。它的特点是:它是由若干个节点组成的(链表的各个节点结构是完全类似的),节点是由有效数据和指针组成的。有效数据区域用来存储信息完成任务的,指针区域用于指向链表的下一个节点从而构成链表。

三、单链表中的一些细节:

1、单链表的构成:

a、链表是由节点组成的,节点中包含:有效数据和指针。

b、定义的struct node只是一个结构体,本身并没有变量生成,也不占用内存。结构体定义相当于为链表节点定义了一个模板,但是还没有一个节点,将来在实际创建链表时需要一个节点时用这个模板来复制一个即可。例如:

1 struct node{2 int data;//有效数据34struct node *pNext;//指向下一个节点的指针56 };//构建一个链表的节点。

2、堆内存的申请和使用:

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、申请堆内存,大小为一个节点的大小(检查申请结果是否正确);2、清理申请到的堆内存;3、把申请到的堆内存当作一个新节点;4、填充你哦个新节点的有效数据和指针区域。

实例:

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}

四、实例演示:

1、单链表的实现:

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返回的指针赋值给这个2930pHeader = p; // 将本节点和它前面的头指针关联起来 31//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返回的指针赋值给这个474849p->pNext = p1; // 将本节点和它前面的头指针关联起来 505152//5354//5556// 每创建一个新的节点,把这个新的节点和它前一个节点关联起来5758// 创建一个链表节点5960struct 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个完整节点的链表。7475// 下面是4.9.3节的代码76// 访问链表中的各个节点的有效数据,这个访问必须注意不能使用p、p1、p2,而只能77// 使用pHeader。7879// 访问链表第1个节点的有效数据80printf("node1 data: %d.n", pHeader->data); 81printf("p->data: %d.n", p->data); // pHeader->data等同于p->data8283// 访问链表第2个节点的有效数据84printf("node2 data: %d.n", pHeader->pNext->data); 85printf("p1->data: %d.n", p1->data); 86// pHeader->pNext->data等同于p1->data8788// 访问链表第3个节点的有效数据89printf("node3 data: %d.n", pHeader->pNext->pNext->data); 90printf("p2->data: %d.n", p2->data); 91// pHeader->pNext->pNext->data等同于p2->data9293return 0;94}

编译结果如下:

1 root@ubuntu-virtual-machine:/mnt/hgfs/day# gcc file2.c2 root@ubuntu-virtual-machine:/mnt/hgfs/day# ./a.out3 node1 data: 1.4 p->data: 1.5 node2 data: 2.6 p1->data: 2.7 node3 data: 3.8 p2->data: 3.

2、在链表末尾添加元素:

思路:由头指针向后遍历,直到走到原来的最后一个节点。原来最后一个节点里面的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// 将本节点和它前面的头指针关联起来 5455pHeader->pNext->pNext = create_node(123); 56// 将来要指向下一个节点的首地址5758// 至此创建了一个有1个头指针+3个完整节点的链表。59 */60 // 访问链表中的各个节点的有效数据,这个访问必须注意不能使用p、p1、p2,而只能61 // 使用pHeader。62// 访问链表第1个节点的有效数据63printf("node1 data: %d.n", pHeader->data); 64//printf("p->data: %d.n", p->data); 65 // pHeader->data等同于p->data66// 访问链表第2个节点的有效数据67printf("node2 data: %d.n", pHeader->pNext->data); 68//printf("p1->data: %d.n", p1->data); 69// pHeader->pNext->data等同于p1->data70// 访问链表第3个节点的有效数据71printf("node3 data: %d.n", pHeader->pNext->pNext->data); 72//printf("p2->data: %d.n", p2->data); 73// pHeader->pNext->pNext->data等同于p2->data74return 0;75}

编译结果:

1root@ubuntu-virtual-machine:/mnt/hgfs/day# gcc file3.c2root@ubuntu-virtual-machine:/mnt/hgfs/day# ./a.out3node1 data: 1.4node2 data: 2.5node3 data: 3.

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就可以了。添加了之后新节点就变成了最后一个。2829 // 计算添加了新的节点后总共有多少个节点,然后把这个数写进头节点中。3031void 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步: 头节点中的计数要加154pH->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);6768// 将本节点和它前面的头指针关联起来 69pHeader->pNext = create_node(432); 70// 将本节点和它前面的头指针关联起来 71pHeader->pNext->pNext = create_node(123); 72// 将来要指向下一个节点的首地址73// 至此创建了一个有1个头指针+3个完整节点的链表。74 */75// 访问链表中的各个节点的有效数据,这个访问必须注意不能使用 p、p1、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.c2 root@ubuntu-virtual-machine:/mnt/hgfs/day# ./a.out3 beader node data: 3.4 node1 data: 3.5 node2 data: 1.6 node3 data: 2.

五、总结:

通过本次链表的学习,让自己对链表的理解更加深了,接下来双链表的使用会在后面更新,欢迎大家来关注!!

审核编辑:符乾江
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • C语言
    +关注

    关注

    183

    文章

    7646

    浏览量

    146132
  • 可编程逻辑
    +关注

    关注

    7

    文章

    528

    浏览量

    45464
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    EMC PCB设计总结

    EMC PCB设计总结
    发表于 03-23 14:52 13次下载

    安路科技2025年度总结

    2026年2月6日,安路科技2025年度总结大会暨表彰盛典在上海圆满召开,公司全员齐聚堂,总结过去,展望未来。会上,公司管理层发表了主题为“拥抱变化,共同成长”的战略展望。
    的头像 发表于 02-24 11:25 841次阅读

    【「玩转高速电路:基于ANSYS HFSS的无源仿真实例」阅读体验】+书中实例总结分享

    宽度特性阻抗随着线宽增大而减小 厚度特性阻抗随着线厚增大而减小 介质厚度特性阻抗随着介质厚增大而增大 介质常数特性阻抗随着介电常数增大而减小 总结参数对端微带线和端带状线影响致线
    发表于 01-08 22:28

    总结学习硬件设计要点

    大家有所重视。 调试方法,多种多样,视情况而定,不能概而论,笔者总结了以下几个方法: a、示波器测量。当然,首先你得清楚你设计出来的电路,会出什么样的波形,才知道测出来对不对,也就是说,理论不行
    发表于 01-06 06:40

    无数据域双向链表的代码

    下面是个简单的示例,演示了如何使用无数据域双向链表进行插入和访问操作: #include #include// 包含offsetof宏 // 定义节点结构体 struct Node
    发表于 12-11 06:56

    实现个嵌入式的软件定时器

    般可分为两种:数组结构和链表结构。什么意思呢?这是(多个)软件定时器在内存中的存储方式,可以用数组来存,也可以用链表来存。 两者的优劣之分就是两种数据结构的特性之分:数组方式的定时器查找较快,但数量
    发表于 12-10 08:29

    rt_object_get_information获取到的链表为空怎么解决?

    rtt启动过程,在初始化堆的时候,进入rt_object_init,调用rt_object_get_information获取到的链表为空,导致系统起不来。
    发表于 10-11 11:44

    第1章 如何学习单片机

      在错误的道路上日夜兼程,最终也无法成功,方法和思路绝对是最重要的。本章节讲到的学习单片机的方法,都是作者学习单片机的无数经验和教训总结出来的瑰宝。通过作者前面的披荆斩棘,开辟了道路,可以告诉读者
    的头像 发表于 10-10 10:32 659次阅读

    常用PromQL查询案例总结

    在云原生时代,Prometheus已经成为监控领域的事实标准。作为名资深运维工程师,我见过太多团队在PromQL查询上踩坑,也见过太多因为监控不到位导致的生产事故。今天分享10个实战中最常用的PromQL查询案例,每个都是血泪经验的
    的头像 发表于 09-18 14:54 857次阅读

    rtt链表的rt_slist_for_each_entry编译报错怎么解决?

    这个要自己补头文件?
    发表于 09-18 07:16

    分享个嵌入式学习阶段规划

    给大家分享个嵌入式学习阶段规划: ()基础筑牢阶段(约 23 天) 核心目标:打牢 C 语言、数据结构、电路基础C 语言开发:学变量 / 指针 / 结构体等核心语法,用 Dev-C++ 实操
    发表于 09-12 15:11

    文详解半导体器件中的粒子效应

    我们知道,带电离子穿透半导体材料的过程中,会与靶材原子发生交互作用,沿离子运动轨迹生成电子 - 空穴对,这物理过程正是粒子效应的诱发根源。从作用机理来看,半导体器件及集成电路中粒子效应的产生需经历三个核心阶段,各阶段的物理
    的头像 发表于 09-08 09:48 1893次阅读
    <b class='flag-5'>一</b>文详解半导体器件中的<b class='flag-5'>单</b>粒子效应

    AI耳机变身翻译官+会议总结大师?涂鸦AI音频开发方案,让耳机升级到下个level

    在接入AI能力后,耳机这种日常化的产品,能有多大的想象空间?它不仅能帮你轻松听懂全球外语和地方方言,还能将语音转化为文字、翻译成不同语言,甚至自动总结会议要点、生成思维导图,适配办公、学习、跨语言
    的头像 发表于 07-10 18:47 2241次阅读
    AI耳机变身翻译官+会议<b class='flag-5'>总结</b>大师?涂鸦AI音频开发方案,让耳机升级到下<b class='flag-5'>一</b>个level

    没辞职、没报天价班,6个月AI学习的成绩

    结业测评考试,以94分的优异成绩顺利结业。学习记录情况:虽然成绩不代表全部,但这份成绩依然可以给所有初学者带来信心:人工智能学习并没有那么难,6个月,只要用心努力,
    的头像 发表于 07-04 10:37 615次阅读
    没辞职、没报天价班,6个月AI<b class='flag-5'>学习</b>的成绩<b class='flag-5'>单</b>

    相关协议信号总结

    电子发烧友网站提供《相关协议信号总结.xlsx》资料免费下载
    发表于 06-25 15:34 5次下载