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语言
    +关注

    关注

    180

    文章

    7591

    浏览量

    135816
  • 可编程逻辑
    +关注

    关注

    7

    文章

    514

    浏览量

    44054
收藏 人收藏

    评论

    相关推荐

    个暑假如何学习单片机

    个暑假是学习和掌握单片机基础知识的良好时机。以下是个关于如何在暑假期间学习单片机的建议计划
    的头像 发表于 07-03 09:19 463次阅读
    <b class='flag-5'>一</b>个暑假如何<b class='flag-5'>学习</b>单片机

    ESP32-S3的LCD接口可以用DMA链表来触发发送数据吗?

    因为是用来驱动LED显示屏,用原来的I2S那样并行,通过链接自己组织数据列表,还是比较方便的,现在S3的I2S好像已经不能并行发数据了,只能用LCD的接口了,所以想知道LCD接口的DMA能不能用链表来组织数据。
    发表于 06-17 07:25

    华为PCBA检查规范设计总结

    福利来啦! 给大家分享《华为PCBA检查规范设计总结
    的头像 发表于 06-15 16:25 2045次阅读
    华为PCBA检查规范设计<b class='flag-5'>总结</b>

    关于射频学习的问题总结

    请问下,你们遇到过开着HB仿真和关着HB仿真,两者小信号有区别的情况吗?增益会掉两个dB,回拨也略有不同。
    发表于 03-13 10:03 770次阅读
    关于射频<b class='flag-5'>学习</b>的问题<b class='flag-5'>总结</b>

    数组和链表在内存中的区别 数组和链表的优缺点

    数组和链表在内存中的区别 数组和链表的优缺点  数组和链表是常见的数据结构,用于组织和存储数据。它们在内存中的存储方式以及优缺点方面存在些显著的差异。本文将详细探讨这些差异以及它们的
    的头像 发表于 02-21 11:30 858次阅读

    数组和链表有何区别

    数组和链表的区别,这个问题,不仅面试中经常遇到,考研的同学也得掌握才行。
    的头像 发表于 02-19 15:33 433次阅读
    数组和<b class='flag-5'>链表</b>有何区别

    单片机编程实例总结

    单片机编程实例总结
    的头像 发表于 01-16 09:17 960次阅读

    免费学习鸿蒙(HarmonyOS)开发,些地址分享

    体系是怎么样的,用学习路线图总结如下: 上面是(略缩版),高清完整版可看主页或点击《鸿蒙4.0全套文档》 鸿蒙的学习地址我们可以前往华为鸿蒙官方进行
    发表于 01-12 20:48

    IGBT管和模块的对比和分析

    最近很多朋友和我聊到IGBT管和模块的区别,优缺点对比等。本人从事电力电子产品研发十几年有余,对管和模块都有定的应用,开发过的产品包括工频塔式机UPS、500KW光伏逆变器、模块化UPS、充电
    的头像 发表于 01-09 09:04 873次阅读

    选择烧结银的经验总结

    选择烧结银的经验总结
    的头像 发表于 12-17 15:46 1250次阅读
    选择烧结银的经验<b class='flag-5'>总结</b>

    LED灯变暗的原因总结

    LED灯越用越暗,是种非常常见的现象。总结能够让LED灯变暗的原因,不外乎以下三点。
    的头像 发表于 12-15 11:24 2892次阅读

    热控仪表元器件符号总结

    热控仪表元器件符号总结
    的头像 发表于 12-11 12:27 2806次阅读

    数据结构:删除有序链表的重复节点

    给定个有序链表(从小到大有序)的头结点head(该结点有值),删除链表中的重复元素,使链表中的所有元素都只出现
    的头像 发表于 12-05 15:46 803次阅读
    数据结构:删除有序<b class='flag-5'>链表</b>的重复节点

    数据结构:判断链表回文结构

    给定链表,判断该链表是否为回文结构。回文是指该字符串正序逆序完全致。如当输入链表 {1,2,3,2,1} 时,断定是回文结构,输出Tr
    的头像 发表于 12-01 13:26 584次阅读
    数据结构:判断<b class='flag-5'>链表</b>回文结构

    数据结构:链表的排序

    给定链表的头结点head(该结点有值),长度为n的无序链表,对其按升序排序后,返回新链表
    的头像 发表于 11-30 13:56 1438次阅读
    数据结构:<b class='flag-5'>单</b><b class='flag-5'>链表</b>的排序