• AcWing 827. 双链表


    题目描述


    分析:

    这里依然是用数组来模拟双链表。双链表中有一个头指针 head 和一个尾指针 tail,为了模拟方便,我们将 head 固定在模拟数组的下标 0 的位置,tail 固定在模拟数组下标 1 的位置。即 idx 初始化后从 2 开始。

    void init()
    {
        // r[1] l[0] 恒为空
        r[0] = 1; // head 向后指向 tail
        l[1] = 0; // tail 向前指向 head
        idx = 2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    插入

    初始化后就可以对链表进行插入结点了,有四种插入方法:

    1. 在第 k 个插入的数右边插入结点
    2. 在第 k 个插入的数的左边插入结点
    3. 在链表最右端插入结点
    4. 在链表最左端插入结点

    首先要说的是这四个插入位置用的是同一个函数!

    // 都可以看作是在第 k 个结点右边插入一个结点
    void add(int k, int x)
    {
        e[idx] = x; // 存储数据
        l[idx] = k; // ① 当前结点的左指针指向第 k 个结点
        r[idx] = r[k]; // ② 当前结点的右指针设为第 k 个结点的右指针指向
        l[r[k]] = idx; // ③ 第 k 个结点右指针指向结点的左指针设置为当前结点
        r[k] = idx; // ④ 第 k 个结点的右指针指向当前节点
        idx ++;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    不同的在于传入的实参上。我们分别看起:
    说明一下:以下的操作顺序皆为个人习惯问题·

    1.在链表最左端插入结点:
    当输入为一个 L 时,我们可以理解为在第 0 个插入的结点的右边插入一个结点。即 L ---> add(0, x)

    在这里插入图片描述

    2.在第 k 个插入的数的右边插入结点
    如果我们要在第 k 个插入的结点的右边插入一个结点的话,由单链表总结出来的结论,我们需要修改第 k 个插入结点的指针情况。在上例中我们要在第 1 个结点右边插入的话,要修改数组下标为 2 的指针指向,因此要执行 add(2,x2),我们抽象化一下:即 IR k x ---> add(k + 1, x)

    在这里插入图片描述
    3.在链表最右端插入结点
    在最右端插入结点时,需要修改尾指针指向的结点的指针域。即 R ---> add(l[1], x)

    在这里插入图片描述
    4.在第 k 个插入的数左边插入结点
    将在左边插入放到最后说明是因为这里有一个巧妙的处理,最好在熟练掌握插入思想后接触,即:在第 k 个结点的左边插入等价位在第 k-1 个结点的右边插入(要求第 k-1 个插入的结点是存在的状态)。同理我们要对 k-1 结点的指针进行修改。所以 IL ---> add(l[k + 1], x)

    在这里插入图片描述

    删除

    双链表的删除操作依旧很简洁。直接看代码

    void remove(int k)
    {
        l[r[k]] = l[k]; // 将 k 结点 右指针指向的结点的左指针 设置为 k 左指针指向的结点
        r[l[k]] = r[k]; // 将 k 结点 左指针指向的结点的右指针 设置为 k 右指针指向的结点
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    若第 k 次插入的结点存储在下标 k + 1,即D k ---> remove(k + 1)

    在这里插入图片描述


    代码(C++)

    #include 
    
    using namespace std;
    
    const int N = 100010;
    int e[N], l[N], r[N], idx;
    
    void init()
    {
        // r[1] l[0] 恒为空
        r[0] = 1; // head 向后指向 tail
        l[1] = 0; // tail 向前指向 head
        idx = 2;
    }
    
    // 都可以看作是在第 k 个结点右边插入一个结点
    void add(int k, int x)
    {
        e[idx] = x; // 存储数据
        l[idx] = k; // ① 当前结点的左指针指向第 k 个结点
        r[idx] = r[k]; // ② 当前结点的右指针设为第 k 个结点的右指针指向
        l[r[k]] = idx; // ③ 第 k 个结点右指针指向结点的左指针设置为当前结点
        r[k] = idx; // ④ 第 k 个结点的右指针指向当前节点
        idx ++;
    }
    
    void remove(int k)
    {
        l[r[k]] = l[k]; // 将 k 结点 右指针指向的结点的左指针 设置为 k 左指针指向的结点
        r[l[k]] = r[k]; // 将 k 结点 左指针指向的结点的右指针 设置为 k 右指针指向的结点
    }
    int main()
    {
        int n;
        cin >> n;
        init();
        
        while (n --)
        {
            string op;
            cin >> op;
            int k, x;
            
            // 看作是在第 0 个插入的结点的右边插入一个结点
            if (op == "L") {
                cin >> x;
                add(0, x);
            }
            
            // 修改尾指针指向结点的指针
            else if (op == "R") {
                cin >> x;
                add(l[1], x);
            } 
            
            //等价于在第 k - 1 个插入结点的右边插入
            else if (op == "IL") {
                cin >> k >> x;
                add(l[k + 1], x);
            }
            
            // 最“标准的”插入操作
            // 第 k 个插入结点的下标存储在 k + 1 的位置
            else if (op == "IR") {
                cin >> k >> x;
                add(k + 1, x);
            }
            
            else if (op == "D"){
                cin >> k;
                remove(k + 1);
            }
        }
        
        // i = r[i] 为不断指向右指针指向的结点
        for (int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

  • 相关阅读:
    Linux——报错合集2
    Vue14 监视属性简写
    C - Bound Found
    关于“网络安全”五点须知!
    【mysql篇-进阶篇】索引
    目标检测算法——YOLOv5/YOLOv7改进之结合SPD-Conv(低分辨率图像和小目标涨点明显)
    初识设计模式 - 单例模式
    k8s 读书笔记 - 详解 Pod 调度(Ⅰ卷)
    FasterViT:英伟达提出分层注意力,构造高吞吐CNN-ViT混合网络 | ICLR 2024
    爬虫过程和反爬
  • 原文地址:https://blog.csdn.net/qq_52127701/article/details/125886759