深圳幻海软件技术有限公司 欢迎您!

双向链表(数据结构)(C语言)

2023-05-18

目录概念带头双向循环链表的实现前情提示双向链表的结构体定义双向链表的初始化关于无头单向非循环链表无需初始化函数,顺序表、带头双向循环链表需要的思考双向链表在pos位置之前插入x双向链表的打印双链表删除pos位置的结点双向链表的尾插关于单链表的尾插需要用到二级指针,双向链表不需要用到二级指针的思考双向

目录

概念

带头双向循环链表的实现

前情提示

双向链表的结构体定义

双向链表的初始化

关于无头单向非循环链表无需初始化函数,顺序表、带头双向循环链表需要的思考

双向链表在pos位置之前插入x

双向链表的打印

双链表删除pos位置的结点

双向链表的尾插

关于单链表的尾插需要用到二级指针,双向链表不需要用到二级指针的思考

双向链表的判空

双向链表的尾删

双向链表的头插 

双向链表的头删

双向链表查找值为x的结点

双向链表的销毁 

双向链表的修改

双向链表删除值为x的结点

 双向链表计算结点总数(不计phead)

双向链表获取第i位置的结点

双向链表的清空

总代码(想直接看结果的可以看这里)


概念

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。我们一般构造双向循环链表。循环链表是一种链式存储结构,它的最后一个结点指向头结点,形成一个环。因此,从循环链表中的任何一个结点出发都能找到任何其它结点。


带头双向循环链表的实现

前情提示

List.h  用于  引用的头文件、双向链表的定义、函数的声明。

List.c  用于  函数的定义。

Test.c 用于  双向链表功能的测试。

双向链表的结构体定义

在List.h下

  1. #pragma once//使同一个文件不会被包含(include)多次,不必担心宏名冲突
  2. //先将可能使用到的头文件写上
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. #include<stdbool.h>
  7. typedef int LTDataType;//假设结点的数据域类型为 int
  8. //给变量定义一个易于记忆且意义明确的新名字,并且便于以后存储其它类型时方便改动
  9. //(比如我晚点想存double类型的数据,我就直接将 int 改为 double )
  10. // 带哨兵位双向循环链表的结构体定义
  11. typedef struct ListNode
  12. {
  13. struct ListNode* prev;//前驱指针域:存放上一个结点的地址
  14. struct ListNode* next;//后继指针域:存放下一个结点的地址
  15. LTDataType data;//数据域
  16. }LTNode;
  17. //struct 关键字和 ListNode 一起构成了这个结构类型
  18. //typedef 为这个结构起了一个别名,叫 LTNode,即:typedef struct ListNode LTNode
  19. //现在就可以像 int 和 double 那样直接使用 LTNode 来定义变量

双向链表的初始化

在List.h下

  1. // 双向链表的初始化
  2. // 如果是单链表直接给个空指针就行,不需要单独写一个函数进行初始化
  3. // 即:LTNode* plist = NULL;
  4. // 那为什么顺序表、带头双向循环链表有呢?
  5. // 因为顺序表、带头双向循环链表的结构并不简单,
  6. // 如: 顺序表顺序表为空size要为0,还要看capacity是否要开空间,
  7. // 若不开空间capacity=0,指针要给空,若开空间,还要检查malloc是否成功
  8. // 带头双向循环链表要开个结点,检查malloc是否成功,然后让结点自己指向自己
  9. // 顺序表和双向循环链表的初始化有点复杂,最好构建一个函数
  10. LTNode* LTInit();

在List.c下

  1. #include"List.h"//别忘了
  2. //动态申请一个结点
  3. LTNode* BuyListNode(LTDataType x)
  4. {
  5. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
  6. if (node == NULL)//如果malloc失败
  7. {
  8. perror("malloc fail");
  9. return NULL;
  10. }
  11. //如果malloc成功
  12. //初始化一下,防止野指针,如果看到返回的是空指针,那逻辑可能有些错误
  13. node->next = NULL;
  14. node->prev = NULL;
  15. node->data = x;
  16. return node;
  17. }
  18. // 双向链表的初始化
  19. LTNode* LTInit()
  20. {
  21. LTNode* phead = BuyListNode(-1);//创建哨兵位
  22. //自己指向自己
  23. phead->next = phead;
  24. phead->prev = phead;
  25. return phead;
  26. }

关于无头单向非循环链表无需初始化函数,顺序表、带头双向循环链表需要的思考

无头单向非循环链表结构太简单了,初始化只需直接赋空指针,无需单独写一个函数进行初始化。

即:LTNode* plist = NULL;

那为什么顺序表、带头双向循环链表有单独写一个函数进行初始化呢?
因为顺序表、带头双向循环链表的结构并不简单。

如:

顺序表顺序表为空size要为0,还要看capacity是否要开空间,若不开空间capacity=0,指针要给空,若开空间,还要检查malloc是否成功。

带头双向循环链表要开个结点,检查malloc是否成功,然后让结点自己指向自己。

顺序表和双向循环链表的初始化有点复杂,最好构建一个函数。

双向链表在pos位置之前插入x

在List.h下

  1. // 双向链表在pos位置之前进行插入
  2. void LTInsert(LTNode* pos, LTDataType x);

在List.c下

  1. // 双向链表在pos位置之前进行插入
  2. void LTInsert(LTNode* pos, LTDataType x)
  3. {
  4. assert(pos);//pos肯定不为空
  5. LTNode* prev = pos->prev;
  6. LTNode* newnode = BuyListNode(x);//创建一个需要插入的结点
  7. prev->next = newnode;
  8. newnode->prev = prev;
  9. newnode->next = pos;
  10. pos->prev = newnode;
  11. }

双向链表的打印

在List.h下

  1. // 双向链表打印
  2. void LTPrint(LTNode* phead);

在List.c下

  1. // 双向链表打印
  2. void LTPrint(LTNode* phead)
  3. {
  4. assert(phead);//有哨兵位
  5. printf("<=>phead<=>");
  6. LTNode* cur = phead->next;//cur指向第一个要打印的结点
  7. while (cur != phead)//cur等于头结点时打印就结束了
  8. {
  9. printf("%d<=>", cur->data);
  10. cur = cur->next;
  11. }
  12. printf("\n");
  13. }

在Test.c下

  1. #include"List.h"//别忘了
  2. //测试函数
  3. void TestList1()
  4. {
  5. LTNode* plist = LTInit();
  6. LTInsert(plist, 1);
  7. LTInsert(plist, 2);
  8. LTInsert(plist, 3);
  9. LTInsert(plist, 4);
  10. LTPrint(plist);
  11. }
  12. int main()
  13. {
  14. TestList1();
  15. return 0;
  16. }

双链表删除pos位置的结点

在List.h下

  1. // 双向链表删除pos位置的结点
  2. void LTErase(LTNode* pos);

在List.c下

  1. // 双向链表删除pos位置的结点
  2. void LTErase(LTNode* pos)
  3. {
  4. assert(pos);//pos肯定不为空
  5. LTNode* posprev = pos->prev;
  6. LTNode* posnext = pos->next;
  7. posprev->next = posnext;
  8. posnext->prev = posprev;
  9. free(pos);
  10. pos = NULL;
  11. //这个置空其实已经没有意义了,形参的改变不会改变实参
  12. }

在Test.c下

  1. //测试函数
  2. void TestList1()
  3. {
  4. LTNode* plist = LTInit();
  5. LTInsert(plist, 1);
  6. LTInsert(plist, 2);
  7. LTInsert(plist, 3);
  8. LTInsert(plist, 4);
  9. LTPrint(plist);
  10. LTErase(plist->next);
  11. LTPrint(plist);
  12. }
  13. int main()
  14. {
  15. TestList1();
  16. return 0;
  17. }

双向链表的尾插

在List.h下

  1. //双向链表优于单链表的点——不需要找尾、二级指针
  2. //(我们改的不是结构体的指针,改的是结构体的变量)
  3. // 双向链表的尾插
  4. void LTPushBack(LTNode* phead, LTDataType x);

在List.c下

法一:(便于新手更好地理解双向链表的尾插) 

  1. // 双向链表的尾插
  2. void LTPushBack(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);//有哨兵位
  5. //法一:(便于新手更好地理解双向链表的尾插)
  6. //一步就可完成链表为空/不为空的尾插——因为有哨兵位
  7. LTNode* newnode = BuyListNode(x);//创建一个要插入的结点
  8. LTNode* tail = phead->prev;
  9. tail->next = newnode;
  10. newnode->prev = tail;
  11. newnode->next = phead;
  12. phead->prev = newnode;
  13. }

法二:函数复用(简单方便)

  1. // 双向链表的尾插
  2. void LTPushBack(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);//有哨兵位
  5. //法二:函数复用(简单方便)
  6. LTInsert(phead, x);
  7. }

关于单链表的尾插需要用到二级指针,双向链表不需要用到二级指针的思考

单链表改变的是结构体的指针,双向链表改变的是结构体的变量

二级指针和一级指针的区别在于指针所指向变量的层级不同,一级指针指向的是结构体的变量,而二级指针指向的是结构体指针的地址
单链表中,在进行链表结点的删除或插入操作时,需要更新结点之间的指针指向。若使用一级指针,则操作会直接改变指向结点的指针,很难实现目标。因此需要传递二级指针,让函数能够修改指向结点指针的地址,也就是修改之前结点指针变量存放的地址
而双向链表中,每个结点除了保存指向下一结点的指针外,还有保存指向上一结点的指针,结点之间的双向指针关系使得结点的插入和删除操作更加方便。双向链表不需要传递二级指针,因为在结点的删除和插入操作中,只需要先修改当前结点前后结点的指针,无需直接改变前后结点指针变量存放的地址
综上所述,单链表只有指向下一结点的指针,通过传递二级指针来修改结点之间的指针关系,使得操作更加灵活;而双向链表的结点之间有双向指针关系,无需直接改变前后结点指针变量存放的地址,因此只需要传递一级指针即可

单链表(对比):

在Test.c下

  1. //测试函数
  2. void TestList2()
  3. {
  4. LTNode* plist = LTInit();
  5. LTPushBack(plist, 5);
  6. LTPushBack(plist, 6);
  7. LTPushBack(plist, 7);
  8. LTPushBack(plist, 8);
  9. LTPrint(plist);
  10. }
  11. int main()
  12. {
  13. TestList2();
  14. return 0;
  15. }

双向链表的判空

在尾删/头删之前,我们要先判断链表是否为空。

在List.h下

  1. // 双向链表的判空
  2. bool LTEmpty(LTNode* phead);

在List.c下

  1. // 双向链表的判空
  2. bool LTEmpty(LTNode* phead)
  3. {
  4. assert(phead);
  5. return phead->next == phead;
  6. //两者相等就是空链表(返回真),两者不相等就不是空链表(返回假)
  7. }

双向链表的尾删

在List.h下

  1. // 双向链表的尾删
  2. void LTPopBack(LTNode* phead);

在List.c下

法一:(便于新手更好地理解双向链表的尾删)

  1. // 双向链表的尾删
  2. void LTPopBack(LTNode* phead)
  3. {
  4. assert(phead);//有哨兵位
  5. assert(!LTEmpty(phead));//判空
  6. //法一:(便于新手更好地理解双向链表的尾删)
  7. LTNode* tail = phead->prev;
  8. LTNode* tailPrev = tail->prev;
  9. tailPrev->next = phead;
  10. phead->prev = tailPrev;
  11. free(tail);
  12. tail = NULL;
  13. }

法二:函数复用(简单方便)

  1. // 双向链表的尾删
  2. void LTPopBack(LTNode* phead)
  3. {
  4. assert(phead);//有哨兵位
  5. assert(!LTEmpty(phead));//判空
  6. //法二:函数复用
  7. LTErase(phead->prev);
  8. }

在Test.c下

  1. //测试函数
  2. void TestList2()
  3. {
  4. LTNode* plist = LTInit();
  5. LTPushBack(plist, 5);
  6. LTPushBack(plist, 6);
  7. LTPushBack(plist, 7);
  8. LTPushBack(plist, 8);
  9. LTPrint(plist);
  10. LTPopBack(plist);
  11. LTPopBack(plist);
  12. LTPrint(plist);
  13. }
  14. int main()
  15. {
  16. TestList2();
  17. return 0;
  18. }

双向链表的头插 

在List.h下

  1. // 双向链表头插
  2. void LTPushFront(LTNode* phead, LTDataType x);

在List.c下

法一:只用phead和newnode两个指针(便于新手更好地理解双向链表的头插)

  1. // 双向链表头插
  2. void LTPushFront(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);//有哨兵位
  5. LTNode* newnode = BuyListNode(x);//创建一个要插入的结点
  6. //法一:只用phead和newnode两个指针(便于新手更好地理解双向链表的头插)
  7. //顺序很重要!!!
  8. newnode->next = phead->next;
  9. phead->next->prev = newnode;
  10. phead->next = newnode;
  11. newnode->prev = phead;
  12. }

法二:多用了first记录第一个结点的位置(便于新手更好地理解双向链表的头插)

  1. // 双向链表头插
  2. void LTPushFront(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);//哨兵位
  5. LTNode* newnode = BuyListNode(x);//创建一个要插入的结点
  6. //法二:多用了first先记住第一个结点(便于新手更好地理解双向链表的头插)
  7. LTNode* first = phead->next;
  8. phead->next = newnode;
  9. newnode->prev = phead;
  10. newnode->next = first;
  11. first->prev = newnode;
  12. }

法三:函数复用(简单方便)

  1. // 双向链表头插
  2. void LTPushFront(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);//有哨兵位
  5. //法三:函数复用(简单方便)
  6. LTInsert(phead->next, x);
  7. }

在Test.c下

  1. //测试函数
  2. void TestList3()
  3. {
  4. LTNode* plist = LTInit();
  5. LTPushFront(plist, 1);
  6. LTPushFront(plist, 2);
  7. LTPushFront(plist, 3);
  8. LTPushFront(plist, 4);
  9. LTPrint(plist);
  10. }
  11. int main()
  12. {
  13. TestList3();
  14. return 0;
  15. }

双向链表的头删

在List.h下

  1. // 双向链表头删
  2. void LTPopFront(LTNode* phead);

在List.c下

法一:(便于新手更好地理解双向链表的头删)

  1. // 双向链表头删
  2. void LTPopFront(LTNode* phead)
  3. {
  4. assert(phead);//有哨兵位
  5. assert(!LTEmpty(phead));//判空
  6. //法一:(便于新手更好地理解双向链表的头删)
  7. LTNode* head = phead->next;
  8. LTNode* headnext = head->next;
  9. phead->next = headnext;
  10. headnext->prev = phead;
  11. free(head);
  12. head = NULL;
  13. }

法二:函数复用(简单方便) 

  1. // 双向链表头删
  2. void LTPopFront(LTNode* phead)
  3. {
  4. assert(phead);//有哨兵位
  5. assert(!LTEmpty(phead));//判空
  6. //法二:函数复用(简单方便)
  7. LTErase(phead->next);
  8. }

在Test.c下

  1. //测试函数
  2. void TestList3()
  3. {
  4. LTNode* plist = LTInit();
  5. LTPushFront(plist, 1);
  6. LTPushFront(plist, 2);
  7. LTPushFront(plist, 3);
  8. LTPushFront(plist, 4);
  9. LTPrint(plist);
  10. LTPopFront(plist);
  11. LTPopFront(plist);
  12. LTPrint(plist);
  13. }
  14. int main()
  15. {
  16. TestList3();
  17. return 0;
  18. }

双向链表查找值为x的结点

在List.h下

  1. // 双向链表查找值为x的结点
  2. LTNode* LTFind(LTNode* phead, LTDataType x);

在List.c下

  1. // 双向链表查找值为x的结点
  2. LTNode* LTFind(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);//有哨兵位
  5. LTNode* cur = phead->next;
  6. while (cur != phead)//让cur去遍历
  7. {
  8. if (cur->data == x)//如果找到
  9. {
  10. return cur;
  11. }
  12. cur = cur->next;
  13. }
  14. return NULL;//如果没找到
  15. }

在Test.c下

  1. //测试函数
  2. TestList4()
  3. {
  4. LTNode* plist = LTInit();
  5. LTInsert(plist, 1);
  6. LTInsert(plist, 2);
  7. LTInsert(plist, 3);
  8. LTInsert(plist, 4);
  9. LTPrint(plist);
  10. LTNode* pos = LTFind(plist, 3);
  11. if (pos)
  12. {
  13. LTErase(pos);
  14. pos = NULL;
  15. }
  16. LTPrint(plist);
  17. }
  18. int main()
  19. {
  20. TestList4();
  21. return 0;
  22. }

双向链表的销毁 

在List.h下

  1. // 双向链表的销毁
  2. void LTDestory(LTNode* phead);

在List.c下

   

  

 

  1. // 双向链表的销毁
  2. void LTDestory(LTNode* phead)
  3. {
  4. assert(phead);
  5. LTNode* cur = phead->next;//让cur遍历
  6. while (cur != phead)
  7. {
  8. LTNode* curnext = cur->next;
  9. free(cur);
  10. cur = curnext;
  11. }
  12. free(phead);
  13. phead = NULL;
  14. //这个置空其实已经没有意义了,形参的改变不会改变实参
  15. //我们为了保持接口的一致性,不传二级指针,选择在测试的时候置空
  16. }

在Test.c下

  1. //测试函数
  2. TestList4()
  3. {
  4. LTNode* plist = LTInit();
  5. LTInsert(plist, 1);
  6. LTInsert(plist, 2);
  7. LTInsert(plist, 3);
  8. LTInsert(plist, 4);
  9. LTPrint(plist);
  10. LTNode* pos = LTFind(plist, 3);
  11. if (pos)
  12. {
  13. LTErase(pos);
  14. pos = NULL;
  15. }
  16. LTPrint(plist);
  17. LTDestory(plist);
  18. plist = NULL;//在这里置空
  19. }

双向链表的修改

在List.h下

  1. // 双向链表的修改,修改pos位置的值为x
  2. void LTModify(LTNode* pos, LTDataType x);

在List.c下

  1. // 双向链表的修改,修改pos位置的值为x
  2. void LTModify(LTNode* pos, LTDataType x)
  3. {
  4. assert(pos);
  5. pos->data = x;
  6. }

在Test.c下

  1. //测试函数
  2. TestList5()
  3. {
  4. LTNode* plist = LTInit();
  5. LTInsert(plist, 1);
  6. LTInsert(plist, 2);
  7. LTInsert(plist, 3);
  8. LTInsert(plist, 4);
  9. LTPrint(plist);
  10. LTModify(plist->next,5);
  11. LTPrint(plist);
  12. }
  13. int main()
  14. {
  15. TestList5();
  16. return 0;
  17. }

双向链表删除值为x的结点

在List.h下

  1. // 双向链表删除值为x的结点
  2. void LTRemove(LTNode* phead,LTDataType x);

在List.c下

  1. // 双向链表删除值为x的结点
  2. void LTRemove(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);//有哨兵位
  5. LTNode* pos = phead->next;
  6. while (pos != phead)
  7. {
  8. pos = LTFind(phead, x);
  9. if (pos == NULL)//如果遍历完
  10. {
  11. return NULL;
  12. }
  13. LTErase(pos);
  14. pos = pos->next;
  15. }
  16. }

在Test.c下

  1. TestList6()
  2. {
  3. LTNode* plist = LTInit();
  4. LTInsert(plist, 1);
  5. LTInsert(plist, 3);
  6. LTInsert(plist, 3);
  7. LTInsert(plist, 4);
  8. LTInsert(plist, 3);
  9. LTPrint(plist);
  10. LTRemove(plist, 3);
  11. LTPrint(plist);
  12. }
  13. int main()
  14. {
  15. TestList6();
  16. return 0;
  17. }

 双向链表计算结点总数(不计phead)

在List.h下

  1. // 双向链表计算结点总数(不计phead)
  2. int LTTotal(LTNode* phead);

在List.c下

  1. // 双向链表计算结点总数(不计phead)
  2. int LTTotal(LTNode* phead)
  3. {
  4. assert(phead);//有哨兵位
  5. int count = 0;//count来计数
  6. LTNode* cur = phead->next;//让cur去遍历
  7. while (cur != phead)
  8. {
  9. count++;
  10. cur = cur->next;
  11. }
  12. return count;
  13. }

在Test.c下

  1. TestList6()
  2. {
  3. LTNode* plist = LTInit();
  4. LTInsert(plist, 1);
  5. LTInsert(plist, 3);
  6. LTInsert(plist, 3);
  7. LTInsert(plist, 4);
  8. LTInsert(plist, 3);
  9. LTPrint(plist);
  10. LTRemove(plist, 3);
  11. LTPrint(plist);
  12. printf("%d\n", LTTotal(plist));
  13. }
  14. int main()
  15. {
  16. TestList6();
  17. return 0;
  18. }

双向链表获取第i位置的结点

在List.h下

  1. // 双向链表获取第i位置的结点
  2. LTNode* LTGet(LTNode* phead, int i);

在List.c下

  1. // 双向链表获取第i位置的结点
  2. LTNode* LTGet(LTNode* phead, int i)
  3. {
  4. assert(phead);//有哨兵位
  5. int length = LTTotal(phead);
  6. LTNode* cur = phead->next;
  7. if (i == 0)
  8. {
  9. return phead;
  10. }
  11. else if (i<0 || i>length)//位置不合法
  12. {
  13. return NULL;
  14. }
  15. else if (i <= (length / 2))//从表头开始遍历
  16. {
  17. cur = phead->next;
  18. for (int count = 1; count < i; count++)
  19. {
  20. cur = cur->next;
  21. }
  22. }
  23. else//从表尾开始遍历
  24. {
  25. cur = phead->prev;
  26. for (int count = 1; count <= length - i; count++)
  27. {
  28. cur = cur->prev;
  29. }
  30. }
  31. return cur;
  32. }

在Test.c下

  1. //测试函数
  2. TestList7()
  3. {
  4. LTNode* plist = LTInit();
  5. LTInsert(plist, 5);
  6. LTInsert(plist, 6);
  7. LTInsert(plist, 7);
  8. LTInsert(plist, 8);
  9. LTInsert(plist, 9);
  10. LTPrint(plist);
  11. LTNode* pos = LTGet(plist,3);
  12. if (pos)
  13. {
  14. LTErase(pos);
  15. pos = NULL;
  16. }
  17. LTPrint(plist);
  18. }
  19. int main()
  20. {
  21. TestList7();
  22. return 0;
  23. }

双向链表的清空

在List.h下

  1. // 双向链表的清空
  2. void LTClean(LTNode* phead);

在List.c下

  1. // 双向链表的清空
  2. void LTClear(LTNode* phead)
  3. {
  4. assert(phead);//有哨兵位
  5. while (!LTEmpty(phead))//如果不为空就一直头删
  6. {
  7. LTPopFront(phead);
  8. }
  9. }

在Test.c下

  1. //测试函数
  2. TestList8()
  3. {
  4. LTNode* plist = LTInit();
  5. LTInsert(plist, 5);
  6. LTInsert(plist, 6);
  7. LTInsert(plist, 7);
  8. LTInsert(plist, 8);
  9. LTInsert(plist, 9);
  10. LTPrint(plist);
  11. LTClear(plist);
  12. LTPrint(plist);
  13. }
  14. int main()
  15. {
  16. TestList8();
  17. return 0;
  18. }

总代码(想直接看结果的可以看这里)

在List.h下

  1. #pragma once//使同一个文件不会被包含(include)多次,不必担心宏名冲突
  2. //先将可能使用到的头文件写上
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. #include<stdbool.h>
  7. typedef int LTDataType;//假设结点的数据域类型为 int
  8. //给变量定义一个易于记忆且意义明确的新名字,并且便于以后存储其它类型时方便改动
  9. //(比如我晚点想存double类型的数据,我就直接将 int 改为 double )
  10. // 带哨兵位双向循环链表的结构体定义
  11. typedef struct ListNode
  12. {
  13. struct ListNode* prev;//前驱指针域:存放上一个结点的地址
  14. struct ListNode* next;//后继指针域:存放下一个结点的地址
  15. LTDataType data;//数据域
  16. }LTNode;
  17. //struct 关键字和 ListNode 一起构成了这个结构类型
  18. //typedef 为这个结构起了一个别名,叫 LTNode,即:typedef struct ListNode LTNode
  19. //现在就可以像 int 和 double 那样直接使用 LTNode 来定义变量
  20. // 双向链表的初始化
  21. // 如果是单链表直接给个空指针就行,不需要单独写一个函数进行初始化
  22. // 即:LTNode* plist = NULL;
  23. // 那为什么顺序表、带头双向循环链表有呢?
  24. // 因为顺序表、带头双向循环链表的结构并不简单,
  25. // 如:顺序表顺序表为空size要为0,还要看capacity是否要开空间,
  26. //若不开空间capacity=0,指针要给空,若开空间,还要检查malloc是否成功
  27. // 带头双向循环链表要开个结点,检查malloc是否成功,然后让结点自己指向自己
  28. // 顺序表和双向循环链表的初始化有点复杂,最好构建一个函数
  29. LTNode* LTInit();
  30. // 双向链表在pos位置之前进行插入x
  31. void LTInsert(LTNode* pos, LTDataType x);
  32. // 双向链表的打印
  33. void LTPrint(LTNode* phead);
  34. // 双向链表删除pos位置的结点
  35. void LTErase(LTNode* pos);
  36. //双向链表优于单链表的点——不需要找尾、二级指针
  37. // (我们改的不是结构体的指针,改的是结构体的变量)
  38. // 双向链表的尾插
  39. void LTPushBack(LTNode* phead, LTDataType x);
  40. // 双向链表的判空
  41. bool LTEmpty(LTNode* phead);
  42. // 双向链表的尾删
  43. void LTPopBack(LTNode* phead);
  44. // 双向链表头插
  45. void LTPushFront(LTNode* phead, LTDataType x);
  46. // 双向链表头删
  47. void LTPopFront(LTNode* phead);
  48. // 双向链表查找值为x的结点
  49. LTNode* LTFind(LTNode* phead, LTDataType x);
  50. // 双向链表的销毁
  51. void LTDestory(LTNode* phead);
  52. // 双向链表的修改,修改pos位置的值为x
  53. void LTModify(LTNode* pos, LTDataType x);
  54. // 双向链表删除值为x的结点
  55. void LTRemove(LTNode* phead, LTDataType x);
  56. // 双向链表计算结点总数(不计phead)
  57. int LTTotal(LTNode* phead);
  58. // 双向链表获取第i位置的结点
  59. LTNode* LTGet(LTNode* phead, int i);
  60. // 双向链表的清空
  61. void LTClear(LTNode* phead);

在List.c下

  1. #include"List.h"
  2. //动态申请一个结点
  3. LTNode* BuyListNode(LTDataType x)
  4. {
  5. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
  6. if (node == NULL)//如果malloc失败
  7. {
  8. perror("malloc fail");
  9. return NULL;
  10. }
  11. //如果malloc成功
  12. //初始化一下,防止野指针,如果看到返回的是空指针,那逻辑可能有些错误
  13. node->next = NULL;
  14. node->prev = NULL;
  15. node->data = x;
  16. return node;
  17. }
  18. // 双向链表的初始化
  19. LTNode* LTInit()
  20. {
  21. LTNode* phead = BuyListNode(-1);
  22. //自己指向自己
  23. phead->next = phead;
  24. phead->prev = phead;
  25. return phead;
  26. }
  27. // 双向链表在pos位置之前进行插入x
  28. void LTInsert(LTNode* pos, LTDataType x)
  29. {
  30. assert(pos);//pos肯定不为空
  31. LTNode* prev = pos->prev;
  32. LTNode* newnode = BuyListNode(x);//创建一个需要插入的结点
  33. prev->next = newnode;
  34. newnode->prev = prev;
  35. newnode->next = pos;
  36. pos->prev = newnode;
  37. }
  38. // 双向链表的打印
  39. void LTPrint(LTNode* phead)
  40. {
  41. assert(phead);//有哨兵位
  42. printf("<=>phead<=>");
  43. LTNode* cur = phead->next;//cur指向第一个要打印的结点
  44. while (cur != phead)//cur等于头结点时打印就结束了
  45. {
  46. printf("%d<=>", cur->data);
  47. cur = cur->next;
  48. }
  49. printf("\n");
  50. }
  51. // 双向链表删除pos位置的结点
  52. void LTErase(LTNode* pos)
  53. {
  54. assert(pos);//pos肯定不为空
  55. LTNode* posprev = pos->prev;
  56. LTNode* posnext = pos->next;
  57. posprev->next = posnext;
  58. posnext->prev = posprev;
  59. free(pos);
  60. pos = NULL;
  61. //这个置空其实已经没有意义了,形参的改变不会改变实参
  62. }
  63. // 双向链表的尾插
  64. void LTPushBack(LTNode* phead, LTDataType x)
  65. {
  66. assert(phead);//有哨兵位
  67. //法一:(便于新手更好地理解双向链表的尾插)
  68. //一步就可完成链表为空/不为空的尾插
  69. //LTNode* newnode = BuyListNode(x);
  70. //LTNode* tail = phead->prev;
  71. //tail->next = newnode;
  72. //newnode->prev = tail;
  73. //newnode->next = phead;
  74. //phead->prev = newnode;
  75. //法二:函数复用(简单方便)
  76. LTInsert(phead, x);
  77. }
  78. // 双向链表的判空
  79. bool LTEmpty(LTNode* phead)
  80. {
  81. assert(phead);
  82. return phead->next == phead;
  83. //两者相等就是空链表(返回真),两者不相等就不是空链表(返回假)
  84. }
  85. // 双向链表的尾删
  86. void LTPopBack(LTNode* phead)
  87. {
  88. assert(phead);//有哨兵位
  89. //法一:(便于新手更好地理解双向链表的尾删)
  90. //assert(!LTEmpty(phead));//判空
  91. //LTNode* tail = phead->prev;
  92. //LTNode* tailPrev = tail->prev;
  93. //tailPrev->next = phead;
  94. //phead->prev = tailPrev;
  95. //free(tail);
  96. //tail = NULL;
  97. //法二:函数复用
  98. LTErase(phead->prev);
  99. }
  100. // 双向链表头插
  101. void LTPushFront(LTNode* phead, LTDataType x)
  102. {
  103. assert(phead);//有哨兵位
  104. //LTNode* newnode = BuyListNode(x);//创建一个要插入的结点
  105. //法一:只用phead和newnode两个指针(便于新手更好地理解双向链表的头插)
  106. //newnode->next = phead->next;
  107. //phead->next->prev = newnode;
  108. //phead->next = newnode;
  109. //newnode->prev = phead;
  110. //法二:多用了first先记住第一个结点(便于新手更好地理解双向链表的头插)
  111. //LTNode* first = phead->next;
  112. //phead->next = newnode;
  113. //newnode->prev = phead;
  114. //newnode->next = first;
  115. //first->prev = newnode;
  116. //法三:函数复用(简单方便)
  117. LTInsert(phead->next, x);
  118. }
  119. // 双向链表头删
  120. void LTPopFront(LTNode* phead)
  121. {
  122. assert(phead);//有哨兵位
  123. assert(!LTEmpty(phead));//判空
  124. //法一:(便于新手更好地理解双向链表的头删)
  125. //LTNode* head = phead->next;
  126. //LTNode* headnext = head->next;
  127. //phead->next = headnext;
  128. //headnext->prev = phead;
  129. //free(head);
  130. //head = NULL;
  131. //法二:函数复用(简单方便)
  132. LTErase(phead->next);
  133. }
  134. // 双向链表查找值为x的结点
  135. LTNode* LTFind(LTNode* phead, LTDataType x)
  136. {
  137. assert(phead);//有哨兵位
  138. LTNode* cur = phead->next;
  139. while (cur != phead)//让cur去遍历
  140. {
  141. if (cur->data == x)
  142. {
  143. return cur;
  144. }
  145. cur = cur->next;
  146. }
  147. return NULL;
  148. }
  149. // 双向链表的销毁
  150. void LTDestory(LTNode* phead)
  151. {
  152. assert(phead);
  153. LTNode* cur = phead->next;
  154. while (cur != phead)
  155. {
  156. LTNode* curnext = cur->next;
  157. free(cur);
  158. cur = curnext;
  159. }
  160. free(phead);
  161. phead = NULL;
  162. //这个置空其实已经没有意义了,形参的改变不会改变实参
  163. //我们为了保持接口的一致性,不传二级指针,选择在测试的时候置空
  164. }
  165. // 双向链表的修改,修改pos位置的值为x
  166. void LTModify(LTNode* pos, LTDataType x)
  167. {
  168. assert(pos);//pos肯定不为空
  169. pos->data = x;
  170. }
  171. // 双向链表删除值为x的结点
  172. void LTRemove(LTNode* phead, LTDataType x)
  173. {
  174. assert(phead);//有哨兵位
  175. LTNode* pos = phead->next;
  176. while (pos != phead)
  177. {
  178. pos = LTFind(phead, x);
  179. if (pos == NULL)//如果遍历完
  180. {
  181. return NULL;
  182. }
  183. LTErase(pos);
  184. pos = pos->next;
  185. }
  186. }
  187. // 双向链表计算结点总数(不计phead)
  188. int LTTotal(LTNode* phead)
  189. {
  190. assert(phead);//有哨兵位
  191. int count = 0;//count来计数
  192. LTNode* cur = phead->next;//让cur去遍历
  193. while (cur != phead)
  194. {
  195. count++;
  196. cur = cur->next;
  197. }
  198. return count;
  199. }
  200. // 双向链表获取第i位置的结点
  201. LTNode* LTGet(LTNode* phead, int i)
  202. {
  203. assert(phead);//有哨兵位
  204. int length = LTTotal(phead);
  205. LTNode* cur = phead->next;
  206. if (i == 0)
  207. {
  208. return phead;
  209. }
  210. else if (i<0 || i>length)//位置不合法
  211. {
  212. return NULL;
  213. }
  214. else if (i <= (length / 2))//从表头开始遍历
  215. {
  216. cur = phead->next;
  217. for (int count = 1; count < i; count++)
  218. {
  219. cur = cur->next;
  220. }
  221. }
  222. else//从表尾开始遍历
  223. {
  224. cur = phead->prev;
  225. for (int count = 1; count <= length - i; count++)
  226. {
  227. cur = cur->prev;
  228. }
  229. }
  230. return cur;
  231. }
  232. // 双向链表的清空
  233. void LTClear(LTNode* phead)
  234. {
  235. assert(phead);//有哨兵位
  236. while (!LTEmpty(phead))//如果不为空就一直头删
  237. {
  238. LTPopFront(phead);
  239. }
  240. }

在Test.c下

  1. #include"List.h"
  2. //测试函数
  3. void TestList1()
  4. {
  5. LTNode* plist = LTInit();
  6. LTInsert(plist, 1);
  7. LTInsert(plist, 2);
  8. LTInsert(plist, 3);
  9. LTInsert(plist, 4);
  10. LTPrint(plist);
  11. LTErase(plist->next);
  12. LTPrint(plist);
  13. }
  14. //测试函数
  15. void TestList2()
  16. {
  17. LTNode* plist = LTInit();
  18. LTPushBack(plist, 5);
  19. LTPushBack(plist, 6);
  20. LTPushBack(plist, 7);
  21. LTPushBack(plist, 8);
  22. LTPrint(plist);
  23. LTPopBack(plist);
  24. LTPopBack(plist);
  25. LTPrint(plist);
  26. }
  27. //测试函数
  28. void TestList3()
  29. {
  30. LTNode* plist = LTInit();
  31. LTPushFront(plist, 1);
  32. LTPushFront(plist, 2);
  33. LTPushFront(plist, 3);
  34. LTPushFront(plist, 4);
  35. LTPrint(plist);
  36. LTPopFront(plist);
  37. LTPopFront(plist);
  38. LTPrint(plist);
  39. }
  40. //测试函数
  41. TestList4()
  42. {
  43. LTNode* plist = LTInit();
  44. LTInsert(plist, 1);
  45. LTInsert(plist, 2);
  46. LTInsert(plist, 3);
  47. LTInsert(plist, 4);
  48. LTPrint(plist);
  49. LTNode* pos = LTFind(plist, 3);
  50. if (pos)
  51. {
  52. LTErase(pos);
  53. pos = NULL;
  54. }
  55. LTPrint(plist);
  56. LTDestory(plist);
  57. plist = NULL;//在这里置空
  58. }
  59. //测试函数
  60. TestList5()
  61. {
  62. LTNode* plist = LTInit();
  63. LTInsert(plist, 1);
  64. LTInsert(plist, 2);
  65. LTInsert(plist, 3);
  66. LTInsert(plist, 4);
  67. LTPrint(plist);
  68. LTModify(plist->next, 5);
  69. LTPrint(plist);
  70. }
  71. TestList6()
  72. {
  73. LTNode* plist = LTInit();
  74. LTInsert(plist, 1);
  75. LTInsert(plist, 3);
  76. LTInsert(plist, 3);
  77. LTInsert(plist, 4);
  78. LTInsert(plist, 3);
  79. LTPrint(plist);
  80. LTRemove(plist, 3);
  81. LTPrint(plist);
  82. printf("%d\n", LTTotal(plist));
  83. }
  84. //测试函数
  85. TestList7()
  86. {
  87. LTNode* plist = LTInit();
  88. LTInsert(plist, 5);
  89. LTInsert(plist, 6);
  90. LTInsert(plist, 7);
  91. LTInsert(plist, 8);
  92. LTInsert(plist, 9);
  93. LTPrint(plist);
  94. LTNode* pos = LTGet(plist, 3);
  95. if (pos)
  96. {
  97. LTErase(pos);
  98. pos = NULL;
  99. }
  100. LTPrint(plist);
  101. LTClear(plist);
  102. LTPrint(plist);
  103. }
  104. //测试函数
  105. TestList8()
  106. {
  107. LTNode* plist = LTInit();
  108. LTInsert(plist, 5);
  109. LTInsert(plist, 6);
  110. LTInsert(plist, 7);
  111. LTInsert(plist, 8);
  112. LTInsert(plist, 9);
  113. LTPrint(plist);
  114. LTClear(plist);
  115. LTPrint(plist);
  116. }
  117. int main()
  118. {
  119. //TestList1();
  120. //TestList2();
  121. //TestList3();
  122. //TestList4();
  123. //TestList5();
  124. //TestList6();
  125. //TestList7();
  126. TestList8();
  127. return 0;
  128. }

欢迎指正

 

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览46513 人正在系统学习中