C语言 数据布局【双向链表】动态模仿实现

[复制链接]
发表于 2025-10-1 10:17:34 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
弁言

        用动态的方式实现双向链表的注意接口,以代码解释为主。
一、什么是双向链表

        单向链表是可以向后依次遍历每个元素,双向链表是既可以依次向后遍历每个元素,也可以依次向前,遍历每个元素。
        有了单链表的实现,双向链表的实现是很类似的。
        注意:这里是实现的是带“哨兵位”的双向链表,即带头双向循环链表。也有不带头的双向循环链表,可以自己实验实验。
二、动态双向链表模仿实现

分3个文件:
  1. List.c  //双链表的主要代码(核心)
  2. List.h  //双链表的函数的声明
  3. test.c  //测试代码
复制代码
1、双链表的布局

界说在List.h文件中
  1. #pragma once
  2. #include<stdio.h>
  3. #include<assert.h>
  4. #include<stdlib.h>
  5. #include<stdbool.h>
  6. //双链表的结构:
  7. typedef int LTDataType;
  8. typedef struct ListNode
  9. {
  10.         LTDataType data;      //存储的数据
  11.         struct ListNode* next;//指向前一个地址的指针
  12.         struct ListNode* prev;//指向下一个地址的指针
  13. }LTNode;
复制代码
2、申请新结点,初始化哨兵位
  1. LTNode* LTBuyNode(LTDataType x)  //申请新结点
  2. {
  3.         LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  4.         if (newnode == NULL)
  5.         {
  6.                 perror("malloc fail!");
  7.                 exit(1);
  8.         }
  9.         newnode->data = x;
  10.           newnode->next = newnode->prev = newnode; //结点自己指向自己
  11.         return newnode;
  12. }
  13. LTNode* LTInit() //初始化哨兵位
  14. {
  15.         LTNode* phead = LTBuyNode(-1); //将哨兵位的值设置成无效的值
  16.         return phead;
  17. }
复制代码
3、尾插,头插

小提示:先修改新结点的指针,再修改原链表的指针
以明白为主,不消影象代码
  1. //尾插
  2. void LTPushBack(LTNode* phead, LTDataType x)
  3. {
  4.         assert(phead);
  5.         LTNode* newnode = LTBuyNode(x);
  6.         newnode->prev = phead->prev;  //新结点的前驱指向最后一个结点
  7.         newnode->next = phead;        //新结点的后驱指向哨兵位
  8.         phead->prev->next = newnode;  //先找到原链表的最后一个结点,将其后驱改成新结点的地址
  9.         phead->prev = newnode;        //最后将哨兵位的前驱指向新结点(即最后一行结点)
  10. }
  11. //头插
  12. void LTPushFront(LTNode* phead, LTDataType x)
  13. {
  14.         assert(phead);
  15.         LTNode* newnode = LTBuyNode(x);
  16.         newnode->next = phead->next;
  17.         newnode->prev = phead;
  18.         phead->next->prev = newnode;
  19.         phead->next = newnode;
  20. }
复制代码
4、打印,判断链表是否为空
  1. void LTPrint(LTNode* phead)
  2. {
  3.         assert(phead);
  4.         LTNode* pcur = phead->next;  //指向第一个结点
  5.         while (pcur != phead)    //回到哨兵位时,结束打印
  6.         {
  7.                 printf("%d->", pcur->data);
  8.                 pcur = pcur->next;    //依次向后找结点,打印结点内容
  9.         }
  10.         printf("\n");
  11. }
  12. //判断有无元素
  13. bool LTEmpty(LTNode* phead)
  14. {
  15.         assert(phead);
  16.         return phead->next == phead;  //当哨兵位自己指向自己说明没有元素,链表为空,否则,链表里面就是有元素的
  17. }
复制代码
5、尾删,头删
  1. //尾删
  2. void LTPopBack(LTNode* phead)
  3. {
  4.         assert(!LTEmpty(phead));
  5.         LTNode* del = phead->prev; //拿到尾结点
  6.         del->prev->next = phead;   //修改尾结点前一个结点的后驱
  7.         phead->prev = del->prev;   //修改头结点的前驱
  8.         free(del);
  9.         del = NULL;
  10. }
  11. //头删
  12. void LTPopFront(LTNode* phead)
  13. {
  14.         assert(!LTEmpty(phead));
  15.         LTNode* del = phead->next;
  16.         del->next->prev = phead;  //修改头结点的下一个结点的前驱
  17.         phead->next = del->next;  //修改头结点的后驱
  18.         free(del);
  19.         del = NULL;
  20. }
复制代码
6、查找
  1. //查找
  2. LTNode* LTFind(LTNode* phead, LTDataType x)
  3. {
  4.         assert(phead);
  5.         LTNode* pcur = phead->next;
  6.         while (pcur != phead)  //依次向后遍历链表
  7.         {
  8.                 if (pcur->data == x)
  9.                 {
  10.                         return pcur;   //找到返回结点的地址
  11.                 }
  12.                 pcur = pcur->next;
  13.         }
  14.         return NULL; //没有就返回NULL
  15. }
复制代码
7、在pos位置前或后插入数据
  1. //在pos位置之后插入数据
  2. void LTInsert(LTNode* pos, LTDataType x)
  3. {
  4.         assert(pos);
  5.         LTNode* newnode = LTBuyNode(x);
  6.         newnode->prev = pos;
  7.         newnode->next = pos->next;
  8.         pos->next->prev = newnode;
  9.         pos->next = newnode;
  10. }
  11. //在pos位置前插入数据
  12. void LTInsertAfter(LTNode* pos, LTDataType x)
  13. {
  14.         assert(pos);
  15.         LTNode* newnode = LTBuyNode(x);
  16.         newnode->next = pos;
  17.         newnode->prev = pos->prev;
  18.         pos->prev->next = newnode;
  19.         pos->prev = newnode;
  20. }
复制代码
8、删除pos位置的结点
  1. //删除pos位置的结点
  2. void LTErase(LTNode* pos)
  3. {
  4.         assert(pos);
  5.         pos->next->prev = pos->prev;
  6.         pos->prev->next = pos->next;
  7.         free(pos);
  8.         pos = NULL;
  9. }
复制代码
9、烧毁双向链表
  1. 销毁双向链表(法一:因为要对头节点做修改,传二级指针)
  2. void LTDesTroy(LTNode** pphead)
  3. {
  4.         assert(pphead);
  5.         LTNode* pcur = (*pphead)->next;
  6.         while (pcur != *(pphead))
  7.         {
  8.                 LTNode* next = pcur->next;
  9.                 free(pcur);
  10.                 pcur = next;
  11.         }
  12.         free(*pphead);
  13.         *pphead = NULL;
  14. }
复制代码
  1. //销毁双向链表(法二:为了保持代码的统一性,这里也传一级指针,但是调用完函数后需要手动将头结点置为NULL;)
  2. void LTDesTroy(LTNode* phead)
  3. {
  4.         assert(phead);
  5.         LTNode* pcur = phead;
  6.         while (pcur != phead)
  7.         {
  8.                 LTNode* next = pcur->next;
  9.                 free(pcur);
  10.                 pcur = next;
  11.         }
  12. }
复制代码
三、以是代码

        说白了:双向链表的实现和修改就是对结点地点的修改,只要想明白了,是很简朴的
1、List.h中的代码:
  1. #pragma once
  2. #include<stdio.h>
  3. #include<assert.h>
  4. #include<stdlib.h>
  5. #include<stdbool.h>
  6. //双链表的结构:
  7. typedef int LTDataType;
  8. typedef struct ListNode
  9. {
  10.         LTDataType data;      //存储的数据
  11.         struct ListNode* next;//指向前一个地址的指针
  12.         struct ListNode* prev;//指向下一个地址的指针
  13. }LTNode;void LTPrint(LTNode* phead);LTNode* LTInit();//尾插void LTPushBack(LTNode* phead, LTDataType x);//头插void LTPushFront(LTNode* phead, LTDataType x);//判断有无元素bool LTEmpty(LTNode* phead);//尾删void LTPopBack(LTNode* phead);//头删void LTPopFront(LTNode* phead);//查找LTNode* LTFind(LTNode* phead, LTDataType x);//在pos位置之后插入数据void LTInsert(LTNode* pos, LTDataType x);//在pos位置前插入数据void LTInsertAfter(LTNode* pos, LTDataType x);//删除pos位置的结点void LTErase(LTNode* pos);//烧毁双向链表(法一:由于要对头节点做修改,传二级指针)//void LTDesTroy(LTNode** pphead);//烧毁双向链表(法二:为了保持代码的同一性,这里也传一级指针,但是调用完函数后必要手动将头结点置为NULL;)void LTDesTroy(LTNode* phead);
复制代码
2、List.c中的代码
  1. #define _CRT_SECURE_NO_WARNINGS#include"List.h"
  2. LTNode* LTBuyNode(LTDataType x)  //申请新结点
  3. {
  4.         LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  5.         if (newnode == NULL)
  6.         {
  7.                 perror("malloc fail!");
  8.                 exit(1);
  9.         }
  10.         newnode->data = x;
  11.           newnode->next = newnode->prev = newnode; //结点自己指向自己
  12.         return newnode;
  13. }
  14. LTNode* LTInit() //初始化哨兵位
  15. {
  16.         LTNode* phead = LTBuyNode(-1); //将哨兵位的值设置成无效的值
  17.         return phead;
  18. }void LTPrint(LTNode* phead){        assert(phead);        LTNode* pcur = phead->next;  //指向第一个结点        while (pcur != phead)    //回到哨兵位时,竣事打印        {                printf("%d->", pcur->data);                pcur = pcur->next;    //依次向后找结点,打印结点内容        }        printf("\n");}
  19. //尾插
  20. void LTPushBack(LTNode* phead, LTDataType x)
  21. {
  22.         assert(phead);
  23.         LTNode* newnode = LTBuyNode(x);
  24.         newnode->prev = phead->prev;  //新结点的前驱指向最后一个结点
  25.         newnode->next = phead;        //新结点的后驱指向哨兵位
  26.         phead->prev->next = newnode;  //先找到原链表的最后一个结点,将其后驱改成新结点的地址
  27.         phead->prev = newnode;        //最后将哨兵位的前驱指向新结点(即最后一行结点)
  28. }
  29. //头插
  30. void LTPushFront(LTNode* phead, LTDataType x)
  31. {
  32.         assert(phead);
  33.         LTNode* newnode = LTBuyNode(x);
  34.         newnode->next = phead->next;
  35.         newnode->prev = phead;
  36.         phead->next->prev = newnode;
  37.         phead->next = newnode;
  38. }//判断有无元素bool LTEmpty(LTNode* phead){        assert(phead);        return phead->next == phead;  //当哨兵位自己指向自己阐明没有元素,链表为空,否则,链表内里就是有元素的}
  39. //尾删
  40. void LTPopBack(LTNode* phead)
  41. {
  42.         assert(!LTEmpty(phead));
  43.         LTNode* del = phead->prev; //拿到尾结点
  44.         del->prev->next = phead;   //修改尾结点前一个结点的后驱
  45.         phead->prev = del->prev;   //修改头结点的前驱
  46.         free(del);
  47.         del = NULL;
  48. }
  49. //头删
  50. void LTPopFront(LTNode* phead)
  51. {
  52.         assert(!LTEmpty(phead));
  53.         LTNode* del = phead->next;
  54.         del->next->prev = phead;  //修改头结点的下一个结点的前驱
  55.         phead->next = del->next;  //修改头结点的后驱
  56.         free(del);
  57.         del = NULL;
  58. }
  59. //查找
  60. LTNode* LTFind(LTNode* phead, LTDataType x)
  61. {
  62.         assert(phead);
  63.         LTNode* pcur = phead->next;
  64.         while (pcur != phead)  //依次向后遍历链表
  65.         {
  66.                 if (pcur->data == x)
  67.                 {
  68.                         return pcur;   //找到返回结点的地址
  69.                 }
  70.                 pcur = pcur->next;
  71.         }
  72.         return NULL; //没有就返回NULL
  73. }
  74. //在pos位置之后插入数据
  75. void LTInsert(LTNode* pos, LTDataType x)
  76. {
  77.         assert(pos);
  78.         LTNode* newnode = LTBuyNode(x);
  79.         newnode->prev = pos;
  80.         newnode->next = pos->next;
  81.         pos->next->prev = newnode;
  82.         pos->next = newnode;
  83. }
  84. //在pos位置前插入数据
  85. void LTInsertAfter(LTNode* pos, LTDataType x)
  86. {
  87.         assert(pos);
  88.         LTNode* newnode = LTBuyNode(x);
  89.         newnode->next = pos;
  90.         newnode->prev = pos->prev;
  91.         pos->prev->next = newnode;
  92.         pos->prev = newnode;
  93. }
  94. //删除pos位置的结点
  95. void LTErase(LTNode* pos)
  96. {
  97.         assert(pos);
  98.         pos->next->prev = pos->prev;
  99.         pos->prev->next = pos->next;
  100.         free(pos);
  101.         pos = NULL;
  102. }//烧毁双向链表(法一:由于要对头节点做修改,传二级指针)//void LTDesTroy(LTNode** pphead)//{//        assert(pphead);//        LTNode* pcur = (*pphead)->next;//        while (pcur != *(pphead))//        {//                LTNode* next = pcur->next;//                free(pcur);//                pcur = next;//        }//        free(*pphead);//        *pphead = NULL;//}void LTDesTroy(LTNode* phead){        assert(phead);        LTNode* pcur = phead;        while (pcur != phead)        {                LTNode* next = pcur->next;                free(pcur);                pcur = next;        }}
复制代码
3、测试时的代码(test.c中):
  1. #include"List.h"
  2. int main()
  3. {
  4.         LTNode* plist = LTInit();
  5.         LTPushBack(plist, 1);
  6.         LTPushBack(plist, 2);
  7.         LTPushBack(plist, 3);
  8.         LTPushBack(plist, 4);
  9.        
  10.         /*LTPushFront(plist, 3);
  11.         if (LTFind(plist, 2))
  12.         {
  13.                 printf("找到了\n");
  14.         }
  15.         else
  16.         {
  17.                 printf("无\n");
  18.         }*/
  19.         //LTInsert(LTFind(plist, 3), 4);
  20.         //LTInsertAfter(LTFind(plist, 3), 4);
  21.         //LTErase(LTFind(plist, 2));
  22.         LTPrint(plist);
  23.         LTDesTroy(plist);
  24.         plist = NULL;
  25.         return 0;
  26. }
复制代码
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表