当前位置:   article > 正文

NzN的数据结构--实现双向链表

NzN的数据结构--实现双向链表

        上一章中,我们学习了链表中的单链表,那今天我们来学习另一种比较常见的链表--双向链表!!

目录

一、双向链表的结构

二、 双向链表的实现

1. 双向链表的初始化和销毁

2. 双向链表的打印

3. 双向链表的头插/尾插

4. 双向链表的头删/尾删

5. 查找数据是否存在

6. 在指定位置之后插入数据

7. 删除指定位置的数据

8. 判断双向链表是否为空

三、顺序表和双向链表的优缺点分析


一、双向链表的结构

        “哨兵位”存在的意义:遍历循环链表避免死循环。

        注意:带头链表里的头节点,实际为“哨兵位,不存储任何有效数据。

  1. typedef int LTDataType;
  2. typedef struct ListNode
  3. {
  4. LTDataType data;//存储的数据
  5. struct ListNode* prev; //指针保存前一个节点的地址
  6. struct ListNode* next; //指针保存下一个节点的地址
  7. }LTNode;

二、 双向链表的实现

        我们先在头文件中定义需要实现的相关接口。

  1. //List.h
  2. #include<stdio.h>
  3. #include <stdbool.h>//引用bool类型
  4. #include<stdlib.h>
  5. typedef int LTDataType;
  6. typedef struct ListNode
  7. {
  8. LTDataType data;//存储的数据
  9. struct ListNode* prev; //指针保存前一个节点的地址
  10. struct ListNode* next; //指针保存下一个节点的地址
  11. }LTNode;
  12. //创建节点
  13. LTNode* LTBuyNode(LTDataType x);
  14. //双向链表有哨兵位,插入数据之前链表中必须初始化一个哨兵位
  15. //需要修改哨兵位就要传二级指针
  16. //void LTInit(LTNode** pphead);
  17. LTNode* LTInit();
  18. void LTDestroy(LTNode* phead);
  19. void LTPrint(LTNode* phead);
  20. //头插/尾插
  21. //不需要修改哨兵位就不需要传二级指针
  22. void LTPushBack(LTNode* phead, LTDataType x);
  23. void LTPushFront(LTNode* phead, LTDataType x);
  24. //头删/尾删
  25. void LTPopBack(LTNode* phead);
  26. void LTPopFront(LTNode* phead);
  27. //查找数据是否存在
  28. LTNode* LTFind(LTNode* phead, LTDataType x);
  29. //在pos位置之后插入数据
  30. void LTInsert(LTNode* pos, LTDataType x);
  31. //删除pos位置的数据
  32. void LTErase(LTNode* pos);
  33. //判断链表是否为空
  34. bool LTEmpty(LTNode* phead);

1. 双向链表的初始化和销毁

  1. //双向链表初始化
  2. void LTInit(LTNode** pphead)
  3. {
  4. *pphead = (LTNode*)malloc(sizeof(LTNode));
  5. if (*pphead == NULL)
  6. {
  7. perror("malloc fail");
  8. exit(1);
  9. }
  10. (*pphead)->data = -1;//给哨兵位一个无效的数据,是多少都可以
  11. //带头双向循环链表在刚初始化一个哨兵位时,next和prev都指向自己
  12. (*pphead)->next = (*pphead)->prev = *pphead;
  13. return phead;
  14. }

        这种写法要涉及到二级指针,非常麻烦,那我们尝试简化一下代码。

  1. LTNode* LTInit()
  2. {
  3. LTNode*phead= (LTNode*)malloc(sizeof(LTNode));
  4. if (phead == NULL)
  5. {
  6. perror("malloc fail");
  7. exit(1);
  8. }
  9. phead->data = -1;
  10. phead->next = phead->prev = phead;
  11. return phead;
  12. }

        实际上,这段代码还可以进行简化。因为双向链表为空时,仍然有一个哨兵位,那我们在初始化时就可以直接申请一个哨兵位。

  1. //将申请节点的功能进行封装
  2. LTNode* LTBuyNode(LTDataType x) {
  3. LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  4. if (newnode == NULL) {
  5. perror("malloc");
  6. exit(1);
  7. }
  8. newnode->data = x;
  9. newnode->next = newnode->prev = newnode;
  10. return newnode;
  11. }
  12. //双向链表初始化
  13. LTNode* LTInit()
  14. {
  15. LTNode* phead = LTBuyNode(-1);//申请哨兵位
  16. return phead;
  17. }
  18. //双向链表销毁
  19. void LTDestroy(LTNode* phead)
  20. {
  21. assert(phead);
  22. //遍历链表,把每一个节点都释放
  23. LTNode* pcur = phead->next;
  24. while (pcur != phead)
  25. {
  26. LTNode* next = pcur->next;
  27. free(pcur);
  28. pcur = next;
  29. }
  30. //链表中哨兵位也要释放
  31. free(phead);
  32. phead = NULL;
  33. }

2. 双向链表的打印

  1. //双向链表打印
  2. void LTPrint(LTNode* phead)
  3. {
  4. assert(phead);//phead不能为空
  5. LTNode* pcur = phead->next;
  6. while (pcur != phead)
  7. {
  8. //从第一个节点开始走,走到哨兵位结束
  9. printf("%d->", pcur->data);
  10. pcur = pcur->next;
  11. }
  12. printf("\n");
  13. }

3. 双向链表的头插/尾插

  1. void LTPushBack(LTNode* phead, LTDataType x)
  2. {
  3. LTNode* newnode = LTBuyNode(x);
  4. //ptail->next=phead;//尾节点的next指向哨兵位
  5. //phead->prev=ptail//哨兵位的prev指向尾节点
  6. //新尾节点的next要指向哨兵位
  7. newnode->next = phead;
  8. //新尾节点的prev要指向原来的尾节点
  9. newnode->prev = phead->prev;
  10. //原来尾节点的next指向新的尾节点
  11. phead->prev->next = newnode;
  12. //哨兵位的prev连接新的尾节点
  13. phead->prev = newnode;
  14. }
  15. void LTPushFront(LTNode* phead, LTDataType x)
  16. {
  17. assert(phead);
  18. LTNode* newnode = LTBuyNode(x);
  19. //新的头节点的next指向原来的头节点
  20. newnode->next = phead->next;
  21. //新的头节点的prev指向哨兵位
  22. newnode->prev = phead;
  23. //原来头节点的prev指向新的头节点
  24. phead->next->prev = newnode;
  25. //哨兵位的next指向新的头节点
  26. phead->next = newnode;
  27. }

4. 双向链表的头删/尾删

  1. void LTPopBack(LTNode* phead)
  2. {
  3. assert(phead);
  4. //链表不能为空(只有一个哨兵位)
  5. assert(phead->next != phead);
  6. LTNode* del = phead->prev;
  7. LTNode* prev = del->prev;
  8. //原来尾节点的前一个节点的next指向哨兵位
  9. prev->next = phead;
  10. //哨兵位的prev变成原来尾节点的前一个节点
  11. phead->prev = prev;
  12. //释放原来的尾节点
  13. free(del);
  14. del = NULL;
  15. }
  16. void LTPopFront(LTNode* phead)
  17. {
  18. assert(phead);
  19. //链表不能为空(只有一个哨兵位)
  20. assert(phead->next != phead);
  21. LTNode* del = phead->next;
  22. LTNode* next = del->next;
  23. //原来头节点的后一个节点的prev指向哨兵位
  24. next->prev = phead;
  25. //哨兵位的next变成原来头节点的后一个节点
  26. phead->next = next;
  27. //释放原来的尾节点
  28. free(del);
  29. del = NULL;
  30. }

5. 查找数据是否存在

  1. LTNode* LTFind(LTNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. LTNode* pcur = phead->next;
  5. while (pcur != phead)
  6. {
  7. if (pcur->data == x) {
  8. return pcur;
  9. }
  10. pcur = pcur->next;
  11. }
  12. return NULL;
  13. }

6. 在指定位置之后插入数据

  1. void LTInsert(LTNode* pos, LTDataType x)
  2. {
  3. assert(pos);
  4. LTNode* newnode = LTBuyNode(x);
  5. //新节点的next指向pos后面原来的节点
  6. newnode->next = pos->next;
  7. //新节点的prev指向pos节点
  8. newnode->prev = pos;
  9. //pos节点后面原来的节点的prev换成新节点
  10. pos->next->prev = newnode;
  11. //pos节点的next换成新节点
  12. pos->next = newnode;
  13. }

7. 删除指定位置的数据

  1. void LTErase(LTNode* pos)
  2. {
  3. assert(pos);
  4. //pos前面节点的next指向pos后面的节点
  5. pos->prev->next = pos->next;
  6. //pos后面节点的prev指向pos前面的节点
  7. pos->next->prev = pos->prev;
  8. free(pos);
  9. pos = NULL;
  10. }

8. 判断双向链表是否为空

  1. bool LTEmpty(LTNode* phead)
  2. {
  3. return phead->next == phead;
  4. }

三、顺序表和双向链表的优缺点分析

顺序表

带头双向循环链表

优点

下标随机访问(实现二分查找、排序、堆算法等);

Cache命中率高(存储空间连续)

任意位置插入删除数据效率高;

按需申请、释放,不存在空间浪费

缺点

前面部分的插入删除,效率低下;

扩容会有效率损失,还可能会存在空间浪费

不支持下标随机访问;

Cache命中率低(存储空间不连续)

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

闽ICP备14008679号