• 【数据结构】双向带头循环链表的实现



    一、双向循环链表与顺序表的区别

    二、List.h

    三、List.c

    1、带头双向循环链表的初始化

    2、带头双向循环链表的销毁

    3、带头双向循环链表的打印

    4、动态开辟一个节点

    5、带头双向循环链表的判空

    6、带头双向循环链表的尾插、尾删

    7、带头双向循环链表的头插、头删

    8、带头双向循环链表的长度

    9、带头双向循环链表的查找、任意位置插入、删除


    一、双向循环链表与顺序表的区别

    不同点

    顺序表

    双向带头循环链表

    在内存中的存储方式

    连续存储

    不一定连续

    随机访问

    支持随机访问

    不支持随访问

    任意元素插入或删除

    尾插O(1),其他O(N),可能需要挪动元素

    通过修改指针指向即可

    应用场景

    元素高效存储、频繁访问

    任意位置插入删除频繁

    三级缓存命中率

    寄存器的速度远快于主存,所以需要三级缓存先将数据取出,寄存器读取时将相邻地址的数据进行读取,所以顺序表内存地址连续的特点使其三级缓存命中率高。

    双向带头循环链表虽然在链表中结构最复杂,但是解决了单链表找前一个节点需要遍历、要判断头结点是否为空的缺点。

    二、List.h

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. typedef int ListDataType;
    7. typedef struct ListNode
    8. {
    9. struct ListNode* next;
    10. struct ListNode* prev;
    11. ListDataType data;
    12. }ListNode;
    13. ListNode* ListInit();//初始化
    14. void ListDestroy(ListNode* phead);//销毁
    15. void PrintList(ListNode* phead);//打印
    16. bool ListEmpty(ListNode* phead);//判断链表是否为空(只有头节点)
    17. void ListPushBack(ListNode* phead, ListDataType x);//尾插
    18. void ListPopback(ListNode* phead);//尾删
    19. void ListPushFront(ListNode* phead, ListDataType x);//头插
    20. size_t ListSize(ListNode* phead);//有效节点个数
    21. ListNode* ListFind(ListNode* phead, ListDataType x);//查找(可用于修改)
    22. void ListInsert(ListNode* pos, ListDataType x);//pos位置插入
    23. void ListErase(ListNode* pos);//删除pos位置

    结构体内包含指向下一个节点的指针next、指向上一个节点的指针prev和本节点存储的数据data

    三、List.c

    1、带头双向循环链表的初始化

    1. ListNode* ListInit()//初始化,返回带哨兵位的头结点
    2. {
    3. ListNode* guard = (ListNode*)malloc(sizeof(ListNode));
    4. if (guard == NULL)
    5. {
    6. perror("ListInit fail:\n");
    7. exit(-1);
    8. }
    9. guard->next = guard;
    10. guard->prev = guard;
    11. return guard;
    12. }

    malloc一个哨兵位的头结点,并将其next和prev指针置空。

    2、带头双向循环链表的销毁

    1. void ListDestroy(ListNode* phead)//销毁,传一级指针调用方需要在外部将头结点指针置空
    2. {
    3. assert(phead);
    4. ListNode* cur = phead->next;
    5. while (cur != phead)//释放非头结点
    6. {
    7. ListNode* next = cur->next;
    8. free(cur);
    9. cur = next;
    10. }
    11. free(phead);//再释放头结点
    12. }

    为了保持接口的一致性,形参用一级指针,所以需要调用者在外部对头指针置空。

    先释放非头结点,再释放头结点。

    3、带头双向循环链表的打印

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

    从头结点的next开始,遍历打印

    4、动态开辟一个节点

    1. ListNode* BuyNode(ListDataType x)//动态开辟一个节点
    2. {
    3. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
    4. if (newnode == NULL)
    5. {
    6. perror("BuyNode:\n");
    7. exit(-1);
    8. }
    9. newnode->data = x;
    10. newnode->prev = NULL;
    11. newnode->next = NULL;
    12. return newnode;
    13. }

    将值放入这个节点,并将两个指针置空

    5、带头双向循环链表的判空

    1. bool ListEmpty(ListNode* phead)//判断链表是否只有头节点
    2. {
    3. assert(phead);
    4. return phead->next == phead;
    5. }

    只有哨兵位这一个头结点即为空

    6、带头双向循环链表的尾插、尾删

    1. void ListPushBack(ListNode* phead, ListDataType x)//尾插
    2. {
    3. assert(phead);
    4. ListNode* tail = phead->prev;
    5. ListNode* newnode = BuyNode(x);
    6. newnode->next = phead;
    7. newnode->prev = tail;
    8. tail->next = newnode;
    9. phead->prev = newnode;
    10. }
    11. void ListPopback(ListNode* phead)//尾删
    12. {
    13. assert(phead);
    14. assert(!ListEmpty(phead));
    15. ListNode* tail = phead->prev;
    16. ListNode* prev = tail->prev;
    17. free(tail);
    18. prev->next = phead;
    19. phead->prev = prev;
    20. }

    注意尾删前需要判空

    7、带头双向循环链表的头插、头删

    1. void ListPushFront(ListNode* phead, ListDataType x)//头插
    2. {
    3. assert(phead);
    4. ListNode* cur = phead->next;
    5. ListNode* newnode = BuyNode(x);
    6. newnode->next = cur;
    7. newnode->prev = phead;
    8. cur->prev = newnode;
    9. phead->next = newnode;
    10. }
    11. void ListPopFront(ListNode* phead)//头删
    12. {
    13. assert(phead);
    14. assert(!ListEmpty(phead));
    15. ListNode* cur = phead->next;
    16. ListNode* next = cur->next;
    17. free(cur);
    18. phead->next = next;
    19. next->prev = phead;
    20. }

    注意头删前需要判空

    8、带头双向循环链表的长度

    1. size_t ListSize(ListNode* phead)//有效节点个数
    2. {
    3. assert(phead);
    4. ListNode* cur = phead->next;
    5. size_t count = 0;
    6. while (cur != phead)
    7. {
    8. cur = cur->next;
    9. ++count;
    10. }
    11. return count;
    12. }

    循环计数即可,哨兵位不算

    9、带头双向循环链表的查找、任意位置插入、删除

    1. ListNode* ListFind(ListNode* phead, ListDataType x)//查找(可用于修改)
    2. {
    3. assert(phead);
    4. ListNode* cur = phead->next;
    5. while (cur!=phead)
    6. {
    7. if (cur->data == x)
    8. return cur;
    9. cur = cur->next;
    10. }
    11. return NULL;
    12. }
    13. void ListInsert(ListNode* pos, ListDataType x)//pos位置之前插入
    14. {
    15. assert(pos);
    16. ListNode* newnode = BuyNode(x);
    17. ListNode* prev = pos->prev;
    18. prev->next = newnode;
    19. newnode->prev = prev;
    20. newnode->next = pos;
    21. pos->prev = newnode;
    22. }
    23. void ListErase(ListNode* pos)//删除pos位置
    24. {
    25. assert(pos);
    26. ListNode* prev = pos->prev;
    27. ListNode* next = pos->next;
    28. free(pos);
    29. prev->next = next;
    30. next->prev = prev;
    31. }

    查找:遍历链表,返回第一个data==x的节点指针,查找通常配合下面两个接口使用

    任意位置插入、删除:使用pos位置进行插入删除操作。

    头插、头删、尾插、尾删接口可以复用上面三个接口

  • 相关阅读:
    软考高级系统架构设计师系列论文二十八:论需求分析方法及应用
    ChatGPT追祖寻宗:GPT-2论文要点解读
    Qt应用程序打包工具安装
    c++动态库调用
    Ruby网络爬虫教程:从入门到精通下载图片
    【开源】物联网智慧消防云平台系统,前后端分离,微服务框架带文档,源码分享
    DDD防腐层设计
    <学习笔记>从零开始自学Python-之-基础语法篇(九)类的继承与多态
    C++编译错误
    SpringBoot中post请求报405错误排坑
  • 原文地址:https://blog.csdn.net/gfdxx/article/details/126287813