【题目来源】
https://www.acwing.com/problem/content/828/
【题目描述】
实现一个单链表,链表初始为空,支持三种操作:
- 向链表头插入一个数;
- 删除第 k 个插入的数后面的数;
- 在第 k 个插入的数后插入一个数。
现在要对该链表进行 M 次操作,进行完所有操作后,从头到尾输出整个链表。
注意:++题目中第 k 个插入的数并不是指当前链表的第 k 个数++ 。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,...第 n 个插入的数。
【输入格式】
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
- H x,表示向链表头插入一个数 x。
- D k,表示删除第 k 个插入的数后面的数(当 k 为 0 时,表示删除头结点)。
- I k x,表示在第 k 个插入的数后面插入一个数 x(此操作中 k 均大于 0)。
【输出格式】
共一行,将整个链表从头到尾输出。
【数据范围】
1≤M≤100000
所有操作保证合法。
【输入样例】
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6
【输出样例】
6 4 6 5
【算法分析】
本题利用数组模拟实现单链表。利用数组模拟单链表常见的核心操作代码如下所示:
cpp
// head 头结点的下标
// e[i] 结点i的值
// ne[i] 结点i的next指针
// idx 当前可用结点的下标,可看做指针
int head, e[N], ne[N], idx;
void init() { //初始化
head=-1; //head指向空
idx=0;
}
void add_to_head(int x) { //将x插入到首元结点
e[idx]=x;
ne[idx]=head; // idx指向head指向的结点
head=idx++; // head指向idx
}
void add(int k,int x) { //将x插到到下标为k的结点后面
e[idx]=x;
ne[idx]=ne[k];
ne[k]=idx++;
}
void remove(int k) { //将下标为K后面的点删除
ne[k]=ne[ne[k]];
}
学习过链式前向星(https://blog.csdn.net/hnjzsyjyj/article/details/126474608)的会发现这些模拟单链表的变量及数组很熟悉。这是因为,链式前向星也是用数组模拟单链表,不过是要模拟多个单链表。链式前向星常见的核心操作代码如下所示:
其中:
val[idx] 表示第 idx 条边的权值。
e[idx] 表示第 idx 条边的终点。
ne[idx] 表示与第 idx 条边同起点的最近一次被添加的边的编号。
h[a] 表示以结点 a 为起点的最近一次被添加的边的编号。这个表述是在使用 ne[idx]=h[a] 时,也即使用 h[a]=idx++ 更新 h[a] 之前而言的。要特别注意这个语境。
很明显,h[a] 中存储的是以 a 为起点的所有边中编号最大的那个。且在遍历时,把这条边作为以 a 为起点的所有边中的第一条边进行遍历。即边的遍历顺序与输入顺序恰好是相反的。而网上常见的表述"e[idx] 表示第 idx 条边的终点,ne[idx] 表示与第 idx 条边同起点的**++下一条边++** 的存储位置,h[a] 表示以 a 为起点的++第一条边++的存储位置",是按边的遍历顺序进行表述的。
在上述约定下,则有:
● 链式前向星的核心代码如下:
cpp
void add(int a,int b) {
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
如果是有权图,需多设置一个数组 val[] 存储权值。有权图的链式前向星的核心代码如下:
cpp
void add(int a,int b,int w) {
val[idx]=w,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
● 基于链式前向星的深度优先搜索(DFS)的核心代码如下:
cpp
void dfs(int u) {
cout<<u<<" ";
st[u]=true;
for(int i=h[u]; ~i; i=ne[i]) { //~i; equivalent to i!=-1;
int j=e[i];
if(!st[j]) {
dfs(j);
}
}
}
● 基于链式前向星的广度优先搜索(BFS)的核心代码如下:
cpp
void bfs(int u) {
queue<int>q;
st[u]=true;
q.push(u);
while(!q.empty()) {
int t=q.front();
q.pop();
cout<<t<<" ";
for(int i=h[t]; ~i; i=ne[i]) { //~i; equivalent to i!=-1;
int j=e[i];
if(!st[j]) {
q.push(j);
st[j]=true; //need to be flagged immediately after being queued
}
}
}
}
【算法代码】
cpp
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
// head 头结点的下标
// e[i] 结点i的值
// ne[i] 结点i的next指针
// idx 当前可用结点的下标,可看做指针
int head, e[N], ne[N], idx;
void init() { //初始化
head=-1; //head指向空
idx=0;
}
void add_to_head(int x) { //将x插入到首元结点
e[idx]=x;
ne[idx]=head; // idx指向head指向的结点
head=idx++; // head指向idx
}
void add(int k,int x) { //将x插到到下标为k的结点后面
e[idx]=x;
ne[idx]=ne[k];
ne[k]=idx++;
}
void remove(int k) { //将下标为K后面的点删除
ne[k]=ne[ne[k]];
}
int main() {
int T;
cin>>T;
init();
while(T--) {
int k,x;
char op;
cin>>op;
if(op=='H') {
cin>>x;
add_to_head(x);
} else if(op=='D') {
cin>>k;
if(!k) head=ne[head]; //对删除头结点特判
remove(k-1);
} else {
cin>>k>>x;
add(k-1,x);
}
}
for(int i=head; i!=-1; i=ne[i]) cout<<e[i]<<" ";
return 0;
}
/*
in:
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6
out:
6 4 6 5
*/
【参考文献】
https://www.acwing.com/blog/content/2141/
https://blog.csdn.net/hnjzsyjyj/article/details/126474608