数组实现双链表

之前写了数组实现单链表,提到了数组实现链表比指针实现最大的优点就是快,可以随机存取,而且不用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;
}
posted @ 2020-07-16 13:11  machine_gun_lin  阅读(415)  评论(1编辑  收藏  举报