数组实现双链表
之前写了数组实现单链表,提到了数组实现链表比指针实现最大的优点就是快,可以随机存取,而且不用new节点。
在图论的题目里用到邻接表,往往都是用数组实现。
数组实现双链表比单链表就多了一些对于左指针的操作。
为了实现的方便,不像在单链表实现里用一个额外的变量head
去记录链表的头节点。
而是直接用两个哨兵节点固定为双链表的头节点和尾节点。
我们可以固定头节点为0,尾节点为1.
也就是说,0的right指针
指向链表的第一个节点,1的left指针
指向链表的第一个节点。
这样,0和1就被固定为头节点和尾节点了。我们如果要插入节点,初始下标就得从2开始。
类比单链表的数组实现,要实现双链表,就得开三个数组val
,l
,r
。
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;
}