链表,栈与队列的简单讲解
单链表
单链表是一种常见的数据结构,其包含自身所存储的值和指向下一个节点的指针如果该链表没有下一个节点,则指向null。链表的实现分为静态链表与动态链表,其中静态链表每个节点使用数组实现,动态链表的节点使用不连续的块内存(结构体或类)来实现。
使用数组实现单链表数据结构
单链表一般有三种操作,从头插入,从中间插入,删除操作
AcWing 826单链表
实现一个单链表,链表初始为空,支持三种操作:
(1) 向链表头插入一个数;
(2) 删除第k个插入的数后面的数;
(3) 在第k个插入的数后插入一个数
现在要对该链表进行M次操作,进行完所有操作后,从头到尾输出整个链表。
注意:题目中第k个插入的数并不是指当前链表的第k个数。例如操作过程中一共插入了n个数,则按照插入的时间顺序,这n个数依次为:第1个插入的数,第2个插入的数,…第n个插入的数。
import java.util.*;
class Linklist{
private final int N = 100010;
private int[] v = new int[N];
private int[] np = new int[N];
//head表示头节点,若head为-1,则表示链表为空
//idn表示当先可以使用的v数组的下标。
private int head = -1,idn = 0;
public void add_head(int x){
v[idn] = x;
np[idn] = head;
head = idn;
idn++;
}
public void add(int index,int x){
v[idn] = x;
np[idn] = np[index];
np[index] = idn;
idn++;
}
public void remove(int index){
if(index == 0) head = np[head];
else {
index--;
np[index] = np[np[index]];
}
}
@Override
public String toString(){
int i = head;
StringBuilder sb = new StringBuilder();
while(i != -1){
sb.append(v[i]);
sb.append(" ");
i = np[i];
}
return sb.toString();
}
}
public class Main{
private static Linklist list = new Linklist();
public static void main(String[] args)throws IOException{
Scanner Reader = new Scanner();
int m = Reader.nextInt();
while(m-- != 0){
char c = Reader.next().charAt(0);
if(c == 'H'){
int n = Reader.nextInt();
list.add_head(n);
}
if(c == 'D'){
int n = Reader.nextInt();
list.remove(n);
}
if(c == 'I'){
int index =Reader.nextInt(),value = Reader.nextInt();
list.add(index-1,value);
}
}
System.out.print(list.toString());
}
}
双链表
双链表与单链表相似,每个节点除了自身所存储的值和指向下一个节点的指针之外,每个节点还多了一个指向上一个节点的指针。
使用数组实现双链表数据结构
AcWing 827.双链表
实现一个双链表,双链表初始为空,支持5种操作:
(1) 在最左侧插入一个数;
(2) 在最右侧插入一个数;
(3) 将第k个插入的数删除;
(4) 在第k个插入的数左侧插入一个数;
(5) 在第k个插入的数右侧插入一个数
现在要对该链表进行M次操作,进行完所有操作后,从左到右输出整个链表。
注意:题目中第k个插入的数并不是指当前链表的第k个数。例如操作过程中一共插入了n个数,则按照插入的时间顺序,这n个数依次为:第1个插入的数,第2个插入的数,…第n个插入的数。
import java.util.*;
class Linklist{
public int[] V,L,R;
private int idx;
public Linklist(){
V = new int[100010];
L = new int[100010];
R = new int[100010];
idx = 2;
R[0] = 1;
L[1] = 0;
//V数组表示每个节点所存储的值,L数组表示每个节点指向的上一个节点,
//R数组表示每个节点指向的下一个节点
//idx指当前使到了哪一个节点假设0节点为头节点,1节点为尾节点。
}
//添加节点操作,在坐标index的右侧插入一个节点。
//如果我们想在坐标index的左侧插入一个节点,可以转换为
//在坐标index的左节点的右侧插入一个节点,所以可以使用一个方法。
public void add(int index, int value){
V[idx] = value;
R[idx] = R[index];
L[idx] = L[index];
L[R[index]] = idx;
R[index] = idx;
idx++;
}
//删除节点
public void delete(int index){
R[L[index]] = R[index];
L[R[index]] = L[index];
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
int i = R[0];
while(i!=1){
sb.append(V[i]);
sb.append(" ");
i = R[i];
}
return sb.toString();
}
}
public class Main{
public static void main(String[] args){
Linklist list = new Linklist();
Scanner Reader = new Scanner(System.in);
int m = Reader.nextInt();
while(m-- != 0){
String temp = Reader.next();
if(temp.equals("L")){
int val = Reader.nextInt();
list.add(0,val);
}
if(temp.equals("R")){
int val = Reader.nextInt();
list.add(list.L[1],val);
}
if(temp.equals("D")){
int index = Reader.nextInt();
list.delete(index+1);
}
if(temp.equals("IR")){
int index = Reader.nextInt();
int val = Reader.nextInt();
list.add(index+1,val);
}
if(temp.equals("IL")){
int index = Reader.nextInt();
int val = Reader.nextInt();
list.add(list.L[index+1],val);
}
}
System.out.print(list);
}
}
栈
栈是一种先进后出的数据结构,栈的实现非常简单,只需要一个数组和一个指针即可,而且没有内存泄漏。
使用数组实现栈
import java.util.*;
public class Main{
public static void main(String[] args){
int[] st = new int[100010];
int tt = 0;
Scanner Reader = new Scanner(System.in);
int k = Reader.nextInt();
while(k-- != 0){
String temp = Reader.next();
if(temp.equals("push")){
int val = Reader.nextInt();
st[++tt] = val;
}
if(temp.equals("pop")){
int v = st[tt--];
}
if(temp.equals("empty")){
if(tt == 0) System.out.println("YES");
else System.out.println("NO");
}
if(temp.equals("query")){
System.out.println(st[tt]);
}
}
}
}
单调栈的应用
单调栈是一种特殊的栈,单调栈是指栈中所存储的元素具有单调性,一般用于在一个无序列表中寻找第i个数左边或者右边第一个比他小或大的数。
AcWing 830 给定一个长度为N的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出-1。
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner Reader = new Scanner(System.in);
int[] st = new int[100010];
int tt = 0;
int m = Reader.nextInt();
while(m-- != 0){
int temp = Reader.nextInt();
//如果栈不空,并且栈顶的值大于要入栈的值,就把栈顶的值弹出来
//这么做的原因是该值在temp的左边,并且比temp大,所以肯定不能满足答案,所以把他弹出
while(tt != 0 && st[tt] >= temp){
tt--;
}
if(tt == 0) System.out.print(-1 + " ");
else System.out.print(st[tt]+" ");
//在满足以上条件后,栈顶的值满足条件的值,如果栈为空,那么说明没有满足的值。
st[++tt] = temp;
}
}
}
队列
队列是一种先进先出的数据结构,队列的实现需要两个指针,分别是指向队列的头和队列的尾。
利用数组实现队列
public class Main{
public static void main(String[] args)throws IOException{
int[] que = new int[100010];
int h=0,t=0;
Scanner Reader = new Scanner(System.in);
int m = Reader.nextInt();
while(m-- != 0){
String token = Reader.next();
if(token.equals("pop")){
++t;
}
if(token.equals("push")){
que[++h] = Reader.nextInt();
}
if(token.equals("query")){
System.out.println(que[t+1]);
}
if(token.equals("empty")){
if(t == h){
System.out.println("YES");
}else{
System.out.println("NO");
}
}
}
}
}
单调队列
public class Main{
public static void main(String[] args)throws IOException{
int que[] = new int[1000010];
int a[] = new int[1000010];
int h=0,t=0;
StringBuilder sb = new StringBuilder();
Reader.init(System.in);
int n = Reader.nextInt();
int k = Reader.nextInt();
for(int i = 0; i < n; i++){
a[i] = Reader.nextInt();
}
for(int i = 0; i < n; i++){
//控制窗口的长度
if(h > t && que[t+1] < i-k+1) t++;
//控制队列的单调性,这里比较难懂,需要仔细思考一下。
//他是通过下标来维护队列的长度,队列中保存的是原序列中的下标值,不是序列中的值。
while(h > t && a[que[h]] >= a[i])h--;
que[++h] = i;
if(i >= k-1) sb.append(a[que[t+1]] + " ");
}
sb.append("\n");
h=0;t=0;
for(int i = 0; i < n; i++){
if(h > t && que[t+1] < i-k+1) t++;
while(h > t && a[que[h]] <= a[i])h--;
que[++h] = i;
if(i >= k-1) sb.append(a[que[t+1]] + " ");
}
System.out.print(sb.toString());
}
}