一:双向链表的概念
每个节点除开存有数据,还有一个指针指向前一个节点,一个指针指向后一个节点,尾节点和哨兵位互相指向,从而形成一个循环。
二:双向链表的实现
第一点:
本文采用三个文件进行实现
1:List.h(对实现函数以及节点的声明)
2:List.c(增删查改的函数的实现)
3:text.c(对程序的使用检测)
第二点:
本文需要用到的参数和变量
1:plist:实参,头指针
2:phead:形参,头指针
第三点:
所有的函数实现内部第一步永远考虑是否需要对接收到的结构体指针进行断言,这样会更严谨!
1:参数为phead的,一定需要断言,因为不管该链表是否存在节点,都不会为空,因为最少也存在哨兵位的节点。
2:在删除的时候,我们还需要另外assert(phead->next != phead),避免出现只有哨兵位的情况,这样的话,不存在节点让我们去删除。
三:函数的实现讲解
前提:节点的声明
解释:
1:data用来保存节点的值,next用来指向下一个节点,prev用来指向上一个节点。
2:将类型进行一个typedef,方便之后的类型改变带来的修改,只需要修改int即可。
3:将结构体重命名为LTNode,方便之后使用。
第一个函数:创建新节点函数
解释:
1:参数为一个值,将会赋给创建的新节点的data。然后将创建的节点的next置空(会方便后续的操作)。
2:初始化(malloc)顺序表的空间为一个节点的大小,malloc函数的返回值为void*,所以需要强转为LTNode* 的类型,然后该空间才能给新节点使用。
3:malloc开辟失败的检测,以及exit(-1)代表即刻退出。
第二个函数:初始化链表
解释:
初始化一般只会使用一次,用于哨兵位的创建。
第三个函数:打印链表
解释:
1:断言phead,链表不可能为空,最少都存在哨兵位。
2:打印不需要打印哨兵位,哨兵位本身的data没有意义,所以我们从head(哨兵位的下一个)开始遍历打印。
3:<=>符号是为了更加形象的展示循环,以及phead,是打印出来形象表示哨兵位的。
第四个函数:尾插
解释:
1: 断言phead
2:通过哨兵位的prev找到尾节点,并且放在tail里面,这样方便之后的指针指向的更改。
3:尾插一个节点进来,不但要进行尾节点和尾插节点的循环,还要进行尾插节点和哨兵位的循环。
4:当只有哨兵位的时候,tail也就是哨兵位,以上的步骤依旧可以正常的尾插,所以不需要额外增加情况的判断。
第五个函数:尾删
解释:
1:断言 phead->next是因为避免出现只有哨兵位的情况,这样的话,不存在节点让我们去删除。
2:tail和tailprev节点的创建,方便我们后面进行更改指针的指向。
3:记得释放掉tail。
第六个函数:头插
解释:
1:断言
2:头插不仅要进行首节点和头插节点的循环,还要记得进行头插节点和哨兵的循环
3:当只有哨兵位的时候,first也就是哨兵位,以上的步骤依旧可以正常的头插,所以不需要额外增加情况的判断。
第七个函数: 头删
解释:
1: 断言 phead->next是因为避免出现只有哨兵位的情况,这样的话,不存在节点让我们去删除。
2:头删要进行第二个节点和哨兵位循环。
3:释放掉头结点
第八个函数:查找值为x的节点
解释:
1:断言。
2:应该从哨兵的下一个节点开始遍历查找。
第九个函数:在pos节点前插入一个值为x的节点
解释:
1:断言
2:不仅要进行新节点和pos节点的循环,还要进行新节点和pos前一个节点的循环
第十个函数:删除pos节点
解释:
1:将pos的前后节点进行循环连接,再释放pos即可
第十一个函数:计算节点个数
解释:
1:从哨兵位的下一个开始计数才是正确的
最后是text.c函数进行一系列测试的运行结果:
头文件的展示: