双链表是一种常见的链表数据结构,它具有每个节点除了存储数据外,还保存指向前一个节点和后一个节点的指针。这种结构使得双链表具有灵活的插入和删除操作,能够在 O(1) 时间复杂度内完成。
算法原理:
-
节点结构:每个节点包含两个指针,一个指向前一个节点,一个指向后一个节点,以及一个数据域,用来存储节点数据。
-
头节点和尾节点:双链表通常有一个头节点和一个尾节点,它们的指针分别指向链表的第一个节点和最后一个节点,这样可以便于在链表两端进行插入和删除操作。
操作步骤:
-
初始化:创建一个空的双链表,初始化头节点和尾节点,使它们的指针均为空。
-
节点插入:
- 在链表的特定位置插入一个新节点时,需要修改该位置前后节点的指针,将新节点正确连接到链表中。
- 插入操作包括更新当前节点的前后指针以及更新前后节点的指针,确保链表的连续性。
-
节点删除:
- 从双链表中删除一个节点时,需要修改待删除节点前后节点的指针,将它们正确连接起来,从而将待删除节点从链表中移除。
-
遍历:
- 从头节点或尾节点开始,根据节点的后继指针或前驱指针依次访问链表中的每个节点,以完成遍历操作。
双链表的特点是可以在任意位置高效地插入和删除节点,同时支持正向和反向遍历。这使得双链表在需要频繁插入和删除操作的场景中非常有用。
问题:
要实现双链表,需支持以下五种操作:
- 在双链表的最左侧插入一个数。
- 在双链表的最右侧插入一个数。
- 删除第 k 个插入的数。
- 在第 k 个插入的数左侧插入一个数。
- 在第 k 个插入的数右侧插入一个数。
在执行 M 次操作后,从左到右输出整个链表。
请注意,题目中提到的第 k 个插入的数并不是指当前链表的第 k 个数。例如,如果操作过程中一共插入了 n 个数,那么按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,...,第 n 个插入的数。
输入格式为:
- 第一行包含一个整数 M,表示操作次数。
- 接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
- L x:在链表的最左端插入数 x。
- R x:在链表的最右端插入数 x。
- D k:将第 k 个插入的数删除。
- IL k x:在第 k 个插入的数左侧插入一个数 x。
- IR k x:在第 k 个插入的数右侧插入一个数 x。
输入样例:
10
R 7
D 1
L 3
IL 2 10
D 3
IL 2 7
L 8
R 9
IL 4 7
IR 2 2
输出样例:
8 7 7 3 2 9
代码:
#include<iostream>
using namespace std;
const int N = 100010;
int value[N], l[N], r[N], idx;
// 初始化双链表
void init(){
r[0] = 1, l[1] = 0; // 设置双链表的初始状态,0是左端哨兵,1是右端哨兵
idx = 2; // 索引从2开始,因为0和1被用作哨兵
}
// 在第 k 个插入的数右侧插入一个数 x
void insert(int k, int x){
value[idx] = x;
l[idx] = k;
r[idx] = r[k];
l[r[k]] = idx;
r[k] = idx++; // 插入后,k右侧的数就是新的数x
}
// 删除第 k 个插入的数
void remove(int k){
r[l[k]] = r[k];
l[r[k]] = l[k]; // 删除k节点后,调整前后节点的指向
}
int main(){
int m;
cin >> m;
init(); // 初始化双链表
while(m--){
int k, x;
string op;
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;
remove(k + 1); // 第 k 个插入的数实际索引是 k + 1
}
else if(op == "IL"){ // 在第 k 个插入的数左侧插入一个数
cin >> k >> x;
insert(l[k + 1], x);
}
else { // 在第 k 个插入的数右侧插入一个数
cin >> k >> x;
insert(k + 1, x);
}
}
// 从左到右输出整个链表
for(int i = r[0]; i != 1; i = r[i])
cout << value[i] << ' ';
return 0;
}
双链表是一种数据结构,相较于单链表,每个节点不仅保存了指向下一个节点的指针,还保存了指向前一个节点的指针。这使得双链表在某些情况下更加灵活,能够更方便地进行前向和后向遍历。以下是双链表的总结:
特点:
- 前后指针: 每个节点包含两个指针,分别指向前一个节点和后一个节点。
- 哨兵节点: 通常在双链表的两端设置哨兵节点,简化边界条件的处理。
基本操作:
- 插入操作: 在指定位置的节点前或后插入新节点,通过调整前后节点的指针完成插入。
- 删除操作: 删除指定位置的节点,同样通过调整前后节点的指针完成删除。
- 初始化: 设置哨兵节点,初始化前后指针。
应用场景:
- 前后遍历: 可以方便地实现从前向后或从后向前遍历链表。
- 插入和删除效率: 在涉及频繁插入和删除操作的场景中,双链表比单链表更高效。
代码实现注意事项:
- 初始化: 在初始化时设置哨兵节点,确保链表的边界条件被妥善处理。
- 索引转换: 在实际应用中,第 k 个插入的数对应的节点索引可能需要加一或减一,具体取决于索引从 0 还是 1 开始。
总体而言,双链表在某些场景下具有优势,但在内存占用上相对于单链表会更多。根据具体应用需求,选择合适的数据结构以提高操作效率。