此次我们主要通过数组来模拟一下单链表,并完成一些基本的功能。
文章目录
前言
此次我们主要通过数组来模拟一下单链表,并完成一些基本的功能。
一、单链表
实现一个单链表,链表初始为空,支持三种操作:
- 向链表头插入一个数;
- 删除第 k 个插入的数后面的一个数;
- 在第 k个插入的数后插入一个数。
现在要对该链表进行 M次操作,进行完所有操作后,从头到尾输出整个链表。
注意:题目中第 k个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n个数依次为:第 1个插入的数,第 2个插入的数,…第 n个插入的数。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
H x
,表示向链表头插入一个数 x。D k
,表示删除第 k 个插入的数后面的数(当 k 为 00 时,表示删除头结点)。I k x
,表示在第 k 个插入的数后面插入一个数 x(此操作中 k 均大于 00)。
输出格式
共一行,将整个链表从头到尾输出。
数据范围
1≤M≤100000
所有操作保证合法。
二、思路模拟
1.引入变量解释
图1.1 数组模拟单链表
我们引入一个整型head,来表示头结点(第一个结点)的下标;引入一维整型数组e,用来存储结点的值;一个整型index,用来表示当前数组e第一个没有存储值的下标(即第一个空结点的下标),或者是我们新存入结点,index就是新存入结点在e数组的下标;用一个一维整型数组ne,用来存储当前结点指向的下一个结点(假设3结点指向5结点)即5结点在数组e中的下标(如图1.1)。
2.链表初始化
head的初始值为-1,此时表示该链表为空,没有存储任何值;
index的初始值为0,表示空链表,即第一个结点就是空的。
//初始化
public static void init(){
head = -1;
index = 0;
}
3.在头结点后插入一个结点
图3.1思路模拟
我们新插入的结点值是x,那么我们先创建一个结点即e[index] = x;然后我们需要将新结点指向头结点原本指向的结点,原本头结点的下标就是head,而ne[index]就是新结点要指向下一个结点的下标,那么我们要进行即ne[index] = head;然后我们要让头指针指向新结点,即head = index,即此时的头结点的下标就是我们新插入结点的下标。最后切记index++,因为我们index表示的是在e数组中第一个空的结点的下标,那么我们用了一个结点,我们就是让index后移一位,让index指向的结点仍是空的。
//将x插入头结点后面
public static void addHead(int x){
e[index] = x;
ne[index] = head;
head = index;
index++;
}
4.表示在第k个数后面插入一个数
图4.1思路模拟
这个操作我们可以转换成在指定下标的结点后面插入一个结点的操作。(注意图中的k是结点的下标)我们将新插入的结点赋值为x即e[index] = x,然后我们需要让新结点指向下标为k的结点指向的下一个结点,此时下标为k的结点指向的下一个结点就是ne[k],那么我们需要进行e[index] = ne[k],此时我们新结点就指向原本下标为k结点所指向的下一个结点。最后我们还要让下标为k的结点指向新结点,下标为k的结点指向的下一个结点的下标为ne[k],那么我们就需要让ne[k] = index,此时我们就完成了将新结点插入整个链表。最后切记让index++,还是为了index指向的结点仍是空的。
//将x插入到下标是k的结点后面
public static void add(int x,int k){
e[index] = x;
ne[index] = ne[k];
ne[k] = index;
index++;
}
5. 把第k个数后面的一个数删除掉
图5.1思路模拟
我们可以转化为删除指定下标结点的后面一个结点。假设我们要删除下标为k的结点的后面一个结点,那么我们需要让结点k指向它的下一个结点指向的下一个结点,结点k指向的下一个结点为ne[k],我们假设这个结点的下标为m,那么m = ne[k]。然后m指向的下一个结点为ne[m]。我们的删除操作就是让结点k直接指向m指向的下一个结点,即ne[k] = ne[m],最后我们可以写成ne[k] = ne[ne[k]]。此时我们就完成了删除操作。
//将下标为k的结点后面的结点删掉
public static void remove(int k){
ne[k] = ne[ne[k]];
}
当我们删除第0个插入的数后面一个数,传入的下标k = 0 - 1发生数组越界,我们需要特殊处理一下 ,即删除头节点,那我们就需要将头指针后移,就是将头结点的索引head,改为头结点的下一个结点即ne[head],那么我们进行head = ne[head]即可。
//删除下标为k-1结点的后一个结点
else if (cmd.equals("D")) {
k = Integer.parseInt(str[1]);
if(k != 0){
remove(k-1);
}
//表示删除头结点,头指针后移
else {
head = ne[head];
}
}
注:当这个结点的下一个结点的索引是-1时,就说明这就是最后一个结点。
三、代码如下
1.代码如下:
import java.io.*;
import java.util.*;
public class 单链表 {
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static int N = 1000010;
//表示第一个结点的下标
static int head;
//存储结点值
static int[] e = new int[N];
//存储下一个结点的下标
static int[]ne = new int[N];
//指针,e数组最新空的元素的下标
static int index;
public static void main(String[] args) {
Scanner sc = new Scanner(br);
init();
int m = Integer.parseInt(sc.nextLine());
while (m-- > 0){
int k,x;
String[] str = sc.nextLine().split(" ");
String cmd = str[0];
//向链表头插入一个结点
if(cmd.equals("H")){
x = Integer.parseInt(str[1]);
addHead(x);
}
//删除下标为k-1结点的后一个结点
else if (cmd.equals("D")) {
k = Integer.parseInt(str[1]);
if(k != 0){
remove(k-1);
}
//表示删除头结点,头指针后移
else {
head = ne[head];
}
}
//在下标为k-1的结点后面插一个结点
else if (cmd.equals("I")) {
k = Integer.parseInt(str[1]);
x = Integer.parseInt(str[2]);
add(k-1,x);
}
}
//打印链表
for(int i = head;i != -1;i = ne[i]){
pw.print(e[i]+" ");
}
pw.flush();
}
//初始化
public static void init(){
head = -1;
index = 0;
}
//将x插入头结点后面
public static void addHead(int x){
e[index] = x;
ne[index] = head;
head = index;
index++;
}
//将x插入到下标是k的结点后面
public static void add(int k,int x){
e[index] = x;
ne[index] = ne[k];
ne[k] = index;
index++;
}
//将下标为k的结点后面的结点删掉
public static void remove(int k){
ne[k] = ne[ne[k]];
}
}
2.读入数据
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
3.代码运行结果
6 4 6 5
总结
上午主要通过数组来模拟单链表,关键通过对各个变量知道什么意思,明白增删操作是怎样进行的,看一下图示和文字描述加强一下理解。