内容概述
本篇介绍了线性结构除顺序表外的另一存储结构=>链表中的单向链表。前文中讲解的顺序表实在连续空间这一严苛条件下进行的数据存储,但大部分情况下很难找出一块连续空间来,因此有了链表的出现。所谓链表,它像链子一样将一块块不连续的空间链接起来以达到在不连续空间中存储数据的效果。
节点是组成链表最重要的东西,它由一块数据域和一块指针域构成
很显然,数据域内存放了该位置所存储的元素,而指针域内从放的便是下一个连续元素的所在的节点,就这样一环扣一环,达到了在不连续空间中存储连续数据的效果。本篇博客会跟大家分享链表与顺序表的区别以及链表的代码实现。
顺序表与链表的区别
存储方式上:
正如前面概述说的,顺序表是在一段连续空间里实现数据的连续存储,类似于一段数组空间,它既有物理上的连续性质,也有逻辑上的连续性质。链表呢?它通过指针链接离散节点的方式实现了非连续空间的连续存储,因此,链表不存在物理上的连续,仅有逻辑上的连续。
插入和删除上:
对于顺序表,如果在尾部进行插入或删除,在忽略扩容情况下,可以直接对data[table->pos]下手,但如果是在中间插入或删除,则还需要对插入位置的后续元素进行统一后移或前移。链表的存在很好地简化了这一步繁琐的操作,它要进行插入或删除时只要对新结点和插入位置的相邻节点进行指针的指向变换操作即可。
空间利用上:
顺序表虽然对连续空间的要求相当严苛,但它实实在在的利用了每一处空间,没有浪费。反观链表,为了方便非连续空间的连续存储,牺牲了多余空间来存储指针。
单向链表代码实现
1.前置定义以及链表操作函数声明
首先进行的是链表中最重要的组成元素---->节点的定义,此处用结构体容纳了一个节点的数据域以及指针域,由于指针指向的是下一个节点,所以为了方便在结构体内定义本结构体数据类型的变量,我们在头和尾分别使用_node以及node来进行节点结构体定义,符合c语言自上而下编译的特点。
再往下定义的是整个链表的头节点,由于链表通过指针串联每一个链表,所以有了头节点便能追溯到每一个节点元素,因此我们可以用头节点来代表整个链表。在头节点结构体中,我们还存储了链表的一些相关数据,如此处的count用来记录链表的节点数。
2.建立链表
此处的关键是将链表的头节点空间建立出来,和顺序表一样,我们依旧把链表放在堆区,所以链表table用指针的形式定义,用于接收malloc所产生的堆区空间。除了创建头节点的空间之外,我们还在链表建立函数里进行了链表相关内部值的初始化操作,如初始化头节点内所存数据以及节点数为0(此处头节点不算入总节点数内)以及令头节点指向空(NULL)。
头插节点(头节点的使用)
上图便是链表中插入节点的变更操作,我们先用新节点的指针指向前驱节点的next,再将前驱节点的next指向新节点。注意,这两步操作的顺序绝对不能调换,如果先将前驱节点的next指向新节点,那后继节点就失踪了,这时你是没有办法找到后继节点的,你的链表也就断了。正确进行了插入操作后,我们再将新节点的数据值代入,然后让总节点数count++就完成头插节点的操作了。
任意位置插入节点(根据索引)
在了解了头部插入节点的操作之后,我们就可以进一步拓展到任意位置的插入,它相较于头插节点,只多了一步寻找前驱节点索引的操作,具体是这样实现的:首先判断一下所给索引的有效性,插入节点就是插空,在这里我们习惯将首个元素前面的位置称为0索引,一直数到最后一个元素的后面位置就是count,所以才有了pos<0||pos>count便是无效这一判断说法,紧接着我们用辅助指针p作为头节点的替身,方便后续“指针自增”操作p = p->next,然后就可以开始寻找待插入位置前驱指针的索引了。这里我们为什么要令index=-1呢?这是因为index表示前驱指针的索引,这就有了index = pos - 1这等式,而我们又知道pos的初始值为0,index的初始值也就顺理成章得等于-1,然后我们再以index<pos-1,作为循环条件逐渐循环p的“指针自增”,逐步找出所插位置的前驱索引。接下来就简单了,我们套用上一段代码的操作,只不过将头节点换成p节点,然后count总数++,一套行云流水的操作下来,任意位置插入也被我们搞定了。(友情提示:别搞错了插入节点的操作顺序)
打印链表
这里主要提一下while§循环的妙用,当p传递到最后一个元素,这个节点指针指向为空,很自然地跳出了循环。还有,这里解释一下为什么第一行内容没有像上一步插入操作中用辅助指针获取头节点一样,二是变成head.nextle呢?这是因为头节点的数据域里的元素不是我们想要的,也没经过我们任何的插入修改,不在我们打印的范畴之内,所以我们要从head+1开始打印。
删除节点(按值删除以及按索引删除)
重点说一下这里删除节点的操作:我们用辅助节点tmp先储存待删除节点的next,然后令待删除节点的next指向tmp的next也就是待删除结点的nextnext,这样就完成了删除节点的操作,和插入一样,这两步操作的顺序也不能调换。
释放链表
总结
链表的操作都是从头节点开始的,可以说头节点是整个链表的核心,当我们把头节点以及插入删除的节点操作步骤理解熟记后,单向链表就不再是难题。还是希望看到这里的朋友可以给主播点个赞关注支持一下,文中遇到任何问题都可以通过评论区提出,感谢大家支持。