当前位置:   article > 正文

带头双向循环链表

带头双向循环链表

目录

1.1双向链表的结点结构

1.2双向链表的创建

1.3双向链表的初始化

1.4双向链表的尾插

 1.5双向链表的打印

1.6双向链表的头插法

 1.7双向链表的尾删法

 1.8双向链表的头删法

1.9双向链表查找值

1.10双向链表的插入(前插)

 1.11双向链表的删除

1.12判断双向链表是否为空(空返回1,非空返回0)

1.13求双链表长度

1.14销毁双链表

 补充:顺序表和链表的区别



1.1双向链表的结点结构

  1. typedef int LTDataType;
  2. typedef struct ListNode
  3. {
  4. struct ListNode* next;
  5. struct ListNode* prev;
  6. LTDataType data;
  7. }ListNode;

使用typedef:如果一开始我们就确定了结构体中的变量类型,后续在项目过程中如果需要对这个变量类型进行调整,那么所需的操作是很繁琐的。故使用typedef,后续若是需要修改,改动typedef就足够了。

1.2双向链表的创建

  1. ListNode* BuyListNode(LTDataType x)
  2. {
  3. ListNode* node = (ListNode*)malloc(sizeof(ListNode));
  4. if (node == NULL)
  5. {
  6. perror("错误原因:");
  7. exit(-1);
  8. }
  9. node->data = x;
  10. node->next = NULL;
  11. node->prev = NULL;
  12. return node;
  13. }

1.3双向链表的初始化

不需要二级指针是因为其带了哨兵位的头结点

  1. ListNode* ListInit()
  2. {
  3. ListNode* phead = BuyListNode(0);
  4. phead->next = phead;
  5. phead->prev = phead;
  6. return phead;
  7. }

1.4双向链表的尾插

尾插先找尾,而phead的prev为尾

 

 image-20210812183854878

 image-20210812184128925

image-20210812184243320

  1. void ListPushBack(ListNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. ListNode* tail = phead->prev;
  5. ListNode* newnode = BuyListNode(x);
  6. //原来尾结点和现在新结点链接
  7. tail->next = newnode;
  8. newnode->prev = tail;
  9. //新结点和头结点链接
  10. newnode->next = phead;
  11. phead->prev = newnode;
  12. }

如果只插入一个结点是否需要改正上述代码?不需要

 1.5双向链表的打印

 注意循环终止的条件

  1. void ListPrint(ListNode* phead)
  2. {
  3. ListNode* cur = phead->next;
  4. while (cur != phead)
  5. {
  6. printf("%d", cur->data);
  7. cur = cur->next;
  8. }
  9. printf("\n");
  10. }

1.6双向链表的头插法

 

 image-20210812185758040

image-20210812190100341

image-20210812190144568

  1. void ListPushFront(ListNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. ListNode* first = phead->next;
  5. ListNode* newnode = BuyListNode(x);
  6. phead->next = newnode;
  7. newnode->prev = phead;
  8. newnode->next = first;
  9. first->prev = newnode;
  10. }

用头插法只插入一个结点是否可行?可行

 

 1.7双向链表的尾删法

image-20210812185210087

 image-20210812185227614

  1. void ListNodePopBack(ListNode* phead)
  2. {
  3. assert(phead);
  4. ListNode* tail = phead->prev;
  5. ListNode* tailPrev = tail->prev;
  6. free(tail);
  7. tailPrev->next = phead;
  8. phead->prev = tailPrev;
  9. }

只有一个结点时(非头结点)是否可用上述代码?进入free循环时头结点会被删除

改正:加一步判断 assert(phead->next != phead);

 

 1.8双向链表的头删法

image-20210812190437575

image-20210812190509966

  1. void ListNodePopFront(ListNode* phead)
  2. {
  3. assert(phead);
  4. assert(phead->next != phead);
  5. ListNode* first = phead->next;
  6. ListNode* second = first->next;
  7. free(first);
  8. phead->next = second;
  9. second->prev = phead;
  10. }

1.9双向链表查找值

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

1.10双向链表的插入(前插)

  1. void ListInsert(ListNode* pos, LTDataType x)
  2. {
  3. assert(pos);
  4. ListNode* prev = pos->prev;
  5. ListNode* newnode = BuyListNode(x);
  6. prev->next = newnode;
  7. newnode->prev = prev;
  8. newnode->next = pos;
  9. pos->prev = newnode;
  10. }

pos为头和尾代码能否运行?若x = 4

相当于尾插(在phead前面插入一个结点)

 

 由此,尾插法的代码可改为

ListInsert(phead,x);

 头插法可改为

ListInsert(phead->next,x);

 1.11双向链表的删除

  1. void ListErase(ListNode* pos )
  2. {
  3. assert(pos);
  4. ListNode*prev = pos->prev;
  5. ListNode*next = pos->next;
  6. prev->next = next;
  7. next->prev = prev;
  8. free(pos);
  9. }

由此,尾删除法可改为

ListErase(phead->prev);

 头删法可改为

ListErase(phead->next)

1.12判断双向链表是否为空(空返回1,非空返回0)

  1. int ListEmpty(ListNode* phead)
  2. {
  3. assert(phead);
  4. return phead->next == phead ? 1 : 0;
  5. }

1.13求双链表长度

  1. int ListSize(ListNode* phead)
  2. {
  3. assert(phead);
  4. int size = 0;
  5. ListNode* cur = phead->next;
  6. while (cur != phead)
  7. {
  8. ++size;
  9. cur = cur->next;
  10. }
  11. return size;
  12. }

1.14销毁双链表

  1. void ListDestory(ListNode* phead)
  2. {
  3. assert(phead);
  4. ListNode* cur = phead->next;
  5. while (cur != phead)
  6. {
  7. ListNode* next = cur->next;//提前保存它的下一个
  8. //ListErase(cur);
  9. free(cur);
  10. cur = next;
  11. }
  12. free(phead);
  13. //phead = NULL;
  14. }

使用函数的时候注意将野指针置(或者用二级指针,因为传一级指针时函数内部的置空不起作用)

ListDestory(plist);

plist = NULL;

 补充:顺序表和链表的区别

不同点顺序表链表
存储空间上物理上连续逻辑上连续,但物理上不连续
随机访问支持O(1)不支持:O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低,O(N)只需修改指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
缓存利用率

优点

1.按下标去进行随机访问

2、cpu高速缓存命中率比较高

1.按需申请内存,需要存一个数据就申请一块内存

2.任意位置插入和删除数据效率高,O(1)

缺点

1.空间不够需要增容(一定程序的性能消耗)可能存在一定的空间浪费

2、头部或者中间插入删除数据需要挪动数据,O(N)

不支持下标的随机访问

 

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

闽ICP备14008679号