zoukankan      html  css  js  c++  java
  • 数组实现双链表

    之前写了数组实现单链表,提到了数组实现链表比指针实现最大的优点就是快,可以随机存取,而且不用new节点。
    在图论的题目里用到邻接表,往往都是用数组实现。

    数组实现双链表比单链表就多了一些对于左指针的操作。
    为了实现的方便,不像在单链表实现里用一个额外的变量head去记录链表的头节点。
    而是直接用两个哨兵节点固定为双链表的头节点和尾节点。
    我们可以固定头节点为0,尾节点为1.

    也就是说,0的right指针指向链表的第一个节点,1的left指针指向链表的第一个节点。
    这样,0和1就被固定为头节点和尾节点了。我们如果要插入节点,初始下标就得从2开始。

    类比单链表的数组实现,要实现双链表,就得开三个数组vallr
    val[i]表示下标为i的节点的值,l[i]表示下标为i的节点的前驱节点,r[i]表示下标为i的节点的后继结点。

    另外还需要有一个变量idx记录当前插入的节点的下标。由于0和1已经被占用了,所以idx从2开始。

    双链表具体的操作,可以根据题目来确定。来看一道题:


    原题链接在这

    这道题的题意是要我们实现一个双链表,双链表支持五个操作:
    (1)在链表的第一个节点之前(最左侧)插入一个数
    (2)在链表的最后一个节点之后(最右侧)插入一个数
    (3)删除第k个插入的数
    (4)在第k个插入的数左侧插入一个数
    (5)在第k个插入的数右侧插入一个数

    实现操作之前,先初始化双链表。

    void init() {
          r[0] = 1;
          l[1] = 0;
          idx = 2;
    }
    

    这三行代码的作用是初始化一个空的双链表,头节点0的right指针指向尾节点1,尾节点1的left指针指向头节点0,要插入的数的下标初始化为2(因为0,1已经被占用了)。

    再看其他操作,看起来要实现很多操作,实际上这五个操作可以分为两类:插入和删除。

    五个操作中有四个是插入操作,所以可以在一个函数里实现。

    比如要在下标为k的点右侧插入一个数x。
    首先需要创建一个含有值x的节点:val[idx] = x;
    然后这个点的右指针要指向下标为k的点的右指针指向的节点,这个点的左指针要指向下标为k的点:

    r[idx] = r[k];   //r[k]表示下标为k的节点的后继结点。 注意:下标为k的节点,不是第k个插入的节点,而是第k - 1个插入的节点,因为idx是从2开始的,所以第一个插入的节点下标为2...第k个插入的节点的下标为k + 1 !
    l[idx] = k;      //新插入的节点的左指针指向下标k
    

    然后还要修改原来的两个节点,下标为k的节点的右指针指向这个新插入的节点,还有原来是下标为k的节点的下一个节点的左指针要指向新插入的节点。

    l[r[k]] = idx; 
    r[k] = idx;
    

    上面两行代码的顺序不能反,否则如果提前修改了r[k],那么未插入新节点前下标为k的节点的下一个节点就找不到了,更没办法修改它的左指针。
    修改了指针之后,下标idx要增加,以便之后插入新的节点。

    ++idx;
    

    这样,我们就得到了在下标为k的节点之后插入x的函数:

    void Insert(int k, int x) {
          val[idx] = x;
          r[idx] = r[k];
          l[idx] = k;
          l[r[k]] = idx;
          r[k] = idx;
          ++idx;
    }
    

    这是在下标为k的节点的右边插入x,题目还要求在左边插入。
    实际上在左边插入,可以直接调用Insert(l[k], x)
    也就是在下标为k的节点的前驱节点的右边插入。
    这样就不用再写一个函数了。

    这样还剩下两个插入操作,在最左侧插入,和在最右侧插入。

    实际上,由于固定了两个哨兵节点0和1.
    0的右指针指向第一个节点,所以要在最左侧插入,可以直接调用Insert(0, x)
    1的左指针指向最后一个节点,所以要在最右侧插入,可以直接调用Insert(l[1], x)

    所以实际上一个Insert函数就解决了题目要求的四个插入操作。

    这样就只剩下一个删除操作了。
    要删除下标为k的节点,只需要让它的前驱节点的右指针指向它的后继结点,它的后继结点的左指针指向它的前驱节点。

    void Delete(int k) {
          r[l[k]] = r[k];
          l[r[k]] = l[k];
    }
    

    下面是完整代码:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 1e5 + 5;
    int val[N], l[N], r[N], idx;
    
    void init() {
        r[0] = 1;
        l[1] = 0;
        idx = 2;
    }
    
    void Insert(int k, int x) {
        val[idx] = x;
        r[idx] = r[k];
        l[idx] = k;
        l[r[k]] = idx;
        r[k] = idx;
        ++idx;
    }
    
    void Delete(int k) {
        r[l[k]] = r[k];
        l[r[k]] = l[k];
    }
    
    int main() {
        int M;
        cin >> M;
        init();
        while(M--) {
            string op;            //字符串op表示输入的操作
            int k, x;
            cin >> op;
            if(op == "L") {
                cin >> x;
                Insert(0, x);
            } else if(op == "R") {
                cin >> x;
                Insert(l[1], x);
            } else if(op == "D") {      //这里要注意,输入的k是下标
                cin >> k;               //但是要删除第k个数,实际上是删除下标为k+1的数,因为idx从2开始,第一个数下标为2。。。。第k个数下标为k+1
                Delete(k + 1);          //下面IL和IR同理,第k个数的下标都是k+1
            } else if(op == "IL") {
                cin >> k >> x;
                Insert(l[k + 1], x);
            } else if(op == "IR") {
                cin >> k >> x;
                Insert(k + 1, x);
            }
        }
        for(int i = r[0]; i != 1; i = r[i]) {     //链表第一个数是0的右指针指向的节点
            cout << val[i] << ' ';
        }
        cout << endl;
    }
    
  • 相关阅读:
    mysql保存中文乱码的原因和解决办法
    NetSetMan IP地址切换工具
    使用批处理文件,自动设置计算机IP地址
    神逸之作:国产快速启动软件神品ALTRun
    Apache详细介绍
    利用sqoop对mysql执行DML操作
    Mysql定时清空表
    azkaban group分组,权限
    azkaban使用
    sqoop无法导出parquet文件到mysql
  • 原文地址:https://www.cnblogs.com/linrj/p/13322114.html
Copyright © 2011-2022 走看看