在上一篇文章中我们介绍了单链表的创建与修改。
这次,我们将在单链表的基础上介绍双链表。
单链表为不带头单向不循环链表。
而双链表则为带头双向循环链表,与单链表完全相反。
双向链表的介绍
对比:
指向:单链表只能指向下一个节点。而双向链表既指向下一个,又指向前一个节点。
循环:单链表的最后一个节点指向NULL。而双向链表最后又指向了头节点。
带头:之前我们说的不太严谨,头节点和带头结点是两种东西,可以将带头结点head理解为“哨兵位”,即只负责监督,不参与链表修改。
单链表的头节点指的是第一个有效节点,而双向链表的带头结点则是单独一块参与进去的节点,他的下一个节点为第一个有效节点。
双向链表实现:
双向链表也是一个结构体,比单链表多了个前一个prev。
初始化:
单链表的初始化即创建一个“哨兵位”,“哨兵位”的值我们给一个-1用来做标记,哨兵位的值只要是链表有效值中不可能取到的极端值即可,只要能证明这是哨兵位即可。
哨兵位的前一位和后一位都指向自己(切记,双向链表不能指向空)
创建新节点:
单纯可以省一点字。
双向链表的销毁:
同样是对传入的参数报警告。由于双向链表中哨兵位始终存在不会被删除,所以我们传递哨兵位时只需要传递一级指针即可。
这里可能会有点乱。
我们先将最后一个节点的next指向空。由于双向链表有前一位的概念,因此哨兵位的前一位就是最后一位。
然后创建一个变量mn用来存储第一个哨兵位后第一个节点的位置。
如果mn为空,说明最后一位也是哨兵位,将哨兵位释放掉返回即可。
如果mn不为空,那么直接释放掉哨兵位,进入循环:
若mn的下一位不为空,那么则将mn指向后一位,通过prve则可以实现释放空间。
最后出循环的时候说明mn的后一位为空。但是后一位为空的只有上方经过修改的最后一位,所以将mn释放掉即可。
输出链表:
哨兵位后一位为第一个有效节点,当当前节点不为哨兵位时输出即可,依次后移。
判断链表是否为空:
很简单,哨兵位的后一位还是哨兵位那么链表就是空的,这个会在后面用到。
尾插:
从这里开始,双向链表和单向链表的区别逐渐显现出来。
创建新节点,新节点前一位指向哨兵位前一位,后一位指向哨兵位。哨兵位前一位(即尾节点)的后一位修改为新节点,哨兵位的前一位修改为新节点即可。(尾节点和哨兵位的修改不能调换顺序)。
尾删:
同样的,如果只有哨兵位则不执行(LTEmpty在这里发挥作用)。
尾节点可以很轻松的找到,和尾插同样的原理即可。
头插:
同上。
头删:
同上。
在固定位置后插入
直接插入即可,只需修改固定位置的后一位朝向和该位置后一位的前一位朝向即可。
删除某一结点:
修改前后两指针朝向,删除该数据。
找节点:
这个与单链表是同样的原理,只能往后挪一位一位找。
展示:
总结:
链表和数组最大的区别在于:
数组可以很方便的找到某一位上的值,但是链表只能不停往后找。
链表可以很方便的移除某个值,但是数组只能一位一位交换位置。