Skip to content

Commit f86eaaa

Browse files
RexLycliyicheng
andauthored
202311 (#36)
* update: typo fix * update: cpp * update: add http token, with cpp both unfinished * tiny * update: crypto * update: to learn mergable heap * update * update * update * update * update * update: SA unfinished * update: finish 1/2 of sa * update: sa * update * update * update: add netty, webrtc thumbnail. Please Finish all ToDo content as soon as possible! * update * update: add wsl * update * abandon wsl * update * update: netty page to 62/272 * update excercise * update: netty to 82/272 * update: netty to 94/272 * update: netty page to 104/272 * update:exercise * update: page to 104/272 * update * update * update: read to page 124/272 * update * update: add mermaid support * update: add diff array * update * update: page to 154/272 * update: daily exercise * update: page to 163/272, fast read remaining part * update * update: page to 198/272 * update: page to 211/272, read faster! * update * update: add trie code * update' * update: finish netty read, next book is c/c++ 11/14/17/20 * update * update: read cpp * update * update * update: addd 2023 gallery * update: show some drafts --------- Co-authored-by: liyicheng <liyicheng@bmncc.com.cn>
1 parent 6a098ef commit f86eaaa

141 files changed

Lines changed: 3887 additions & 138 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/settings.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"files.associations": {
3+
"vector": "cpp",
4+
"stack": "cpp"
5+
}
6+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
title: "算法导论其十:进阶篇"
3+
date: 2023-12-06T11:04:53+08:00
4+
categories:
5+
- 计算机科学与技术
6+
- 算法
7+
tags:
8+
- 算法系列
9+
- 滚动更新
10+
thumbnailImagePosition: left
11+
thumbnailImage: /images/thumbnail/algorithm.jpg
12+
math: true
13+
# draft: true
14+
---
15+
本章节将会聚焦一些经典问题,以及经典进阶算法。在追求高效率的路上是没有极限的。
16+
<!--more-->
17+
## 经典问题
18+
### RMQ区间最大值最小值查询
19+
问题描述:在一个给定区间中,查询区间内的最大值或最小值
20+
- Tarjan的Sparse-Table算法
21+
- 构建:$dp[i][j]$表示i为起点,长度为$2^j$的区间的最值。状态转移方程(可以理解为二分)为$dp[i][j]=min/max(dp[i][j-1]+dp[i+(1<<(j-1))][j-1])$。注意二层循环,j为外层(按照区间长度分治)。
22+
- 查询:寻找小于等于区间范围的区间(或多个区间,可以重叠)。
23+
- 复杂度:$O(nlogn)$建立,$O(1)$查询
24+
- 线段树:$O(n)$建立,$O(logn)$查询
25+
- 笛卡尔树+LCA+DP:$O(n)$建立,$O(1)$查询
26+
27+
### LCA最近公共祖先
28+
问题描述:在一棵树中,求任意两个节点的祖先中,深度最大的那个。LCA问题往往会同时输入多个查询。
29+
- 朴素算法:
30+
- 描述:将两个节点中深度较大的一个先向树根上跳,直到二者深度一致。然后再共同上跳,直到相遇。
31+
- 复杂度:$O(H)$,考虑到$m$次查询,则整体复杂度为$O(H*m)$,$H$为树的高度。
32+
- 倍增算法:
33+
- 描述:建立一个跳转表,加速朴素算法两个阶段的上跳过程。
34+
- 跳转表:和RMQ问题中的ST表非常类似,令$f[i][j]$代表点$i$的第$2^j$个祖先,则$f[i][j]=f[f[i][j-1]][j-1]$。(爸爸的爸爸是爷爷)
35+
- 建立跳转表:dfs遍历,每个节点利用父节点已经保存的祖先信息计算。另外在dfs时顺便保存每个节点的深度信息。
36+
- 第一阶段上跳:每一次上跳尽量多且不超过另一个节点的深度
37+
- 第二阶段上跳:两节点上跳到父节点相等,但当前节点不相等。如果上跳过程中两个节点跳后相等,则是跳多了。
38+
- 复杂度:预处理$O(nlog n)$,单次查询为$O(log n)$,整体为$O(n log n + m log n)$
39+
- 欧拉序列转RMQ问题:
40+
- 描述:将树转换为欧拉序列,并保存对应的深度信息,再对序列求RMQ问题
41+
- 欧拉序列:按照dfs顺序,当前节点开始访问和结束访问时都记录到序列中,相当于“根左右根”,序列长度为$2n$
42+
- 欧拉序列性质:树上的两点,其最终在欧拉序列中的两个位置(只取节点第一次加入的位置),两个位置之间,深度最小值就是其LCA
43+
- 复杂度:由所使用的RMQ算法决定
44+
- Tarjan算法:
45+
- 描述:考虑一个节点的左右子树,如果一个查询分别位于两个子树上,则LCA一定就是当前的节点。如果用并查集来合并保存节点之间的祖先关系,那么这种查询就能在$O(1)$时间内完成。
46+
- 建立初始并查集(孤立点),并为每一个节点记录所有相关查询
47+
- 遍历:从根开始DFS遍历每一个节点
48+
- 检查当前节点是否有查询请求
49+
- 有,且另一个节点标记为已处理,则另一个节点的并查集祖先就是当前查询的LCA
50+
- 有,且另一个节点未处理,暂时跳过
51+
- 如果存在子节点未访问,对其递归
52+
- 任意子节点递归返回时,都将其并查集合并到当前节点,即以当前节点为子节点的并查集祖先
53+
- 不存在子节点,直接返回
54+
- 复杂度:建立数据结构花费$O(n)$,$n$为节点数,单次查询为$O(1)$,整体为$O(n+m)$。
55+
- 扩展:
56+
- 求树上两点路径:Tarjan算法在使用的过程中,一个查询中,已处理节点的并查集路径和当前节点的各级父节点就是两点之间的路径
57+
- 扩展
58+
- LCA和树差分:求一个带权树上两点之间的距离。其距离等于两点到根的距离之和,减去两倍的两点LCA到根的距离。
59+
- 参考:[最近公共祖先 LCA 算法详解- 朴素、在线、离线](https://blog.csdn.net/qq_43332980/article/details/107437070)[Bilibili推荐:【算法】LCA&RMQ&树差分——保姆级教程](https://www.bilibili.com/video/BV1nE411L7rz)

content/post/CSBasic/algo3-sort.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ math: true
139139
- 关键在于证明$\sum_{i=0}^{n-1}O(E[n_{i}^2])$是线性的。(E代表期望)
140140
- 推理的关键在于令$n_i=\sum_{j=1}^{n}X_{ij}$,其中$X_{ij}=I\\{A[j]$落在桶$i$中$\\}$。$X_{ij}$是指示器随机变量(参考算法导论其一:分析)。
141141
- ### 计数排序
142-
1. 概况:假设n个输入元素中的每一个都是介于0到k之间的整数,此处k为某个证书。当$k=O(n)$时,计数排序的运行时间是$\Theta(n)$。
142+
1. 概况:假设n个输入元素中的每一个都是介于0到k之间的整数,此处k为某个整数。当$k=O(n)$时,计数排序的运行时间是$\Theta(n)$。
143143
2. 基本思路是对于每一个输入元素x,计数小于x的元素个数。从而直接将x放在指定的位置。
144144
```cpp
145145
std::vector<int> count_sort(std::vector<int>& input) {

content/post/CSBasic/algo6-graph.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ int main(){
413413
- 时间复杂度:$O(ElogE)$
414414
- Prim算法
415415
- 贪心策略:Prim算法中,$A$一直保持单棵树,每一次加入的安全边,总是连接树和一个不在树中的顶点,并且这条安全边是当前可选的安全边中最短的一个。
416-
- 时间复杂度:$O(ElogV)$,斐波那契堆优化下能更快
416+
- 时间复杂度:$O(ElogV)$,[斐波那契堆]({{<relref "/content/post/CSBasic/algo7-tree.md#斐波那契堆">}})优化下能更快
417417
- 注意$O(ElogV)$和$O(ElogE)$并没有本质区别,因为$|E|\le|V|^2$,易知$O(logE)=O(logV)$
418418
- 优化:
419419
```cpp
@@ -606,7 +606,7 @@ int main() {
606606
- 路径松弛定理:若$p=<v_0,v_1,...,v_k>$是一条最短路径,且对$p$的边按照$(v_0,v_1)(v_1,v_2)...(v_{k-1},v_k)$进行松弛,则$d[v_k]=\delta(s,v_k)$。
607607
- 前驱子图性质:一旦对于所有$v\in V,d[v]=\delta(s,v)$,则前驱子图构成一个以s为根的最短路径树。
608608
### 单源最短路
609-
- Dijkstra算法:不存在负权边的情况下,时间复杂度$O(V^2)$,斐波那契堆优化下能更快
609+
- Dijkstra算法:不存在负权边的情况下,时间复杂度$O(V^2)$,[斐波那契堆]({{<relref "/content/post/CSBasic/algo7-tree.md#斐波那契堆">}})优化下能更快
610610
- 基本思路:
611611
1. 初始化最短路径估计
612612
2. 初始化已获得最短路径的顶点集合,空集$S$
@@ -714,6 +714,19 @@ int main(){
714714
return 0;
715715
}
716716
```
717+
- A\*算法:一种针对单源点,单终点的启发式算法,是对Dijkstra算法的一个扩展。其结果并不总是准确的,但可以通过调整达到满意的要求。
718+
- 基本思路:
719+
- 待优化公式:对于从源点$s$经过$u$到终点$v$的最短路径估计函数$f[v]$,令$f[v]=d(s,u)+h(u,v)$,其中
720+
- $d(s,u)$是起点到当前点$u$的最短路径估计
721+
- $h(u,v)$是当前点$u$到终点$v$的代价估计,是一个启发式函数
722+
- 每一轮广搜,都选择当前具有最小$d[u]$的节点$v$进行扩展
723+
- 特例:
724+
- 当$h(u,v) = 0$,则该方法就是Dijkstra算法
725+
- 否则,根据应用场景不同,可以选择对应的简单启发函数
726+
- 图中仅允许四向移动:取曼哈顿距离(水平和垂直距离的和)
727+
- 图中允许八向移动:使用对角线距离(水平和垂直距离的最大值)
728+
- 图中允许全向移动:使用欧式距离
729+
- 其他情况,根据$d$和$h$的大小不同,算法有不同的性能和准确性表现
717730
- Bellman-Ford算法:存在负权边的情况下,解决单源最短路径问题。时间复杂度$O(VE)$。
718731
- 基本思路:
719732
1. 初始化最短路径估计
@@ -829,7 +842,7 @@ int main(){
829842
- 和图算法的关系:可以将差分约束转换为最短路径问题。
830843
- 转换为图$G=(V,E)$,其中$V=\\{v_0,v_1,...,v_n\\}$,$E=\\{(v_i,v_j):x_j-x_i\le b_k\mathrm{是一个约束条件}\\}\cup \\{(v_0,v_1),(v_0,v_2),...,(v_0,v_n)\\}$。另外对于约束$x_j-x_i\le b_k$,边$(v_i,v_j)$具有权$w(v_i,v_j)=b_k$,而其他边则有$w(v_0,v_i)=0,1\le i \le n$。
831844
- 引入逻辑顶点$v_0$保证每个顶点均可从$v_0$到达。其他顶点就是未知元,边代表具有约束关系。
832-
- 给定一差分约束系统$Ax\le b$,设$G=(V,E)$未其相应的约束图。如果$G$不包含负权回路。则系统的一个可行解为:
845+
- 给定一差分约束系统$Ax\le b$,设$G=(V,E)$为其相应的约束图。如果$G$不包含负权回路。则系统的一个可行解为:
833846
</br><center>
834847
$x=(\delta(v_0,v_1),\delta(v_0,v_2),...,\delta(v_0,v_n))$
835848
</center>
@@ -875,13 +888,13 @@ int main(){
875888
return 0;
876889
}
877890
```
878-
- Johnson算法:简单版时间复杂度可达$O(VElogV)$,斐波那契堆优化可达$O(V^2logV+VE)$,稀疏图更快
891+
- Johnson算法:简单版时间复杂度可达$O(VElogV)$,[斐波那契堆]({{<relref "/content/post/CSBasic/algo7-tree.md#斐波那契堆">}})优化可达$O(V^2logV+VE)$,稀疏图更快
879892
- 重赋权技术:
880893
- 如果图$G=(V,E)$中所有边的权$w$都是非负的,则执行$|V|$次Dijkstra算法即可。
881-
- 如果存在负权,则重新设定一个权值权值函数$\hat{w}$,满足:路径$p$是加权函数$w$从$u$到$v$的一条最短路径,当且仅当$p$也是加权函数$\hat{w}$从$u$到$v$的一条最短路径;加权函数$\hat{w}$的值永远非负。
894+
- 如果存在负权,则重新设定一个权值函数$\hat{w}$,满足:路径$p$是加权函数$w$从$u$到$v$的一条最短路径,当且仅当$p$也是加权函数$\hat{w}$从$u$到$v$的一条最短路径;加权函数$\hat{w}$的值永远非负。
882895
- 构造方式:
883896
1. 构造新图$G'=(V',E')$,其中$V'=V\cup\\{s\\}$,$E'=E\cup\\{(s,v):v\in V\\}$。即添加一个超级源点$s$,连接所有其他顶点。且扩展原有加权函数$w$,使得$w(s,v)=0,v\in V$。
884-
2. 设函数$h(v)=\delta(s,v)$,易知$h(v)\le h(u)+w(u,v)$,定义新权$\hat{w}(u,v)=w(u,v)+h(u)-h(v)$。注意$u\in V'$。
897+
2. 设函数$h(v)=\delta(s,v)$,即先计算出超级源点到其他点的最短路,由三角不等式易知$h(v)\le h(u)+w(u,v)$,定义新权$\hat{w}(u,v)=w(u,v)+h(u)-h(v)$。注意$u\in V'$。
885898
- 构造正确性的证明思路:
886899
- 新权$\hat{w}(u,v)$的非负性很显然(带入$h$的定义,结合三角不等式)
887900
- $w,\hat{w}$指示同一条最短路径:带入定义,展开$\hat{w}(p)=\sum_{i=1}^{k}\hat{w}(v_{i-1},v_i)$,可知$\hat{w}(p)=w(p)+h(v_0)-h(v_k)$。$h$是固定值,易知两者取得最短路径确实是当且仅当的。

content/post/CSBasic/algo7-tree.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,11 +1104,31 @@ int main(){
11041104
| 合并堆 | $\Theta(n)$ | $\Omega(logn)$ | $\Theta(1)$ |
11051105
| 减小关键字 | $\Theta(logn)$ | $\Theta(logn)$ | $\Theta(1)$ |
11061106
| 删除关键字 | $\Theta(logn)$ | $\Theta(logn)$ | $\mathrm{O}(logn)$ |
1107+
11071108
- 图例
11081109
![二项堆和斐波那契堆示意图](/images/algoSeries/binomailHeap_fiboHeap.svg)
11091110
<center>二项堆(上)和斐波那契堆(下)示意图。注:红色代表标记</center>
11101111

1111-
## 二项堆
1112+
### 基础
1113+
1. 二叉堆:满足堆性质的完全二叉树
1114+
2. [二项堆](https://zh.wikipedia.org/zh-hans/%E4%BA%8C%E9%A1%B9%E5%A0%86):满足堆性质的二项树的集合(一组二项树),且不能有相同度数的二项树。其中二项树的递归定义如下,
1115+
- 树的度数是其根节点的度数。度数为0的二项树只包含一个节点
1116+
- 度数为$k$的二项树有一个根节点,其子节点的度数分别为$k-1$,$k-2$···,$0$
1117+
3. [配对堆](https://oi-wiki.org/ds/pairing-heap/):有如下特点
1118+
- 满足堆性质的多叉树,和其他可合并堆相比,不需要维护额外信息
1119+
- 和常规树不同,通常用兄弟节点链表+子节点指针来表示。更进一步的,可以使用双向兄弟节点链表(首个可以指向父节点)。
1120+
- 合并:直接将两个根中较大的作为根,另一个整体变成其子节点(且插入为首个子节点)。$O(1)$
1121+
- 插入:相当于合并一个单节点堆,且最后加入的子节点,在兄弟节点链表的首位(这样才能$O(1)$)。
1122+
- 查询最大:显然$O(1)$
1123+
- 删除最大:均摊$O(\log{n})$。删除堆顶后,将子节点两两合并,再从尾向首(从旧到新插入)递归两两合并。
1124+
- 增大/减小一个元素的值:此时建议使用双向链表。最差情况下,需要将节点及其子树从树上切下来,并将其重新合并到堆。
1125+
4. 左偏树(Leftist Tree):定义$dist$为节点到自身子树下叶子节点的距离。左偏树就是所有节点的左孩子$dist$,均大于等于右孩子$dist$的树。有如下特点
1126+
- 每个节点额外维护$dist$信息,每个节点的$dist$等于右孩子$dist+1$
1127+
- 合并:合并后需要满足堆性质,和左偏性质。选择根更大的堆根作为新堆的根,并进一步递归合并其右子树和另一个较小的堆。递归合并过程中,如果任意节点右孩子$dist$大于左孩子$dist$,交换左右孩子。$O(log n)$
1128+
- 插入:视作合并堆
1129+
- 删除:先将被删除节点左右子树合并,并插入到原位,随后从原位递归向上更新$dist$,如果违反左偏性质,交换左右孩子。
1130+
1131+
### 二项堆
11121132
- 定义
11131133
- 二项树:一种递归定义的有序树
11141134
- 二项树$B_0$只包含一个节点
@@ -1245,7 +1265,7 @@ int main(){
12451265
1. &emsp; $\mathrm{BINOMAIL\\_HEAP\\_EXTRACT\\_MIN}(H)$
12461266
> 删除关键字相当于,先把该关键字变为无穷小(称为最小元素),再从二项堆中提取最小元素。
12471267

1248-
## 斐波那契堆
1268+
### 斐波那契堆
12491269
- 也是可合并堆的一种,提供可合并堆的各类操作。为了改善时间夫再度,斐波那契堆以均摊分析为指导,维护一个稍显宽松的二项堆。理论上,使用斐波那契堆作为基础数据结构能够改善很多算法(但实际常数因子可能稍高)。
12501270
- 定义:
12511271
- 由一组最小堆有序树构成,但堆中的树并不一定是二项树。
@@ -1291,7 +1311,7 @@ int main(){
12911311
4. 均摊代价最终结果为$O(D(n))$,而由最大度数的上界的性质可知,该值为$O(logn)$。
12921312
1. 减小关键字:
12931313
- 流程:直接修改关键字,如果违反最小堆要求,则和父节点断开链接,当前关键字进入根表,如有需要替换$min[H]$。并对父节点递归向上,检查是否需要级联断开(检查标记)。
1294-
- 势函数变化:假设整个过程中,有$c$个节点被断开,并因此进入根表。则树的数量至多+c,标记的数量至少下降-(c-1)+1(关键字所在节点原来就没标记,最后一个节点增加标记)。此时变化至多为,$\Delta\Phi(H)=((t(H)+c)+2(m(H)-c+2))-(t(H)+2m(H))=4-c。
1314+
- 势函数变化:假设整个过程中,有$c$个节点被断开,并因此进入根表。则树的数量至多+c,标记的数量至少下降-(c-1)+1(关键字所在节点原来就没标记,最后一个节点增加标记)。此时变化至多为,$\Delta\Phi(H)=((t(H)+c)+2(m(H)-c+2))-(t(H)+2m(H))=4-c$
12951315
- 实际代价:修改关键字、断开链接、进入根表$O(1)$。级联删除$O(c)$。
12961316
- 均摊代价:$O(c)+4-c=O(1)$
12971317
1. 删除节点:

0 commit comments

Comments
 (0)