• 带头双向循环链表


    一、头文件

    Link.h这个头文件中包含了顺序表的定义和对顺序表进行操作的所有函数,所有的函数只传递结构体指针以加快程序运行速度。

    1. #include
    2. #include
    3. #include
    4. #include//需要的头文件
    5. #define TYPE int
    6. typedef struct DoubleList
    7. {
    8. TYPE data;//数据
    9. struct DoubleList* prev;//指向前面节点的指针,头节点指向尾节点
    10. struct DoubleList* next;//指向后面节点的指针,尾节点指向头节点
    11. }DList;
    12. //初始化双向链表
    13. DList* InitDList(DList* p);
    14. //初始化节点
    15. DList* Buylistnode(TYPE x);
    16. //头插
    17. void DListfrontpush(DList* p, TYPE x);
    18. //尾插
    19. void DListbackpush(DList* p, TYPE x);
    20. //头删
    21. void DListfrontpop(DList* p);
    22. //尾删
    23. void DListbackpop(DList* p);
    24. //判读链表是否为空
    25. bool ListEmpty(DList* p);
    26. //计算大小
    27. size_t ListSize(DList* p);
    28. //寻找元素
    29. DList* ListFind(DList* p, TYPE x);
    30. // 在pos之前插入
    31. void ListInsert(DList* pos, TYPE x);
    32. // 删除pos位置
    33. void ListErase(DList* pos);
    34. //考虑用一级指针,让调用ListDestory的人置空(保持接口一致性)
    35. void ListDestory(DList* p);
    36. //打印链表
    37. void print(DList* p);

    二、函数

    这些函数储存在一个function.c 文件中

    1.初始化链表

    DList* InitDList(DList* p);

    先申请一个哨兵卫,让哨兵卫的prev和next都指向自己

    1. //初始化双向链表
    2. DList* InitDList(DList* p)
    3. {
    4. DList* guard = (DList*)malloc(sizeof(DList));
    5. if (guard == NULL)
    6. {
    7. perror("malloc fail");
    8. exit(-1);
    9. }
    10. guard->next = guard;
    11. guard->prev = guard;
    12. }

    2.申请一个节点

    DList* InitDList(DList* p);

    从堆区申请一个节点的空间,让哨兵卫的prev和next都置空,等待后面的操作

    1. DList* Buylistnode(TYPE x)
    2. {
    3. DList* newcode = (DList*)malloc(sizeof(DList));
    4. if (newcode == NULL)
    5. {
    6. perror("malloc fail");
    7. exit(-1);
    8. }
    9. newcode->data = x;
    10. newcode->next = NULL;
    11. newcode->prev = NULL;
    12. return newcode;
    13. }

    3.头插

    void DListfrontpush(DList* p, TYPE x);

    定义newcode指针指向新开辟的节点,然后因为p解引用后拿到的是哨兵卫的地址,我们新建一个first指针储存哨兵卫的next同时也是有效数据储存的头一个节点。

    由于双向链表可以前后寻找,所以只需要改变哨兵卫的next,新节点的prev和next,还有原来的有效数据的头的prev三个节点的数据就能将新的节点连接到里面

    1. //头插
    2. void DListfrontpush(DList* p, TYPE x)
    3. {
    4. DList* newcode = Buylistnode(x);
    5. DList* first = p->next;
    6. first->prev = newcode;
    7. p->next = newcode;
    8. newcode->next = first;
    9. newcode->prev = p;
    10. }

    4.尾插

    void DListbackpush(DList* p, TYPE x);

    定义newcode指针指向新开辟的节点,然后因为p解引用后拿到的是哨兵卫的地址同时我们新建一个prevcode指针。由于链表是循环的,所以哨兵卫的prev就是尾节点,prevcode指针赋值为这个尾节点

    我们首先将新的尾节点的next指向哨兵卫,然后哨兵卫的prev改成新节点的地址,我们此时的prevcode指针指向的节点就可以与newcode链接形成新链表

    1. //尾插
    2. void DListbackpush(DList* p, TYPE x)
    3. {
    4. DList* newcode = Buylistnode(x);
    5. DList* prevcode = p->prev;
    6. newcode->next = p;
    7. p->prev = newcode;
    8. prevcode->next = newcode;
    9. newcode->prev = prevcode;
    10. }

    5.判断链表是否为空

    bool ListEmpty(DList* p);

    很简单,看看哨兵卫next是否指向它自己

    1. bool ListEmpty(DList* p)
    2. {
    3. assert(p);
    4. return p->next == p;
    5. }

    6.头删

    void DListfrontpop(DList* p);

    定义两个指针,first指向有效数据头节点,second指向第二个节点,将哨兵卫的next改为指向second,second节点的prev指向哨兵卫。这里不需要担心只有一个有效节点的情况,如果只有一个节点,second就指向哨兵卫,代码跑完后还是哨兵卫自己指向自己

    1. //头删
    2. void DListfrontpop(DList* p)
    3. {
    4. assert(!ListEmpty(p));//保证链表不为空
    5. DList* first = p->next;
    6. DList* second = first->next;
    7. p->next = second;
    8. second->prev = p;
    9. free(first);
    10. first = NULL;
    11. }

    6.尾删

    void DListfrontpop(DList* p);

    定义两个指针,tail指向有效数据尾节点,near指向倒数第二个节点,将near的next指向哨兵卫的prev改为指向near,释放空间即可。

    1. //尾删
    2. void DListbackpop(DList* p)
    3. {
    4. assert(!ListEmpty(p));
    5. DList* tail = p->prev;
    6. DList* near = tail->prev;
    7. near->next = p;
    8. p->prev = near;
    9. free(tail);
    10. tail = NULL;
    11. }

    7.打印链表

    void print(DList* p);

    以数据有效的头节点尾起点,直到遇到哨兵卫停止打印。

    1. //打印链表
    2. void print(DList* p)
    3. {
    4. assert(p);
    5. DList* cur = p->next;
    6. printf("head<=>");
    7. for (cur = p->next; cur != p; cur = cur->next)
    8. {
    9. printf("%d<=>",cur->data);
    10. }
    11. printf("head\n");
    12. }

    8.链表中有效数据的个数

    size_t ListSize(DList* p);

    以数据有效的头节点尾起点,直到遇到哨兵卫停止计数。

    1. size_t ListSize(DList* p)
    2. {
    3. assert(p);
    4. size_t n = 0;
    5. DList* cur = p->next;
    6. for (cur = p->next; cur != p;cur = cur->next)
    7. {
    8. n++;
    9. }
    10. return n;
    11. }

    9.链表中寻找存储相应数据的节点

    DList* ListFind(DList* p, TYPE x);

    以数据有效的头节点尾起点,直到遇到对应节点停止,又循环回到了哨兵卫就返回NULL

    1. DList* ListFind(DList* p, TYPE x)
    2. {
    3. assert(p);
    4. size_t n = 0;
    5. DList* cur = p->next;
    6. while (cur != p)
    7. {
    8. if (cur->data == x)
    9. {
    10. return cur;
    11. }
    12. cur = cur->next;
    13. }
    14. return NULL;
    15. }

    10.链表在pos节点之前插入数据

    void ListInsert(DList* pos, TYPE x);

    首先,不破坏原链表的链接情况,将newcode的prev和next连接前后。

    然后,pos的prev依旧是原链表的前一个,此时需要改原链表前一个节点的next为新节点的地址,最后pos的prev链接newcode就完成了

    1. // 在pos之前插入
    2. void ListInsert(DList* pos, TYPE x)
    3. {
    4. assert(pos);
    5. DList* newcode = Buylistnode(x);
    6. newcode->prev = pos->prev;
    7. newcode->next = pos;
    8. pos->prev->next = newcode;
    9. pos->prev = newcode;
    10. }

    11.链表删除pos节点

    void ListErase(DList* pos);

    建立两个指针,prev指向pos前的节点,first指向后节点,直接将prev和first连接起来并释放pos

    1. // 删除pos位置
    2. void ListErase(DList* pos)
    3. {
    4. assert(pos);
    5. DList* prev = pos->prev;
    6. DList* next = pos->next;
    7. prev->next = next;
    8. next->prev = prev;
    9. free(pos);
    10. pos = NULL;
    11. }

    12.链表销毁

    void ListDestory(DList* p);

    从数据的有效数据头节点逐个向后释放直到回到哨兵卫,然后释放哨兵卫。

    然而此时虽然所有的堆区空间已经释放,但是用于传参的原指针p依旧指向那块空间,这时我们就需要手动在测试函数中置空

    1. //考虑用一级指针,让调用ListDestory的人置空(保持接口一致性)
    2. void ListDestory(DList* p)
    3. {
    4. assert(p);
    5. DList* cur = p->next;
    6. while (cur != p)
    7. {
    8. DList* next = cur->next;
    9. free(cur);
    10. cur = next;
    11. }
    12. free(p);
    13. p = NULL;
    14. }
    15. //配合使用
    16. int main()
    17. {
    18. DList s;
    19. DList* plist = &s;
    20. plist = InitDList(plist);
    21. DListfrontpush(plist, 4);
    22. DListfrontpush(plist, 3);
    23. DListfrontpush(plist, 2);
    24. DListfrontpush(plist, 1);
    25. ListDestory(plist);
    26. plist = NULL;//注意这里
    27. return 0;
    28. }

    三、调试

    通过test.c来进行操作

    1. void test1()
    2. {
    3. DList s;
    4. DList* plist = &s;
    5. plist = InitDList(plist);
    6. DListfrontpush(plist, 4);
    7. DListfrontpush(plist, 3);
    8. DListfrontpush(plist, 2);
    9. DListfrontpush(plist, 1);
    10. print(plist);
    11. DListbackpush(plist, 10);
    12. print(plist);
    13. DListfrontpop(plist);
    14. print(plist);
    15. DListbackpop(plist);
    16. print(plist);
    17. ListInsert(ListFind(plist, 3) , 12);
    18. print(plist);
    19. ListErase(ListFind(plist, 2));
    20. print(plist);
    21. printf("%d", ListSize(plist));
    22. ListDestory(plist);
    23. plist = NULL;
    24. }
    25. int main()
    26. {
    27. test1();
    28. return 0;
    29. }

     

  • 相关阅读:
    Git的安装与使用
    学习Java的第三十二天。。。(XML)
    C语言ASCII码排序(1086: ASCII码排序(多实例测试))
    RabbitMQ的学习之路(二-4)模式详解之路由routing路由模式
    【Hack The Box】windows练习-- Bastion
    ES6 | (二)ES6 新特性(下) | 尚硅谷Web前端ES6教程
    2023-5-27第二十七天
    抗疫行动题材网页设计 大学生最美逆行者感动人物网页代码 众志成城万众一心抗击疫情HTML网页设计
    HOOK Native API
    【ICPR 2021】遥感图中的密集小目标检测:Tiny Object Detection in Aerial Images
  • 原文地址:https://blog.csdn.net/qq_65285898/article/details/126555817