• 《数据结构与算法》-双链表的增删查改,链表与顺序表的区别


    目录

    1.双链表的增删查改

    逻辑图​

    双链表概述

    双链表增删查改各个接口的实现

    双链表框架(双链表头文件Dlist.h)

    双链表各个接口的实现(Dlist.c源文件)

    链表哨兵位头结点创建、初始化

    创建一个结点

    尾插

    尾删

    头插

    头删

    判断链表是否为空

    查找

    在pos节点前插入

    在pos节点处删除

    链表销毁

    打印

    测试用的主函数所在的头文件(test.c)

    2.链表与顺序表的区别

    顺序表优点

    缺点

    链表优点

    缺点

    计算机存储层次三角形(关于cpu高速缓存)


    1.双链表的增删查改

    逻辑图

    双链表概述

    双链表即双向链表,结构体成员开两个指针,一个指针用来存放前一个结点的地址,另一个用来存放后一个结点的地址。

    带头双向循环链表,即带哨兵位头结点且循环的双链表,是更为复杂的链表。

    链表分为8种:单链表、循环单链表、带头单链表、带头循环单链表、双链表、循环双链表、带头双链表、带头循环双链表。

    其中最为复杂的是带头循环双链表,也是更容易实现代码,使代码变得简单,常用的链表。

    双链表增删查改各个接口的实现

    双链表框架(双链表头文件Dlist.h)

    1. #pragma once
    2. #include
    3. #include /*assert*/
    4. #include /*malloc*/
    5. #include /*bool类型返回值为true真或false假*/
    6. // 带头+双向+循环链表增删查改实现
    7. typedef int LTDataType;/*类型可以任意转换*/
    8. typedef struct ListNode
    9. {
    10. LTDataType data;
    11. struct ListNode* next;
    12. struct ListNode* prev;
    13. }ListNode;
    14. // 创建返回链表的头结点.
    15. ListNode* ListCreate();
    16. // 双向链表销毁
    17. void ListDestory(ListNode* pHead);
    18. // 双向链表打印
    19. void ListPrint(ListNode* pHead);
    20. // 双向链表尾插
    21. void ListPushBack(ListNode* pHead, LTDataType x);
    22. // 双向链表尾删
    23. void ListPopBack(ListNode* pHead);
    24. // 双向链表头插
    25. void ListPushFront(ListNode* pHead, LTDataType x);
    26. // 双向链表头删
    27. void ListPopFront(ListNode* pHead);
    28. // 双向链表查找
    29. ListNode* ListFind(ListNode* pHead, LTDataType x);
    30. // 双向链表在pos的前面进行插入
    31. void ListInsert(ListNode* pos, LTDataType x);
    32. // 双向链表删除pos位置的节点
    33. void ListErase(ListNode* pos);

    双链表各个接口的实现(Dlist.c源文件)

    链表哨兵位头结点创建、初始化

    1. // 可以传二级,内部置空头结点
    2. // 建议:也可以考虑用一级指针,让调用ListDestory的人置空 (保持接口一致性)
    3. ListNode* ListCreate()
    4. {
    5. ListNode* plist = (ListNode*)malloc(sizeof(ListNode));
    6. if (plist == NULL)
    7. {
    8. perror("malloc fail");
    9. exit(-1);
    10. }
    11. plist->next = plist;
    12. plist->prev = plist;
    13. return plist;
    14. }

    创建一个结点

    1. ListNode* BuyNode(LTDataType x)
    2. {
    3. ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    4. if (newNode == NULL)
    5. {
    6. perror("BuyNode malloc fail");
    7. return NULL;
    8. }
    9. newNode->next = NULL;
    10. newNode->prev = NULL;
    11. newNode->data = x;
    12. return newNode;
    13. }

    尾插

    1. void ListPushBack(ListNode* pHead, LTDataType x)
    2. {
    3. assert(pHead);
    4. /*第1种写法*/
    5. //ListNode* tail = pHead->prev;
    6. //ListNode* newNode = BuyNode(x);
    7. //tail->next = newNode;
    8. //newNode->prev = tail;
    9. //newNode->next = pHead;
    10. //pHead->prev = newNode;
    11. /*第2种写法,灵活运用双向链表的特性*/
    12. ListInsert(pHead, x);/*pHead的前方插入,即尾部*/
    13. }

    尾删

    1. void ListPopBack(ListNode* pHead)
    2. {
    3. assert(pHead);
    4. assert(pHead->next != pHead);/*只有哨兵位的头节点*/
    5. /*第二种断言是否为空:assert(!ListEmpty(phead));*/
    6. ListNode* tail = pHead->prev;/*老尾*/
    7. ListNode* newtail = tail->prev;/*新尾*/
    8. free(tail);
    9. newtail->next = pHead;
    10. pHead->prev = newtail;
    11. /*第二种尾删写法*/
    12. /*ListErase(pHead->prev);*/
    13. }

    头插

    1. void ListPushFront(ListNode* pHead, LTDataType x)
    2. {
    3. assert(pHead);
    4. ListNode* newNode = BuyNode(x);
    5. ListNode* oldNode = pHead->next;/*因为pHead是哨兵头*/
    6. pHead->next = newNode;
    7. newNode->prev = pHead;
    8. newNode->next = oldNode;
    9. oldNode->prev = newNode;
    10. /*第2种*/
    11. /*ListInsert(pHead->next, x);*/
    12. }

    头删

    1. void ListPopFront(ListNode* pHead)
    2. {
    3. assert(pHead);
    4. /*第一种断言是否为空*/
    5. if (pHead->next == pHead)
    6. {
    7. return;/*除了哨兵位,没有头结点*/
    8. }
    9. /*第二种断言是否为空:assert(!ListEmpty(phead));*/
    10. ListNode* oldHead = pHead->next;
    11. ListNode* newHead = oldHead->next;
    12. pHead->next = newHead;
    13. newHead->prev = pHead;
    14. free(oldHead);
    15. oldHead = NULL;/*置空不置空都可以,临时变量出栈销毁*/
    16. /*第二种头删写法*/
    17. /*ListErase(pHead->next);*/
    18. }

    判断链表是否为空

    1. bool ListEmpty(ListNode* phead)
    2. {
    3. assert(phead);
    4. /*第一种*/
    5. /*if (phead->next == phead)
    6. return true;
    7. else
    8. return false;*/
    9. /*第二种*/
    10. return phead->next == phead;
    11. }

    查找

    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. return cur;
    9. cur = cur->next;
    10. }
    11. return NULL;
    12. }

    在pos节点前插入

    1. void ListInsert(ListNode* pos, LTDataType x)
    2. {
    3. assert(pos);
    4. ListNode* prev = pos->prev;/*pos节点的原前者,利用prev前,next后链接就可以了*/
    5. ListNode* newNode = BuyNode(x);
    6. newNode->next = pos;
    7. pos->prev = newNode;
    8. prev->next = newNode;
    9. newNode->prev = prev;
    10. }

    在pos节点处删除

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

    链表销毁

    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. free(cur);
    9. cur = next;
    10. }
    11. free(pHead);
    12. }

    打印

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

    测试用的主函数所在的头文件(test.c)

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include "DList.h";
    3. void test()
    4. {
    5. ListNode* head = ListCreate();
    6. /*尾插*/
    7. ListPushBack(head, 1);
    8. ListPushBack(head, 2);
    9. ListPushBack(head, 3);
    10. ListPushBack(head, 4);
    11. ListPrint(head);
    12. /*尾删*/
    13. ListPopBack(head);
    14. ListPopBack(head);
    15. ListPrint(head);
    16. /*头插*/
    17. ListPushFront(head, 5);
    18. ListPushFront(head, 6);
    19. ListPushFront(head, 7);
    20. ListPushFront(head, 8);
    21. ListPrint(head);
    22. /*头删*/
    23. ListPopFront(head);
    24. ListPopFront(head);
    25. ListPrint(head);
    26. /*查找与插入连用*/
    27. ListNode* FindNode = ListFind(head, 5);
    28. if (FindNode != NULL)
    29. {
    30. ListInsert(FindNode, 5*100);
    31. ListPrint(head);
    32. }
    33. /*查找与删除连用*/
    34. FindNode = ListFind(head, 500);
    35. if (FindNode != NULL)
    36. {
    37. ListErase(FindNode);
    38. }
    39. ListPrint(head);
    40. ListDestory(head);
    41. }
    42. int main()
    43. {
    44. test();
    45. return 0;
    46. }

    2.链表与顺序表的区别

     

    顺序表优点

    ·尾插尾删效率很高

    ·随机访问(用下标)

    ·相比链表结构,顺序表cpu高速缓存命中率更高。(可以了解的知识范畴)

    缺点

    ·头部和中部插入删除效率低,时间复杂度O(N)

    ·扩容,性能消耗和空间浪费

    链表优点

    ·任意位置插入删除效率很高,时间复杂度O(1)

    ·按需申请释放

    缺点

    ·不支持随机访问

    计算机存储层次三角形(关于cpu高速缓存)

     由下至上,成本越来越高,速度越来越快,存储越来越小。

    数据结构是在主存中,vs代码经过编译后生成可执行文件,再由cpu访问,通过一定的接口,把执行的信息实现在终端窗口上

    cpu执行指令不会直接访问内存

    1.先看数据在不在三级缓存中,在(则命中),直接访问

    2.不在(不命中),先加载到缓存,再访问

    为什么顺序表比链表效率更高?

    cpu看缓存中不命中(找不到)第一个数据,先加载到缓存,由于加载具有局部原理性,一次性可能会加载16个字节(依照电脑硬件,不唯一),顺序表的效率比链表的效率更高,由于链表的地址不是连续的,所以会加载一些不用的数据,存在缓存污染,所以顺序表的高速缓存命中率更高。

    关于计算机cpu缓存,陈皓专家大佬的博客可以看看:与程序员相关的CPU缓存知识 | 酷 壳 - CoolShell

  • 相关阅读:
    AutoCAD Electrical 2022——创建项目
    Font Awesome 教程
    基础术语和模式的学习记录
    @Elasticsearch之深度应用及原理剖析--分布式数据一致性机制
    飞书开发学习笔记(二)-云文档简单开发练习
    ECharts快速入门
    基于SpringBoot的“幼儿园管理系统”的设计与实现(源码+数据库+文档+PPT)
    可以使用clang-format检查格式
    当年很流行,现在已经淘汰的前端技术有哪些?
    CB利用链及无依赖打Shiro
  • 原文地址:https://blog.csdn.net/Scholar_zhang/article/details/126244239