算法与数据结构基础总结
1、线性查找
2、选择排序、插入排序
3、动态数组
4、循环队列
5、链表
6、递归链表
7、归并排序
8、快速排序
9、二分查找
10、二分搜索树
11、堆
12、冒泡排序和希尔排序
13、线段树
14、Trie
15、并查集
16、AVL 树
17、2 - 3 树
18、红黑树
19、哈希表
20、SQRT 分解
21、计数排序
22、LSD 字符串排序算法
23、MSD 字符串排序算法
24、桶排序
25、字符串匹配 Rabin-Karp 算法
26、字符串匹配 KMP 算法
27、随机算法
28、外存算法
29、排序算法总结
30、力扣练习题
liuyubobobo
一些同学能刷动题了,但其实基础不行
快排搞不懂,AVL 树、红黑树搞不明白,这些都是刷题不能带给你的,因为刷题网站完全不考察这些底层算法和数据结构的理解
但在我看来,这些是真正的计算机科学的基础,反而是刷几个 ac 没那么难
我并不认为软件工程师要了解 dp,但是不了解这些基础的算法和数据结构,绝对是计算机技术深入下去的瓶颈
一些基础的 API
基础
与 & 有 0 则 0,二进制截断
或 | 有 1 则 1,二进制组合
非 ! 取反
异或 ^ 无进位相加
时间毫秒值:指的是从 1970 年 1 月 1 日 00:00:00 走到此刻的总的毫秒数,1 s = 1000 ms = 1000000 μs = 1000000000 ns
Arrays.fill(cnt, 0);
Arrays.copyOf(srcArr, length); // length 是新数组的长度
Arrays.copyOfRange(srcArr, l, r + 1);
Arrays.stream(arr).max().getAsInt();
Arrays.stream(arr).sum();
System.arraycopy(srcArr, arcArrStartIndex, targetArr, targetArrStartIndex, length);
"--".repeat(Math.max(0, depth));
treeSet.ceiling(num); // >= num 的最小值
treeSet.floor(num); // <= num 的最大值
treeSet.subSet(min, max); // min <= num < max
treeSet.subSet(min, true, max, true); // min <= num <= max
list.stream().mapToInt(Integer::intValue).toArray();
优先队列
public interface Queue<E> extends Collection<E> {
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
}
// 添加元素
public boolean add(E e);
public boolean offer(E e);
// 移除堆顶元素
public E remove();
public E poll();
// 查看堆顶元素
public E element();
public E peek();
PriorityQueue<Integer> pq1 = new PriorityQueue<>(); // 默认是最小堆
PriorityQueue<Integer> pq2 = new PriorityQueue<>(Collections.reverseOrder()); // 修改为最大堆
栈、队列、双端队列
public static void testStack() {
Stack<Integer> stack = new Stack<>();
stack.push(1); // 入栈 1
stack.push(2); // 入栈 2
stack.push(3); // 入栈 3
int top = stack.peek(); // 查看栈顶元素, 返回 3
int popElement = stack.pop(); // 出栈, 返回 3
boolean empty = stack.empty(); // 查看栈是否为空, 返回 false
int size = stack.size(); // 查看栈大小, 返回 2
}
public static void testQueue() {
Queue<Integer> queue = new LinkedList<>();
queue.offer(1); // 入队 1
queue.offer(2); // 入队 2
queue.offer(3); // 入队 3
int front = queue.peek(); // 查看队首元素, 返回 1
int dequeueElement = queue.poll(); // 出队, 返回 1
boolean empty = queue.isEmpty(); // 查看队列是否为空, 返回 false
int size = queue.size(); // 查看队列大小, 返回 2
}
public static void testDeque1() {
Deque<Integer> queue = new ArrayDeque<>(); // 基于数组实现
Deque<Integer> queue = new LinkedList<>(); // 基于链表实现
queue.addLast(1); // 从队尾入队 1
queue.addLast(2); // 从队尾入队 2
queue.addLast(3); // 从队尾入队 3
queue.addFirst(1); // 从队首入队 1
queue.addFirst(2); // 从队首入队 2
queue.addFirst(3); // 从队首入队 3
int front = queue.peekFirst(); // 查看队首元素, 返回 3
int dequeueElement = queue.pollFirst(); // 从队首出队, 返回 3
int top = queue.peekLast(); // 查看队尾元素, 返回 3
int popElement = queue.pollLast(); // 从队尾出队, 返回 3
boolean empty = queue.isEmpty(); // 查看队列是否为空, 返回 false
int size = queue.size(); // 查看队列大小, 返回 4
}
public static void testDeque2() {
Deque<Integer> deque = new LinkedList<>();
deque.push(1);
deque.push(2);
deque.push(3);
System.out.println(deque.peek()); // 输出 3
while (!deque.isEmpty()) {
System.out.println(deque.pop()); // 输出顺序: 3、2、1
}
}
1、递归
递归的执行流程:终结点、递归子问题、当前问题、返回
222... 1 343434...
1、终结点(基础问题)
2、递归子问题
3、当前问题(这里解决问题)
4、返回
常见的递归终结点
1、链表的递归一般是 node == null 或者对 index 的范围进行判断
2、数组的递归一般是区间问题 arr[l, r],l >= r 或 l > r 或 r - l <= 15
2、取数字
1234 % 10 = 4; // 取出最右边的一位
1234 / 10 = 123; // 去除最右边的一位
3、数组
// 获取一个 [l ... r] 之间的随机数, 即 [0 ... r - l] + l
// [l ... r] 的长度为 (r - l + 1)
int x = random.nextInt(r - l + 1) + l; // random.nextInt(区间长度) + 左边界
arr[l ... r] 的元素个数是 n
r - l 代表: l 走到 r 需要多少步(答案是 n - 1 步)
n = r - l + 1
n - 1 = r - l
r = l + (n - 1)
l = r - (n - 1)
自底向上归并排序:遍历数组区间,只需要遍历 "右区间的左端点",同时需要注意 "右端点不要越界"
快速排序:注意 p1、p2 的初始位置
1、单路快排:j 代表左括弧(for)
2、双路快排:p1、p2 代表遍历索引(while)
3、三路快排:p1、p2 代表括弧,需要额外的变量 i 遍历(while)
从左往右数第 K 个元素的索引为 K - 1
从右往左数第 K 个元素的索引为 length - K
数组长度为 N
每组元素个数为 B
组数 Bn = N / B + (N % B != 0 ? 1 : 0)
第几组:index / B
第几组的第几个:index % B
4、增删改查
增加:判断索引(是否满了)
删除:判断索引(是否为空)
修改:判断索引(是否为空)
查看:判断索引(是否为空)
5、循环
// i % 3 的结果为 [0, 1, 2]
// i % n 的结果为 [0 ... n - 1]
for (int i = 0; i < 10; i++) {
if (i % 3 == 2) {
queue.dequeue();
System.out.println(queue);
}
}
6、链表
dummyHead 简化添加和删除的逻辑
递归:先序、后序
链表的遍历:for (Node cur = head; cur != null; cur = cur.next)、while (cur != null)
7、二分
// 如果问题有明显的边界,就可以用二分查找法在这个边界去搜索问题的解
int l;
int r;
int mid = l + (r - l) / 2; // l <= mid < r, l 与 r 相邻时 mid = l
int l;
int r;
int mid = l + (r - l + 1) / 2; // l < mid <= r, l 与 r 相邻时 mid = r
l = 0, r = data.length - 1
每次循环开始时: data[l]、data[r] 还没看, 因此 l == r 时, 依然要进入循环
l = 0, r = data.length
每次循环开始时: data[l] 还没看, data[r] 可能是解, 因此当 l == r 时, r 就是解
l = -1, r = data.length - 1
每次循环开始时: data[l] 可能是解, data[r] 还没看, 因此当 l == r 时, l 就是解
8、二分搜索树 BST
二分搜索树基于比较,大多递归的终结点是:node == null(类似递归链表没有 index)
深度优先
1、前序遍历:中左右
2、中序遍历:左中右,二分搜索树的中序遍历结果是顺序的
3、后序遍历:左右中,为二分搜索树释放内存
广度优先:层序遍历,更快找到问题的解,常用于算法设计中 - 无权图最短路径
删除节点
1、有左孩子,没右孩子
2、有右孩子,没左孩子
3、左右都有孩子:用待删除节点的后继节点或前驱节点来顶替待删除节点的位置
【1】前驱:比待删除节点小的最大节点,即待删除节点左子树的最大节点 predecessor
【2】后继:比待删除节点大的最小节点,即待删除节点右子树的最小节点 successor
9、Set 和 Map
集合和映射,Set 和 Map,基于 BST 和 LinkedList 实现
1、有序 Set 和 Map:基于 BST 实现
2、无序 Set 和 Map:基于 LinkedList 实现,还可以基于 "哈希表" 来实现
需要注意 Map 中有个工具函数 Node getNode
10、堆和优先队列
堆是完全二叉树,我们可以使用数组来存放堆
建堆方式
1、将 n 个元素逐个插⼊到⼀个空堆中,算法复杂度是 O(N * logN)
2、heapify:从最后一个非叶子节点开始,从后向前,倒序进行 siftDown 操作,算法复杂度为 O(n)
Select K 和 Top K 问题
1、快排思想:时间复杂度 O(n),空间复杂度 O(1)
2、优先队列:时间复杂度 O(N * logK),空间复杂度 O(K)
优先队列的优势:具有实时性,不需要一次性知道所有数据(数据流、极大规模数据)
11、Select K 与 Top K 问题
12、线段树
线段树不是满二叉树,也不是完全二叉树,而是平衡二叉树
但是,我们可以把它看做是满二叉树,这样,就可以用数组来表示线段树
tree[treeIndex] 代表 data[l ... r] 的融合结果,因此 treeIndex、l、r 它们三者是绑定在一起的
13、Trie
14、并查集
用来解决连接问题
1、QuickFind、QuickUnion
2、基于 size 的优化:让 "元素个数少的集合" 合并到 "元素个数多的集合" 上,使得合并后的新树,深度尽量不要增加
3、基于 rank 的优化:让 "层数低的集合" 合并到 "层数高的集合" 上,使得合并后的新树,深度尽量不要增加
4、路径压缩:压缩树的高度使其尽可能的矮
15、AVL 树
Node 新添加了一个成员变量 height
辅助函数
1、获得节点 node 的高度
2、获得节点 node 的平衡因子
3、判断二叉树是否是一棵二分搜索树:中序遍历
4、判断二叉树是否是一棵平衡二叉树:前序遍历
维护自平衡:LL、RR、LR、RL、L、R
1、左旋转,旋转后要更新 height(必须先更新 y, 后更新 x)
2、右旋转,旋转后要更新 height(必须先更新 y, 后更新 x)
在添加和删除时保持自平衡
1、更新 height
2、计算平衡因子 balanceFactor
3、维护自平衡
删除节点后保持自平衡的坑
1、用待删除节点的后继节点或前驱节点来顶替待删除节点的位置时,也需要维护自平衡
2、删除叶子节点后, 返回的 retNode 为 null
16、2 - 3 树
2 - 3 树
添加元素不会添加到空节点
一定是添加到最后搜索到的叶子节点,与它做融合
1、如果插入 "二节点",则融合形成 "三节点"
2、如果插入 "三节点",则融合形成 "四节点",再拆解形成 3 个 "二节点"
【1】如果父节点为 "二节点",则融合形成 "三节点"
【2】如果父节点为 "三节点",则重复 2
17、红黑树
1、每个节点或者是红色的,或者是黑色的
2、根节点是黑色的
3、每一个叶子节点(最后的空节点)是黑色的
4、如果一个节点是红色的,那么他的孩子节点都是黑色的
5、从一个节点到任意叶子节点,经过的黑色节点是一样的
红黑树是保持 "黑平衡" 的二叉树
严格意义上,不是平衡二叉树,最大高度:2 * logN
红黑树添加和删除比 AVL 树快,查询比 AVL 树慢(它比 AVL 树更高)
Node 新添加了一个成员变量 color,默认插入红色节点(新添加的元素一定是红色的)
辅助函数
1、判断节点 node 的颜色
2、保持根节点为黑色节点
3、左旋转
4、右旋转
5、颜色翻转
添加操作
1、保持根节点为黑色节点
2、新添加的元素一定是红色的
3、保持 "黑平衡"
将一个元素插入到一个 "二节点" 中,使其融合形成 "三节点"
1、添加到左边
2、添加到右边:为了保证所有的红色节点都是左倾斜的,在这里需要通过左旋转来调整
将一个元素插入到一个 "三节点" 中,使其变为临时的 "四节点"
再拆解形成 3 个 "二节点",根节点继续向上,与它的父亲节点做融合
1、添加到右边:颜色翻转
2、添加到左边:右旋转、颜色翻转
3、添加到中间:左旋转、右旋转、颜色翻转
1、二分搜索树
对于完全随机的数据,普通的二分搜索树很好用!
缺点:极端情况退化成链表(或者高度不平衡)
2、AVL 树
对于查询较多的使用情况,AVL 树很好用!
3、红⿊树
红黑树牺牲了平衡性(2 * logN 的高度)
统计性能更优(综合增删改查所有的操作)
4、Splay Tree 伸展树
另一种统计性能优秀的树结构
局部性原理:刚被访问的内容下次高概率被再次访问
18、哈希表
哈希函数的设计是很重要的
"键" 通过哈希函数得到的 "索引" 分布越均匀越好
通用的哈希函数,都是转成整型处理,但它并不是唯一的方法!
原则
1、一致性:如果 a == b,则 hash(a) == hash(b)
2、高效性:计算高效简便
3、均匀性:哈希值均匀分布
int hash = 0;
for (int i = 0; i < s.length(); i++) hash = (hash * B + s.charAt(i)) % M; // B 代表进制, M 代表素数
19、快速幂运算
public class Power {
private Power() {
}
/**
* 递归快速幂
*/
public static long pow1(long a, int n) {
if (n == 0) return 1;
long temp = pow1(a, n / 2);
if (n % 2 == 1) return temp * temp * a;
return temp * temp;
}
/**
* 非递归快速幂
*/
public static long pow2(long a, int n) {
long res = 1;
while (n > 0) {
if ((n & 1) == 1) res *= a;
n >>= 1;
a *= a;
}
return res;
}
}
public class Power {
private static final long MOD = (long) (1e9 + 7);
private Power() {
}
/**
* 递归快速幂
*/
public static long pow1(long a, int n) {
if (n == 0) return 1;
long temp = pow1(a, n / 2) % MOD;
if (n % 2 == 1) return temp * temp * a % MOD;
return temp * temp % MOD;
}
/**
* 非递归快速幂
*/
public static long pow2(long a, int n) {
long res = 1;
while (n > 0) {
if ((n & 1) == 1) res = res * a % MOD;
n >>= 1;
a = a * a % MOD;
}
return res;
}
}
20、各种树
满二叉树
- 节点总个数 (2 ^ h) - 1
- 叶子节点的个数 2 ^ (h - 1)
- 非叶子节点个数 2 ^ (h - 1) - 1
本文来自博客园,作者:lidongdongdong~,转载请注明原文链接:https://www.cnblogs.com/lidong422339/p/17306533.html