Acwing算法基础java笔记之第一章:数据结构(1)

目的:只是为了帮助一些上课的同学整理笔记,有什么错误也欢迎指出。

课程详情:活动 - AcWing 

目录

1.单链表

2.双链表

3.栈 & 队列

单调栈

单调队列


1.单链表

头插法思路
​​​​​​(尾插和中间插入同理)

package 链表;

public class 单链表 {
	static int N = 100010;
	//head表示头结点的下标(非固定)
	//e[i]表示节点i的值
	//ne[i]表示节点i的next指针是多少
	//idx 存储当前已经用到了哪个点
	static int head,idx;
	static int[] e = new int[N];
	static int[] ne = new int[N];
	public static void main(String[] args) {
		
	}
	
	public void init() {
		head = -1;
		idx = 0;
	}
	
	//头插法:第一个插入的点的下标是0
	public void add_head(int x) {
		e[idx] = x; //存储节点值
		ne[idx] = head; //将插入的节点与下一个节点连接(下一个节点是head)
		head = idx; //更新head值
		idx++;  
	}
	
	//在单链表中间 将b插入到下标是a的点后面
	public void ad(int a, int b) {  
		e[idx] = b;
		ne[idx] = ne[a];
		ne[a] = idx;
		idx++;
	}
	
	//将下标是a的点后面的b删除
	public void remove(int a, int b) {
		ne[a] = ne[b];
	}
	//将下标为k的点后面的点删除
	public void remove1(int k) {
		ne[k] = ne[ne[k]];
	}
}
案例:

package 杂;

import java.util.Scanner;
/*
 * 由于该中的计数方式是使用插入的顺序,所以需要一个数组来记录数的顺序,而e刚好可以记录,但是注意的是e是从0开始的
 */

public class test {
    static int N = 100010;
    static int[] e = new int[N];
    static int[] ne = new int[N];
    //初始化
    static int head = -1, idx = 0;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int M = sc.nextInt();
        while (M-- > 0) {
            String lable = sc.next();
            switch(lable) {
                case "H" :
                    int x = sc.nextInt();
                    add_head(x);
                    break;
                case "D" :
                    int k = sc.nextInt();
                    remove(k);
                    break;
                case "I":
                    int a = sc.nextInt();
                    int b = sc.nextInt();
                    add(a, b);
                    break;
            }
        }
        sc.close();
        //遍历输出
        for (int i = head; i != -1; i = ne[i]) {
            System.out.print(e[i] + " ");
        }
    }

    private static void add(int a, int b) {
		a -= 1;
        e[idx] = b;
        ne[idx] = ne[a];
        ne[a] = idx;
        idx++;
    }

    private static void remove(int k) {
        k -= 1;
        if (k == 0){
            head = ne[head];
        }else if (k == -1){
            ne[head] = -1;
            head = -1;
        }else{
            ne[k] = ne[ne[k]];
        }
    }

    private static void add_head(int x) {
        e[idx] = x;
        ne[idx] = head; 
        head = idx;
        idx++;
    }
}

//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

2.双链表

与单链表思路相似,只是每个结点从原本的存储节点值和下一位节点地址,修改为“该节点前驱节点的下标地址,节点值,后继节点下标地址”,这里我们可以将ne数组修改成l,r数组,r依旧是单链表ne数组作用,l就作为ne数组记录前驱下标作用。(这段代码借用了csdn的资料,自己的被typora卡没了)

head:0 tail:1

0表示左端点,1表示右端点

#include<bits/stdc++.h>
using namespace std;

const int N =1e5+10;
int e[N],r[N],l[N],idx;
//e用来存储数值,l[]用来存储前驱(左边数的下标),r[]用来存储后继(右边数的下标),idx用来记录当前下标

void init()//初始化
{
	//0是左端点,1是右端点 
	r[0]=1;
	l[0]=0;
	idx=2;
 } 
 
 void add_to_right(int k,int x)//在k右边插入x
 {
 	e[idx]=x;//将x存入当前下标的数值数组中 
 	r[idx]=r[k];//当前下标的后继为k的后继 
 	l[idx]=k;//当前下标的前驱为k 
 	l[r[k]]=idx;//k的后继节点的前驱为idx 
	r[k]=idx++; //k的后继节点为idx; idx++; 
  } 
  
  void add_to_left(int k,int x)//在k的左边插入x 
  {
  	add_to_right(l[k],x);//等价于在k的前驱的右边插入x 
  }
  
  void remove(int k)//删除第k个结点
  {
  	r[l[k]]=r[k];//k的前驱的后继为k的后继
	l[r[k]]=l[k];//k发后继发前驱为k的前驱 
   } 
   
   void add_to_head(int x )//在链表的最左端插入数x
   {
   	add_to_right(0,x);//在0的后面插入x 
	} 
	
	void add_to_tail(int x)//在链表的最右端插入数x
	{
		add_to_right(l[1],x);
	 } 
	int main()
	{
		int m;
		init();
		
		scanf("%d",&m);
		while(m--)
		{
			string c;
			cin>>c;
			
			if(c=="L")
			{
				int x;
				scanf("%d",&x);
				add_to_head(x);
			}
			else if(c=="R")
			{
				int x;
				scanf("%d",&x);
				add_to_tail(x);
			}
			else if(c=="D")
			{
				int k;
				scanf("%d",&k);
				remove(k+1);
			}
			else if(c=="IL")
			{
				int k,x;
				scanf("%d%d",&k,&x);
				add_to_left(k+1,x);
			 } 
			else
			{
				int k,x;
				scanf("%d%d",&k,&x);
				add_to_right(k+1,x);
			}
		 } 
		 for(int i=r[0];i!=1;i=r[i])
		 {
		 	printf("%d ",e[i]);
		 }
		 printf("\n");
		 return 0;
	 } 

3.栈 & 队列

栈:先进后出

队列:先进先出

因为是c++语言的做法,这部分可以仅做参考,Java语言可以考虑java自带类(个人参考:栈和队列(超详细Java实现)_java栈和队列_努力写代码的菜鸟的博客-CSDN博客

  1. Stack类:栈类 过时 (不建议,建议使用动态数组)

  2. Queue:队列类 Queue queue = new LinkedList<Integer>(); (建议用于实现队列,但是需要注意其中方法的区别:add和offer,remove和poll,element和peek)

  3. Deque:双端队列(建议用于实现栈,队列)(java关于Deque的使用_java deque_onedegree的博客-CSDN博客

栈(数组构建):

int stk[N], tt; //栈底从0开始 即在插入操作之中先对其自增,也代表从1开始

//插入操作
stk[ ++tt ] = x;

//弹出操作
tt--;

//判断是否为空
if (tt > 0) {
	//not empty
}else {
	//empty
}

//栈顶
stk[tt];

队列:

//在队尾插入元素,在队头弹出元素
int q[N], hh, tt = -1;
//q[]代表队列数组,而hh代表头,tt代表尾

//插入 先自增再插入,保证tt一直是指向队列最后一位的下标,便于判断队列时候为空
q[ ++tt  ] = x;

//弹出
hh++;

//判断队列是否为空
if (hh <= tt) not empty
else empty
    
//取出队头元素
q[ hh ]
    
//取出队尾元素
q[ tt ]

循环队列:(注意tt一直指向的是最后一位的下一位,而单调队列是指最后一位)

// hh 表示队头,tt表示队尾的后一个位置
int q[N], hh = 0, tt = 0;

// 向队尾插入一个数 注意这里是先读取tt下标的数组值,再自增tt 和单调队列区分!!
//所以tt一直代表的是队列最后一位下标的下一位!!
q[tt ++ ] = x;
// 这里判断循环队列是否已经达到末尾,条件达成则返回数组最初位
if (tt == N) tt = 0;

// 从队头弹出一个数
hh ++ ;
//循环条件设置
if (hh == N) hh = 0;

// 队头的值
q[hh];

// 队尾的值
t = tt-1;
q[t]

// 判断队列是否为空
if (hh != tt)
{
    //不为空
}            

单调栈

—— 模板题 AcWing 830. 单调栈

常见模型:找出每个数左边离它最近的比它大/小的数

import java.util.Scanner;

public class 单调栈 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int N = 10010;
		int[] stk = new int[N];
		int tt = 0,num = 0;
				
		//逐个插入栈中,判断是当前栈顶的大小对比,如果新插入的元素小于栈顶元素,则不断弹出栈顶元素,直到栈中元素单调递增
		for (int i = 0; i < n; i ++) {
			num = sc.nextInt();
			
			//当栈元素在新增的元素后不再单调递减时,需要将所有大于或等于新增元素的元素先弹出
			while (tt > 0 && stk[tt] >= num) {
				tt--;
			}
			
			//判断输出元素
			if (tt == 0) {
				System.out.print(-1 + " "); 
			}else {
				System.out.print(stk[tt] + " ");
			}
			
			//记得将新遍历的元素压入栈中
			stk[ ++tt ] = num;
		}
	}
}

单调队列

—— 模板题 AcWing 154. 滑动窗口

常见模型:找出滑动窗口中的最大值/最小值

package 单调队列;

import java.io.*;
public class 滑动窗口 {
	public static void main(String[] args) throws IOException {
		BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
		String[] s = bf.readLine().split(" ");
		
		int n = Integer.parseInt(s[0]);
		int k = Integer.parseInt(s[1]);
		int[] q = new int[n];
		int[] a = new int[n];
		int tt = -1, hh = 0;
		int[] min = new int[n];
		int[] max = new int[n];
		int mi = 1000010, ma = -1000010;
		
		//队列中储存的是数组的下标
		s = bf.readLine().split(" ");
		for (int i = 0; i < n; i++) {
			//初始化
			a[i] = Integer.parseInt(s[i]);
		}
		
		//最小值
		for (int i = 0; i < n; i++) {
			//弹出队列头
			if (tt >= hh && i - k + 1 > q[hh]) {
				hh++;
			}
			  
			
			//判断队列当前是否是单调递减,否则让队列尾前移一位
			while (tt >= hh && a[q[tt]] >= a[i]) {
				tt--;
			}
			
			//将新元素压入队列
			q[ ++tt ] = i;
				
			//输出
			if ( i - k + 1 >= 0) {
				System.out.print(a[q[hh]] + " ");
			}
		}
		System.out.println(" ");
		
		//最大值
		for (int i = 0; i < n; i++) {
			//弹出头
			if (tt >= hh && i - k + 1 > q[hh]) {
				hh++;
			}
			
			//递减
			while (hh <= tt && a[q[hh]] <= a[i] ) {
				tt--;
			}
			
			q[ ++tt ] = i;
			
			if (i - k + 1 >= 0) {
				System.out.print(a[q[hh]] + " ");
			}
		}
//8 3
//1 3 -1 -3 5 3 6 7
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值