• 链表(初探)


    写作背景

    又是可悲的一天,重庆单天新增800多例.光是我们这栋楼就有三四个.在家"疯"控第三天晚上,闲来无事,整理一下编程笔记.

    引入

    链表是一种用于存储数据的数据结构,通过如链条一般的指针来连接元素。它的特点是插入与删除数据十分方便,但寻找与读取数据的表现欠佳。

    与数组的区别

    链表和数组都可用于存储数据。与链表不同,数组将所有元素按次序依次存储。不同的存储结构令它们有了不同的优势:

    链表因其链状的结构,能方便地删除、插入数据,操作次数是 O(1)。但也因为这样,寻找、读取数据的效率不如数组高,在随机访问数据中的操作次数是 O(n)。

    数组可以方便地寻找并读取数据,在随机访问中操作次数是O(1)。但删除、插入的操作次数是O(n)次。

    构建链表

    Tip

    构建链表时,使用指针的部分比较抽象,光靠文字描述和代码可能难以理解,建议配合作图来理解。

    单向链表

    单向链表中包含数据域和指针域,其中数据域用于存放数据,指针域用来连接当前结点和下一节点。

    1. struct Node {
    2. int value;
    3. Node *next;
    4. };

    双向链表

    双向链表中同样有数据域和指针域。不同之处在于,指针域有左右(或上一个、下一个)之分,用来连接上一个结点、当前结点、下一个结点。

    1. struct Node {
    2. int value;
    3. Node *left;
    4. Node *right;
    5. };

    向链表中插入(写入)数据

    单向链表

    流程大致如下:

    1. 初始化待插入的数据 node
    2. 将 node 的 next 指针指向 p 的下一个结点;
    3. 将 p 的 next 指针指向 node

    代码实现如下:

    1. void insertNode(int i, Node *p) {
    2. Node *node = new Node;
    3. node->value = i;
    4. node->next = p->next;
    5. p->next = node;
    6. }

    单向循环链表

    将链表的头尾连接起来,链表就变成了循环链表。由于链表首尾相连,在插入数据时需要判断原链表是否为空:为空则自身循环,不为空则正常插入数据。

    大致流程如下:

    1. 初始化待插入的数据 node
    2. 判断给定链表 p 是否为空;
    3. 若为空,则将 node 的 next 指针和 p 都指向自己;
    4. 否则,将 node 的 next 指针指向 p 的下一个结点;
    5. 将 p 的 next 指针指向 node

     代码实现如下:

    1. void insertNode(int i, Node *p) {
    2. Node *node = new Node;
    3. node->value = i;
    4. node->next = NULL;
    5. if (p == NULL) {
    6. p = node;
    7. node->next = node;
    8. } else {
    9. node->next = p->next;
    10. p->next = node;
    11. }
    12. }

    双向循环链表

    在向双向循环链表插入数据时,除了要判断给定链表是否为空外,还要同时修改左、右两个指针。

    大致流程如下:

    1. 初始化待插入的数据 node
    2. 判断给定链表 p 是否为空;
    3. 若为空,则将 node 的 left 和 right 指针,以及 p 都指向自己;
    4. 否则,将 node 的 left 指针指向 p;
    5. 将 node 的 right 指针指向 p 的右结点;
    6. 将 p 右结点的 left 指针指向 node
    7. 将 p 的 right 指针指向 node

    代码实现如下:

    1. void insertNode(int i, Node *p) {
    2. Node *node = new Node;
    3. node->value = i;
    4. if (p == NULL) {
    5. p = node;
    6. node->left = node;
    7. node->right = node;
    8. } else {
    9. node->left = p;
    10. node->right = p->right;
    11. p->right->left = node;
    12. p->right = node;
    13. }
    14. }

    单向(循环)链表

    设待删除结点为 p,从链表中删除它时,将 p 的下一个结点 p->next 的值覆盖给 p 即可,与此同时更新 p 的下下个结点。

    流程大致如下:

    1. 将 p 下一个结点的值赋给 p,以抹掉 p->value
    2. 新建一个临时结点 t 存放 p->next 的地址;
    3. 将 p 的 next 指针指向 p 的下下个结点,以抹掉 p->next
    4. 删除 t。此时虽然原结点 p 的地址还在使用,删除的是原结点 p->next 的地址,但 p 的数据被 p->next 覆盖,p 名存实亡。

    代码实现如下:

    1. void deleteNode(Node *p) {
    2. p->value = p->next->value;
    3. Node *t = p->next;
    4. p->next = p->next->next;
    5. delete t;
    6. }

    双向循环链表

    流程大致如下:

    1. 将 p 左结点的右指针指向 p 的右节点;
    2. 将 p 右结点的左指针指向 p 的左节点;
    3. 新建一个临时结点 t 存放 p 的地址;
    4. 将 p 的右节点地址赋给 p,以避免 p 变成悬垂指针;
    5. 删除 t

    代码实现如下:

    1. void deleteNode(Node *&p) {
    2. p->left->right = p->right;
    3. p->right->left = p->left;
    4. Node *t = p;
    5. p = p->right;
    6. delete t;
    7. }

    技巧

    异或链表

    异或链表(XOR Linked List)本质上还是 双向链表,但它利用按位异或的值,仅使用一个指针的内存大小便可以实现双向链表的功能。

    我们在结构 Node 中定义 lr = left ^ right,即前后两个元素地址的 按位异或值。正向遍历时用前一个元素的地址异 或当前节点的 lr 可得到后一个元素的地址,反向遍历时用后一个元素的地址异或当前节点的 lr 又可得到前一个的元素地址。 这样一来,便可以用一半的内存实现双向链表同样的功能。

  • 相关阅读:
    元宇宙大投资 & 元宇宙通证
    实施OKR过程中需要注意的7个事项,尤其是第2个!
    【五】【算法分析与设计】双指针的初见
    Azkaban Flow 2.0的使用
    详解欧拉计划第622题:完美洗牌
    java---卡特兰数---满足条件的01序列(每日一道算法2022.9.29)
    Libgdx游戏开发(6)——游戏暂停
    Windows的批处理——获取系统时间、生成当天日期日志
    Arm功耗管理精讲与实战
    如何去除数据库中重复的数据
  • 原文地址:https://blog.csdn.net/cyyyyds857/article/details/127813582