单链表
题目
实现一个单链表,链表初始为空,支持三种操作:
- 向链表头插入一个数;
- 删除第 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
第 k 个插入的元素在哪里?
在本道题目中,有两个问题:
删除第 k 个插入的数后面的数
在第 k 个插入的数后插入一个数
先解释一下什么叫第k个插入的元素
:
- 把插入操作按先后排序,第 k 次执行插入操作的那个元素。
- 并不是链表中从前往后数第 k 个元素。
在链表中删除指针指向的元素的后一个元素,或者在指针指向的某个元素后面插入一个新元素是很容易实现的。
所以,只要弄明白第 k 个插入的数
的指针在哪里,这两个问题就很容易解决。
来分析一下插入操作:
- 链表为空的时候:idx 的值为 0,
- 插入第一个元素 a 后:e[0] = a, idx 的值变为 1,
- 插入第二个元素 b 后:e[1] = b, idx 的值变为 2,
- 插入第三个元素 c 后:e[2] = c, idx 的值变为 3,
所以: 第 k 个出入元素的索引值 k - 1。
有人会说,如果中间删除了某些元素呢?
在看一下伴随着删除操作的插入:
- 链表为空的时候:idx 的值为 0,
- 插入第一个元素 a 后:e[0] = a, idx 的值变为 1,
- 插入第二个元素 b 后:e[1] = b, idx 的值变为 2,
- 删除第一个插入的元素 a:head 变为 1, idx 不变,依旧为 2。
- 删除第二个插入的元素 b:head 变为 2, idx 不变,依旧为 2。
- 插入第三个元素 c 后:e[2] = c, idx 的值变为 3。
所以删除操作并不改变第 k 个插入元素的索引。
故第 k
个元素的索引一定是 k - 1
。
题解:
head 表示头结点,e数组存储元素,ne数组存储下一个节点索引,indx表示下一个可以存储元素的位置索引。
头结点后面添加元素:
-
在e的idx处存储元素e[ide] = x;
-
该元素插入到头结点后面 ne[idx] = head;
-
头结点指向该元素 head = idx;
-
idx 指向下一个可存储元素的位置 idx++。
在索引 k 后插入一个数
-
在e的idx处存储元素e[index] = x
-
该元素插入到第k个插入的数后面 ne[idx] = ne[k];
-
第k个插入的数指向该元素 ne[k] = idx;
-
idx 指向下一个可存储元素的位置 idx++。
删索引为 k 的元素的后一个元素:
- ne[k] 的值更新为 ne[ne[k]]
画个图
以题目为例:
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
最终结果如下:
代码
#include <iostream>
using namespace std;
const int N = 100010;
int head, e[N], ne[N], idx;
void init()
{
head = -1;
idx = 0;
}
void add_to_head(int x)
{
e[idx] = x, ne[idx] = head, head = idx++;
}
void add(int k, int x)
{
e[idx] = x, ne[idx] = ne[k], ne[k] = idx++;
}
void remove(int k)
{
ne[k] = ne[ne[k]];
}
int main()
{
int m;
cin >> m;
init();
while(m--)
{
char op;
int k, x;
cin >> op;
if(op == 'H')
{
cin >> x;
add_to_head(x);
}
else if(op == 'D')
{
cin >> k;
if(!k) head = ne[head];
else remove(k - 1);//第k个元素对应的索引为k - 1
}
else
{
cin >> k >> x;
add(k - 1, x);//第k个元素对应的索引为k - 1
}
}
for(int i = head; i != -1; i = ne[i]) cout << e[i] << " ";
cout<< endl;
}
Java
import java.util.Scanner;
public class Main{
static int[] e = new int[100010];
static int[] ne = new int[100010];
static int idx,head;
public static void init(){
idx = 0;
head = -1;
}
//H向链表头插入一个数x;
public static void add_head(int x){
e[idx] = x;
ne[idx] = head;
head = idx++;
}
//I在第k位数后面插入一个数x
public static void add(int k,int x){
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx++;
}
//D删除第k个数后面得数
public static void remove(int k){
ne[k] = ne[ne[k]];
}
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
int m = scan.nextInt();
init();
while(m -- > 0){
//因为java中没有输入一个字符,所以用字符串转字符
String s = scan.next();
char op = s.charAt(0);
if(op == 'H'){
int x = scan.nextInt();
add_head(x);
}else if(op == 'D'){
int k = scan.nextInt();
if(k == 0) head = ne[head];
else remove(k-1);
}else {
int k = scan.nextInt();
int x = scan.nextInt();
add(k-1,x);
}
}
for(int i = head;i != -1;i = ne[i] ){
System.out.print(e[i] + " ");
}
}
}
为什么不用课本上学的结构体来构造链表??
学过数据结构课的人,对链表的第一反应就是:
链表由节点构成,每个节点保存了 值 和 下一个元素的位置 这两个信息。节点的表示形式如下:
class Node{
public:
int val;
Node* next;
};
这样构造出链表节点的是一个好方法,也是许多人一开始就学到的。
使用这种方法,在创建一个值为 x 新节点的时候,语法是:
Node* node = new Node();
node->val = x
看一下创建新节点的代码的第一行:
Node* node = new Node();
,中间有一个 new
关键字来为新对象分配空间。
new
的底层涉及内存分配,调用构造函数,指针转换等多种复杂且费时的操作。一秒大概能new
1w次左右。
在平时的工程代码中,不会涉及上万次的new
操作,所以这种结构是一种 见代码知意 的好结构。
但是在算法比赛中,经常碰到操作在10w级别的链表操作,如果使用结构体这种操作,是无法在算法规定时间完成的。
所以,在算法比赛这种有严格的时间要求的环境中,不能频繁使用new操作。也就不能使用结构体来实现数组。
双链表
双链表原理同单链表,只是多实现了左右两端添加节点
实现一个双链表,双链表初始为空,支持 5 种操作:
- 在最左侧插入一个数;
- 在最右侧插入一个数;
- 将第 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 个插入的数左侧插入一个数。
- IR k x,表示在第 k 个插入的数右侧插入一个数。
输出格式
共一行,将整个链表从左到右输出。数据范围
1 ≤ M ≤ 100000所有操作保证合法。
输入样例:
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 e[N], l[N], r[N],index = 0;
void init()
{
//如下初始化导致第 a 个插入的元素的索引为: a + 1
l[1] = 0;
r[0] = 1;
index = 2;
}
// 在索引为 a 的右侧插入x,对应第 a - 1 个插入元素右侧插入 x
void insert(int a, int x)
{
e[index] = x;
l[index] = a;
r[index] = r[a];
l[r[a]] = index;
r[a] = index++;
}
//删除索引为 a 的元素,对应于删除第 a - 1 个插入元素。
void remove(int a)
{
r[l[a]] = r[a];
l[r[a]] = l[a];
}
int main()
{
init();
int m;
cin>>m;
while(m--)
{
string op;
cin >> op;
int k, x;
if(op == "L")
{
cin >> x;
insert(0, x);
}
else if(op == "R")
{
cin >> x;
insert(l[1], x);
}
else if (op == "D")
{
cin >> k;
remove(k + 1);//第 k 个插入元素对应的索引为 k + 1
}
else if (op == "IL")
{
cin >> k >> x;
insert(l[k + 1], x);// 第 k 个插入元素对应的索引为 k + 1, l[k + 1] 为链表中上一个位置对应的索引
}
else
{
cin >> k >> x;
insert(k + 1, x);//第 k 个插入元素对应的索引为 k + 1
}
}
for (int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
cout << endl;
return 0;
}
Java
import java.util.Scanner;
public class Main{
static int N = 100010;
static int[] e = new int[N];
static int[] r = new int[N];
static int[] l = new int[N];
static int idx;
//删除第k位插入的数
public static void remove(int k){
r[l[k]] = r[k];
l[r[k]] = l[k];
}
//这是在第k位数后面插入一个数x
public static void add_all(int k,int x){
e[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;
r[k] = idx++;
}
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
int m = scan.nextInt();
r[0] = 1;l[1] = 0;idx = 2;
while(m -- > 0){
String s = scan.next();
char op = s.charAt(0);
if(op == 'L'){
int x = scan.nextInt();
add_all(0,x);
}else if(op == 'R'){
int x = scan.nextInt();
add_all(l[1],x);
}else if(op == 'D'){
int k = scan.nextInt();
remove(k + 1);
}else if(s.equals("IR")){
int k = scan.nextInt();
int x = scan.nextInt();
add_all(k + 1,x);
}else {
int k = scan.nextInt();
int x = scan.nextInt();
add_all(l[k+1],x);
}
}
for(int i = r[0];i != 1; i= r[i]){
System.out.print(e[i] + " ");
}
}
}