当前位置:   article > 正文

双向链表<数据结构 C版>

双向链表<数据结构 C版>

目录

关于链表的分类

 双向链表结构体

初始化

尾插

头插

打印

判断是否为空

尾删

头删

查找

指定位置之后的插入

指定位置的删除

销毁


关于链表的分类

根据链表的三大特性,单向or双向、带头or不带头、循环or不循环,可将链表分为2*2*2,8种链表,前面我们已经实现了单链表,即:不带头单向非循环链表,它的结构简单,不常用于单独存储数据,而是作为其他数据结构的子结构。

实际运用中,只有单链表(不带头单向非循环链表)和双向链表(带头双向循环链表)运用最多,带头双向循环链表,结构最复杂,常常运用于单独存储数据,使用的链表结构也几乎都是双向带头链表。

附上一张bit课件的图:


 双向链表结构体

放一张bi课件的图片很形象:

  1. //重定义一下类型,方便统一修改
  2. typedef int LNDataType;
  3. typedef struct ListNode {
  4. LNDataType data;//数据
  5. struct ListNode* prev;//前一个结点
  6. struct ListNode* next;//后一个结点
  7. }LN;

初始化

双向链表的初始化,应创建一个哨兵结点(也称头结点),它存放的数据是无效数据(假定-1),

所以我们先实现一个创建单节点的函数:

  1. //创建新节点
  2. LN* LNBuyNode(LNDataType x) {
  3. LN* node = (LN*)malloc(sizeof(LN));//开辟空间
  4. if (!node) {//判断为空
  5. perror("malloc fail!");
  6. exit(1);
  7. }
  8. node->data = x;//传入数据
  9. node->next = node->prev = node;
  10. //双向循环链表单节点也应满足循环,不能初始化为NULL;
  11. return node;
  12. }

接着我们就可方便地调用,创建一个哨兵结点:

  1. //初始化
  2. void LNInit(LN** pphead) {
  3. assert(pphead);
  4. *pphead=LNBuynode(-1);
  5. }

尾插

  1. //尾插
  2. void LNPushBack(LN* phead, LNDataType x) {
  3. assert(phead);
  4. LN* node = LNBuyNode(x);//创建新结点
  5. node->next = phead;//先对新申请的结点参数进行操作,防止对原链表造成改变
  6. node->prev = phead->prev;
  7. phead->prev->next = node;//更改尾结点的next参数的指向
  8. phead->prev = node;//更改头结点prev结点的指向
  9. }

运行测试一下:


头插

注意头插的操作是在哨兵位后插入,双向链表为空的情况也是只剩下哨兵位,因为哨兵位并没有存储有效数据。

  1. //头插
  2. void LNPushFront(LN* phead, LNDataType x) {
  3. assert(phead);
  4. LN* node = LNBuyNode(x);//创建新结点
  5. node->next = phead->next;//先对新申请的结点参数进行操作,防止对原链表造成改变
  6. node->prev = phead;
  7. phead->next->prev = node;//更改头结点的下一个结点的prev指向
  8. phead->next = node;//更改头结点的next指向
  9. }

运行测试一下:


打印

  1. //打印
  2. void LNPrint(LN* phead) {
  3. assert(phead);
  4. LN* pcur = phead->next;
  5. //因为头结点内存储的是无效数据,所以我们让它指向下一个结点
  6. while (pcur!=phead) {//与头结点相遇说明我们已经遍历完整个链表了
  7. printf("%d->", pcur->data);
  8. pcur = pcur->next;
  9. }
  10. printf("\n");
  11. }

判断是否为空

双向链表为空的情况是只有一个哨兵结点而不是NULL

  1. //判断是否为空
  2. bool LNEmpty(LN* phead) {
  3. assert(phead);
  4. return phead->next == phead;
  5. }

尾删

  1. //尾删
  2. void LNPopBack(LN* phead) {
  3. assert(phead);
  4. assert(!LNEmpty(phead));//删除操作至少有一个有效数据
  5. //LNEmptys是空返回true,取反保证不为空
  6. LN* del = phead->prev;//保存要删除的结点
  7. phead->prev = del->prev;//对要受影响的结点的参数进行更改
  8. phead->prev->next = phead;
  9. free(del);//释放掉该地址
  10. del = NULL;
  11. }

运行测试一下:


头删

  1. //头删
  2. void LNPopFront(LN* phead) {
  3. assert(phead);
  4. assert(!LNEmpty(phead));
  5. LN* del = phead->next;//保存要删除的结点
  6. phead->next = del->next;//对要受影响的结点的参数进行更改
  7. phead->next->prev = phead;
  8. free(del);//释放掉该地址
  9. del = NULL;
  10. }

运行测试一下:


查找

因为后面涉及到任意位置的操作,所以这里要写一个查找方法:

  1. //查找
  2. LN* LNFind(LN* phead, LNDataType x) {
  3. assert(phead);
  4. assert(!LNEmpty(phead));
  5. LN* pcur = phead->next;
  6. while (pcur!=phead) {
  7. //判断条件为!=phead,遇到哨兵位说明已经遍历完
  8. if (pcur->data == x) {
  9. return pcur;
  10. }
  11. pcur = pcur->next;
  12. }
  13. return NULL;
  14. }

运行测试一下:


指定位置之后的插入

  1. //指定位置之后的插入
  2. void LNInsert(LN* pos, LNDataType x) {
  3. assert(pos);
  4. LN* node = LNBuyNode(x);
  5. node->next = pos->next;//先对要受影响的结点的参数进行更改
  6. node->prev = pos;
  7. pos->next->prev = node;//更改pos结点的后一个结点的prev参数
  8. pos->next = node;//更改pos结点的next参数
  9. }

运行测试一下:


指定位置的删除

  1. //任意位置的删除
  2. void LNErase(LN* pos) {
  3. assert(pos);
  4. pos->next->prev = pos->prev;
  5. pos->prev->next = pos->next;
  6. free(pos);
  7. pos = NULL;
  8. }

运行测试一下:


销毁

  1. //销毁
  2. void LNDestory(LN** phead) {
  3. assert(phead && *phead);
  4. LN* pcur = (*phead)->next;
  5. while (pcur != *phead) {
  6. LN* next = pcur->next;
  7. free(pcur);
  8. pcur = next;
  9. }
  10. free(*phead);
  11. *phead =pcur= NULL;
  12. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/887733
推荐阅读
相关标签
  

闽ICP备14008679号