算法数据结构篇

一、单链表

1. 单链表(数组模拟)

	static final int N = 100010;
	// head 表示头结点的下标
	// e[i] 表示节点i的值
	// ne[i] 表示节点i的next指针是多少
	// idx 存储当前已经用到了哪个点
	static int head, idx;
	static int[] e = new int[N], ne = new int[N];

	// 初始化
	static void init() {
		head = -1;
		idx = 0;
	}

	// 将x插到头结点
	static void add_to_head(int x) {
		e[idx] = x;
		ne[idx] = head;
		head = idx++;
	}

	// 将x插到下标是k的点后面
	static void add(int k, int x) {
		e[idx] = x;
		ne[idx] = ne[k];
		ne[k] = idx++;
	}

	// 将下标是k的点后面的点删掉
	static void remove(int k) {
		ne[k] = ne[ne[k]];
	}
	
	//遍历
	static void print(){
      for (int i = head; i != -1; i = ne[i]) {
			System.out.print(e[i] + " ");
		}
	}

二、双链表

1.双链表(数组模拟)

	static final int N = 100010;
	// e[i] 表示节点i的值
	// l[i] 表示节点i的左指针是多少
	// r[i] 表示节点i的右指针是多少
	// idx 存储当前已经用到了哪个点
	static int idx;
	static int[] e = new int[N], l = new int[N], r = new int[N];

	static void init() {
		// 初始化:0是左端点;1是右端点
		r[0] = 1;
		l[1] = 0;
		idx = 2;
	}

	// 在k的右边插入一个数x
	static void insert(int k, int x) {
		e[idx] = x;
		r[idx] = r[k];
		l[idx] = k;
		l[r[k]] = idx;
		r[k] = idx++;
	}

	// 删除第k个点
	static void remove(int k) {
		r[l[k]] = r[k];
		l[r[k]] = l[k];
	}
	
	//遍历
	static void print(){
      for (int i = r[0]; i != 1; i = r[i]) {
			System.out.print(e[i] + " ");
		}
	}

三、栈

1.表达式求值

示例:210-1000+24-(5×3)+(3/2)
注意:这里的表达式是中序遍历结果,区别于后序的不同做法

import java.util.*;

public class Main {
	// 存放数字
	static Stack<Integer> num = new Stack<>();
	// 存放操作符
	static Stack<Character> op = new Stack<>();

	// 进行运算
	static void eval() {
		int b = num.pop();
		int a = num.pop();
		char c = op.pop();
		int x;
		if (c == '+')
			x = a + b;
		else if (c == '-')
			x = a - b;
		else if (c == '*')
			x = a * b;
		else
			x = a / b;
		num.push(x);
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		String str = in.nextLine();
		// 设置操作符优先级
		Map<Character, Integer> pr = new HashMap<>();
		pr.put('-', 1);
		pr.put('+', 1);
		pr.put('*', 2);
		pr.put('/', 2);

		char[] chs = str.toCharArray();
		for (int i = 0; i < chs.length; i++) {
			if (Character.isDigit(chs[i])) {
				int x = 0, j = i;
				while (j < chs.length && Character.isDigit(chs[j])) {
					x = x * 10 + chs[j++] - '0';
				}
				i = j - 1;
				num.push(x);
			} else if (chs[i] == '(') {
				op.push('(');
			} else if (chs[i] == ')') {
				while (op.peek() != '(')
					eval();
				op.pop();
			} else {
				while (!op.isEmpty() && op.peek() != '(' && pr.get(op.peek()) >= pr.get(chs[i])) {
					eval();
				}
				op.push(chs[i]);
			}
		}
		while (!op.isEmpty()) {
			eval();
		}
		System.out.println(num.peek());
	}
}

四、单调栈

单调栈指栈中的元素单调递增或递减,主要用于找到每一个元素左边或者右边,第一个比它大或小的数

1. 单调栈

【单调栈】:给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1

import java.util.*;

public class Main {
	static final int N = 100010;

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int[] stk = new int[N];
		int tt = 0;
		for (int i = 0; i < n; i++) {
			int x = in.nextInt();
			while (tt > 0 && stk[tt] >= x) {
				tt--;
			}
			if (tt > 0) {
				System.out.print(stk[tt] + " ");
			} else {
				System.out.print(-1 + " ");
			}
			stk[++tt] = x;
		}

	}
}

2.仰视奶牛

【仰视奶牛】:输出每一个奶牛右边身高比他更高的最近奶牛位置

import java.util.*;

public class Main {
	static final int N = 100010;

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int[] h = new int[N];//记录身高
		int[] res=new int[N];//记录结果
		for(int i=1;i<=n;i++){
		    h[i]=in.nextInt();
		}
		//单调栈
		Stack<Integer> st=new Stack<>();
		for(int i=n;i>0;i--){
		    while(!st.isEmpty()&&h[st.peek()]<=h[i])st.pop();
		    if(!st.isEmpty()){
		        res[i]=st.peek();
		    }
		    st.push(i);
		}
		for(int i=1;i<=n;i++){
		    System.out.println(res[i]);
		}
	}
}

五、单调队列

单增或单减的队列

1. 滑动窗口

【滑动窗口】:给定一个长度为n的数组,大小为k的滑动窗口,输入每个滑动窗口中的最小值

import java.util.*;

public class Main {
	static final int N = 1000010;

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int k = in.nextInt();
		int[] a = new int[N];// 原数组
		for (int i = 0; i < n; i++) {
			a[i] = in.nextInt();
		}
		Deque<Integer> q = new ArrayDeque<>();
		for (int i = 0; i < n; i++) {
			//当对头不在区间中时
			if (!q.isEmpty() && i - k + 1 > q.peekFirst())
				q.pollFirst();
			//当队尾元素比当前元素大时,去除冗余
			while (!q.isEmpty() && a[q.peekLast()] >= a[i])
				q.pollLast();
			q.offerLast(i);
			if (i >= k - 1) {
				System.out.print(a[q.peekFirst()] + " ");
			}
		}
	}
}

六、KMP

时间复杂度:O(n+m)

1.字符串匹配

【字符串匹配】:求出 字符串s2 在 字符串s1 中所有出现的位置

import java.util.*;

public class Main {
	static final int N = 1000010;

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		String str = in.nextLine();
		int n = str.length();
		//匹配串s1
		char[] p = new char[N];
		//匹配子串s2
		char[] s = new char[N];
		for (int i = 0; i < n; i++) {
			s[i + 1] = str.charAt(i);
		}
		str = in.nextLine();
		int m = str.length();
		for (int i = 0; i < m; i++) {
			p[i + 1] = str.charAt(i);
		}
		//求前缀匹配数组ne[],即处理匹配子串向前回退的位置
		int[] ne = new int[N];
		for (int i = 2, j = 0; i <= m; i++) {
			while (j > 0 && p[i] != p[j + 1]) {
				j = ne[j];
			}
			if (p[i] == p[j + 1])
				j++;
			ne[i] = j;
		}
		//进行匹配
		for (int i = 1, j = 0; i <= n; i++) {
			while (j > 0 && s[i] != p[j + 1]) {
				j = ne[j];
			}
			if (s[i] == p[j + 1]) {
				j++;
			}
			if (j == m) {
				System.out.println(i - m + 1);
				j = ne[j];
			}
		}
		for (int i = 1; i <= m; i++) {
			System.out.print(ne[i] + " ");
		}
	}
}

七、Trie

快速存储和查找字符串集合的数据结构

1.字符串统计

【Trie字符串统计】

import java.util.*;

public class Main {
	static final int N = 100010;
	//idx、son存储单词
	static int idx = 0;
	static int[][] son = new int[N][26];
	//作为每个单词结尾的记录标识
	static int[] cnt = new int[N];

	static void insert(char[] str) {
		int p = 0;
		for (int i = 0; i < str.length; i++) {
			int u = str[i] - 'a';
			//如果这个的儿子分支没有字符,说明这条分支还没有这个字符插入过
            //就新建一个然后赋值为然后把【idx】下标赋值上去,作为每个分支的专属坐标
			if (son[p][u] == 0)
				son[p][u] = ++idx;//创建一个新节点
			//p向下进一层
			p = son[p][u];
		}
		//单词结束做好标记
		cnt[p]++;
	}

	static int query(char[] str) {
		int p = 0;
		for (int i = 0; i < str.length; i++) {
			int u = str[i] - 'a';
			if (son[p][u] == 0)
				return 0;
			p = son[p][u];
		}
		return cnt[p];
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		in.nextLine();
		while (n-- > 0) {
			String[] str = in.nextLine().split(" ");
			if (str[0].equals("I")) {
				insert(str[1].toCharArray());
			} else {
				System.out.println(query(str[1].toCharArray()));
			}
		}
	}
}

2.最大异或对

【最大异或对】:给定的n个数字中找出得到的异或结果的最大值

import java.util.*;

public class Main {
	static final int N = 3100010;
	// idx、son存储
	static int idx = 0;
	static int[][] son = new int[N][2];

	static void insert(int x) {
		int p = 0;
		//每个数的二进制有31位组成,从左开始遍历
		for (int i = 30; i >= 0; i--) {
			int u = x >> i & 1;
			if (son[p][u] == 0)
				son[p][u] = ++idx;//这一层为空,创建结点
			p = son[p][u];
		}
	}

	static int query(int x) {
		int p = 0, res = 0;
		for (int i = 30; i >= 0; i--) {
			int u = x >> i & 1;
			if (son[p][1 - u] != 0) {//优先考虑异位位置
				res += (1 << i);
				p = son[p][1 - u];
			} else {
				p = son[p][u];
			}
		}
		return res;
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		in.nextLine();
		String[] strs = in.nextLine().split(" ");
		for (int i = 0; i < strs.length; i++) {
			insert(Integer.parseInt(strs[i]));
		}
		int res = 0;
		for (int i = 0; i < n; i++) {
			res = Math.max(res, query(Integer.parseInt(strs[i])));
		}
		System.out.println(res);
	}
}

八、并查集

1.并查集是一种树形的数据结构,用于处理一些不相交集合的合并和查询问题

2.并查集的常用操作:

  • 查找:查询两个元素是否在同一个集合中
  • 合并:把两个不相交的集合合并为一个集合
//核心代码
// 寻找根节点祖先+路径压缩(每个数直接指向集合的根节点)
	static int find(int x) {
		if (p[x] != x)
			p[x] = find(p[x]);
		return p[x];
	}

1.合并集合

import java.util.*;

public class Main {

	static final int N = 100010;
	static int[] p = new int[N];

	// 寻找根节点祖先+路径压缩(每个数直接指向集合的根节点)
	static int find(int x) {
		if (p[x] != x)
			p[x] = find(p[x]);
		return p[x];
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();// n个数
		int m = in.nextInt();// m个操作
		for (int i = 1; i <= n; i++) {
			p[i] = i;// if(p[i]==i)表示树根,即为父节点
		}
		while (m-- > 0) {
			String str = in.next();
			int a = in.nextInt();
			int b = in.nextInt();
			// 合并集合
			if (str.equals("M")) {
				p[find(a)] = find(b);
			} else {
				// 查找
				if (find(a) == find(b)) {
					System.out.println("Yes");
				} else {
					System.out.println("No");
				}
			}
		}
	}
}

2.连通块中点的数量

import java.util.*;

public class Main {

	static final int N = 100010;
	// size存储每个集合中数的个数
	static int[] p = new int[N], size = new int[N];

	// 寻找根节点祖先+路径压缩(每个数直接指向集合的根节点)
	static int find(int x) {
		if (p[x] != x)
			p[x] = find(p[x]);
		return p[x];
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();// n个数
		int m = in.nextInt();// m个操作
		for (int i = 1; i <= n; i++) {
			p[i] = i;// if(p[i]==i)表示树根,即为父节点
			size[i] = 1;
		}
		while (m-- > 0) {
			String str = in.next();
			int a = in.nextInt();
			// 合并集合
			if (str.equals("C")) {
				int b = in.nextInt();
				if (find(a) == find(b))
					continue;
				else {
					size[find(b)] += size[find(a)];
					p[find(a)] = find(b);
				}

			} else if (str.equals("Q1")) {
				// 查找
				int b = in.nextInt();
				if (find(a) == find(b)) {
					System.out.println("Yes");
				} else {
					System.out.println("No");
				}
			} else {
				System.out.println(size[find(a)]);
			}
		}
	}
}

九、堆

堆是一棵完全二叉树结构,有小根堆(根值小于左右儿子)和大根堆(根值大于左右儿子)

1.堆排序

import java.util.*;

public class Main {
	static final int N = 100010;
	static int[] h = new int[N];
	static int size;

	static void swap(int x, int y) {
		int t = h[x];
		h[x] = h[y];
		h[y] = t;
	}
	//关键代码
	static void down(int u) {
		int t = u;
		if (u * 2 <= size && h[u * 2] < h[t])
			t = u * 2;
		if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t])
			t = u * 2 + 1;
		if (u != t) {
			swap(u, t);
			down(t);
		}
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int m = in.nextInt();
		for (int i = 1; i <= n; i++) {
			h[i] = in.nextInt();
		}
		size = n;
		//从最后一个非叶子结点开始下沉
		for (int i = n / 2; i > 0; i--) {
			down(i);
		}
		while (m-- > 0) {
			System.out.print(h[1] + " ");
			h[1] = h[size--];
			down(1);
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值