作者的博客:
CSDN: https://blog.csdn.net/u013031229
博客园: https://www.cnblogs.com/fzyz999/
主站:https://www.pig2earth.top/
课程主要内容
随机算法:如何分析随机算法的成功率
近似算法:近似算法的设计技巧
Big O Notation
一般用大O来衡量算法复杂度。
f ( n ) = O ( g ( n ) ) 代表 ⩽ f(n) = O(g(n)) \text{代表} \leqslant f(n)=O(g(n))代表⩽
f ( n ) = Ω ( g ( n ) ) 代表 ⩾ f(n)=\Omega(g(n)) \text{代表} \geqslant f(n)=Ω(g(n))代表⩾
证明 f ( n ) = Θ ( g ( n ) ) f(n)=\Theta(g(n)) f(n)=Θ(g(n)),等价于证明 f ( n ) ≥ ( g ( n ) ) f(n)\geq(g(n)) f(n)≥(g(n))且 f ( n ) ≤ c ⋅ g ( n ) f(n)\leq c\cdot g(n) f(n)≤c⋅g(n)
排序问题
待排序集合S包含n个数,对其进行排序。
解决排序的思路:
- 找到集合S中的中位数y
- 将S划分为小于y和大于y的两部分 S 1 , S 2 S_1,S_2 S1,S2
- 递归对 S 1 S_1 S1和 S 2 S_2 S2排序
计算复杂性:
- 找到集合S中的中位数y(cn)
- 将S划分为小于y和大于y的两部分 S 1 , S 2 S_1,S_2 S1,S2(n)
- 递归对
S
1
S_1
S1和
S
2
S_2
S2排序
可以得到复杂性为: T ( n ) < = 2 T ( n / 2 ) + ( c + 1 ) n T(n) <= 2T(n/2) + (c+1)n T(n)<=2T(n/2)+(c+1)n
为什么快排没有这么做?
- 找中位数算法的时间复杂度cn中的c比较大,以前大约是20以上,现在可以优化到2左右
- 找中位数的算法编程上较为复杂
假如我们可以以更低的代价求出大小相近的 S 1 S_1 S1和 S 2 S_2 S2?很难做到,难度和直接找中位数差不多。
快排的思路:
- 寻找集合S中随机的一个数y (O(1))
- 将S划分为小于y和大于y的两部分 S 1 , S 2 S_1,S_2 S1,S2(n)
- 递归对 S 1 S_1 S1和 S 2 S_2 S2排序
快排平均复杂度求解思路:y是第k大的概率是一样的(k可能为1…n)据此可以列出递推式:
T
(
n
)
=
∑
k
=
1
n
1
n
(
T
(
k
−
1
)
+
T
(
n
−
k
)
+
n
−
1
)
T(n) = \sum_{k=1}^{n} \frac{1}{n}(T(k-1)+T(n-k)+n-1)
T(n)=k=1∑nn1(T(k−1)+T(n−k)+n−1)
所以
T
(
n
)
=
2
n
(
T
(
0
)
+
T
(
1
)
+
⋯
+
T
(
n
−
1
)
)
+
n
−
1
T(n)=\frac{2}{n}(T(0)+T(1)+\cdots+T(n-1))+n-1
T(n)=n2(T(0)+T(1)+⋯+T(n−1))+n−1
这个递推式可以想办法消掉和n、n-1无关的项。首先对上式做一下变幻:
n
T
(
n
)
=
2
(
T
(
0
)
+
T
(
1
)
+
⋯
+
T
(
n
−
1
)
)
+
n
(
n
−
1
)
nT(n)=2(T(0)+T(1)+\cdots+T(n-1))+n(n-1)
nT(n)=2(T(0)+T(1)+⋯+T(n−1))+n(n−1)
然后,通过相减消掉后面的项:
n
T
(
n
)
−
(
n
−
1
)
T
(
n
−
1
)
=
2
T
(
n
−
1
)
+
n
(
n
−
1
)
−
(
n
−
1
)
(
n
−
2
)
nT(n)-(n-1)T(n-1)=2T(n-1)+n(n-1)-(n-1)(n-2)
nT(n)−(n−1)T(n−1)=2T(n−1)+n(n−1)−(n−1)(n−2)
整理后得到
n
T
(
n
)
=
(
n
+
1
)
T
(
n
−
1
)
+
2
(
n
−
1
)
nT(n)=(n+1)T(n-1)+2(n-1)
nT(n)=(n+1)T(n−1)+2(n−1)
所以
T
(
n
)
n
+
1
=
T
(
n
−
1
)
n
+
2
(
n
−
1
)
n
(
n
+
1
)
\frac{T(n)}{n+1}=\frac{T(n-1)}{n}+\frac{2(n-1)}{n(n+1)}
n+1T(n)=nT(n−1)+n(n+1)2(n−1)
设 q n = T ( n ) n + 1 q_n=\frac{T(n)}{n+1} qn=n+1T(n),所以 q n = ∑ 2 ( n − 1 ) n ( n + 1 ) = Θ ( H n ) = Θ ( log ( n ) ) q_n=\sum \frac{2(n-1)}{n(n+1)}=\Theta(H_n)=\Theta(\log(n)) qn=∑n(n+1)2(n−1)=Θ(Hn)=Θ(log(n))
因此, T ( n ) = Θ ( n log ( n ) ) T(n)=\Theta(n\log(n)) T(n)=Θ(nlog(n))。
这里其实有一个值得注意的点:
我们可以把快排的所需要的次数用一个关于n的函数表达出来:
f ( n ) = f ( X n − 1 ) + f ( n − X n ) + n − 1 f(n)=f(X_n-1)+f(n-X_n)+n-1 f(n)=f(Xn−1)+f(n−Xn)+n−1。
这里需要注意的是, X n X_n Xn其实是一个关于n的随机变量。
因此,求快排的时间复杂度实际上是在求
f
(
n
)
f(n)
f(n)的期望:
T
(
n
)
=
E
(
f
(
n
)
)
=
E
(
f
(
X
n
−
1
)
+
f
(
n
−
X
n
)
+
n
−
1
)
T(n)=E(f(n))=E(f(X_n-1)+f(n-X_n)+n-1)
T(n)=E(f(n))=E(f(Xn−1)+f(n−Xn)+n−1)
由于期望的可加性:
T
(
n
)
=
E
(
f
(
n
)
)
=
E
(
f
(
X
n
−
1
)
)
+
E
(
f
(
n
−
X
n
)
)
+
n
−
1
T(n)=E(f(n))=E(f(X_n-1))+E(f(n-X_n))+n-1
T(n)=E(f(n))=E(f(Xn−1))+E(f(n−Xn))+n−1
接下来就可以得到:
E
(
f
(
X
n
−
1
)
=
∑
k
=
1
n
P
r
(
X
n
=
k
)
E
(
f
(
k
−
1
)
∣
X
n
=
k
)
)
E(f(X_n-1)=\sum_{k=1}^n Pr(Xn=k)E(f(k-1)|X_n=k))
E(f(Xn−1)=∑k=1nPr(Xn=k)E(f(k−1)∣Xn=k))
接下来我们进行了一个假设:f(k-1)和 X n = k X_n=k Xn=k无关
方法2:
设置一个随机变量:
X
i
j
=
1
(
i
−
t
h
会和
j
−
t
h
进行一次比较
)
,
否则
X
i
j
=
0
X_{ij}=1(i-th\text{会和}j-th\text{进行一次比较}),\text{否则}X_{ij}=0
Xij=1(i−th会和j−th进行一次比较),否则Xij=0
所以时间复杂度= E ( ∑ 1 ⩽ i < j ⩽ n X i j ) ) = ∑ 1 ⩽ i < j ⩽ n P i j E(\sum_{1\leqslant i<j\leqslant n} X_{ij}))=\sum_{1\leqslant i<j\leqslant n} P_{ij} E(∑1⩽i<j⩽nXij))=∑1⩽i<j⩽nPij
这一步利用了E的可加性,它不要求每个事件间是相互独立的。所以问题转换成了求解 P i j P{ij} Pij。也就是第i大的元素和第j大的元素做比较的概率。
这里可以先从两个简单的求一下作为启发: P i , i + 1 P_{i,i+1} Pi,i+1和 P 1 , n P_{1,n} P1,n
P i , i + 1 = 1 P_{i,i+1}=1 Pi,i+1=1因为,假设除了 a i a_i ai和 a i + 1 a_{i+1} ai+1以外,所有的关系我们都知道。那么我们就会得到 a 1 < a 2 < ⋯ < ( a i 和 a i + 1 ) < ⋯ < a n a_1<a_2<\cdots<(a_i\text{和}a_{i+1})<\cdots<a_n a1<a2<⋯<(ai和ai+1)<⋯<an。不进行比较,我们永远也不知道 a i a_i ai和 a i + 1 a_{i+1} ai+1哪个大。所以这个比较是一定要做的。
P 1 , n = 2 n P_{1,n}=\frac{2}{n} P1,n=n2原因是:在快速排序的第一轮,我们肯定会把最小和最大的分到两个集合里面分别去求。此后,由于最大和最小的元素在两个集合里面,所以他们就再也没有机会进行比较了。此时 X 1 , n = 0 X_{1,n}=0 X1,n=0。因此,只有在第一轮才有可能比较最小和最大的数。其它情况都不可能。
第一轮,假设选取的数是第3小的,则会:
(1 2) | 3 | (4 .. n)
第1和第2小的在一个集合,后面的在一个集合
这种情况下1和n肯定不会被比较。
当选取的数是第2小到第n-1小的时候同理。
所以只有一种情况1和n会被比较,也就是1或者n被选为那个分割的数的时候。
所以
P
i
,
j
P_{i,j}
Pi,j如何求?以i=3,j=7为例,看快排的过程一共有三种情况。
P
i
j
=
2
j
−
i
+
1
P_{ij}=\frac{2}{j-i+1}
Pij=j−i+12
第一轮,假设选取的数是第3小或者第7小的的,则会有三种可能:
最后求和,时间复杂度= ∑ 1 ⩽ i < j ⩽ n P i j = ∑ 1 ⩽ i < j ⩽ n 2 j − i + 1 \sum_{1\leqslant i<j\leqslant n} P_{ij}=\sum_{1\leqslant i<j\leqslant n} \frac{2}{j-i+1} ∑1⩽i<j⩽nPij=∑1⩽i<j⩽nj−i+12
进一步推导得到:
2
∑
1
⩽
i
<
j
⩽
n
−
1
(
1
2
+
1
3
+
⋯
+
1
n
−
i
+
1
)
=
2
∑
1
⩽
i
<
j
⩽
n
−
1
(
H
n
−
i
+
1
−
1
)
2\sum_{1\leqslant i<j\leqslant n-1} (\frac{1}{2}+\frac{1}{3}+\cdots+\frac{1}{n-i+1})=2\sum_{1\leqslant i<j\leqslant n-1}(H_{n-i+1}-1)
2∑1⩽i<j⩽n−1(21+31+⋯+n−i+11)=2∑1⩽i<j⩽n−1(Hn−i+1−1)
这个函数正好是 θ ( n l o g n ) \theta(nlogn) θ(nlogn)
最小割
一个无向图G(V,E),选出一组最少的边,把整个图切成两个不连通的分量。
算法:
- 随机选一条边
- 把这条边的的合并在一起
- 删除自环
- 重复1-3步,指导只有两个顶点
- 剩余的边组成的是一个候选的最小割
orig
b - c
/ |
a |
\ |
d _ e
step1: 假设随机选到了a<->b这条边
ab - c
/ | |
| | |
\ | |
d __ e
step2: 假设随机选了ab<->c这条边
d
/ |
abc |
\ |
e
成功概率为多少? O ( 1 n 2 ) O(\frac{1}{n^2}) O(n21)
问题:如何证明
(
1
−
1
n
2
)
n
2
=
θ
(
1
)
(1-\frac{1}{n^2})^{n^2}=\theta(1)
(1−n21)n2=θ(1)?
由于
1
−
1
n
2
≤
e
−
1
n
2
1-\frac{1}{n^2} \leq e^{-\frac{1}{n^2}}
1−n21≤e−n21且
n
n
n是个正整数,所以
(
1
−
1
n
2
)
n
2
≤
(
e
−
1
n
2
)
n
2
=
e
−
1
=
Θ
(
1
)
(1-\frac{1}{n^2})^{n^2} \leq (e^{-\frac{1}{n^2}})^{n^2}=e^{-1}=\Theta(1)
(1−n21)n2≤(e−n21)n2=e−1=Θ(1)
实现的复杂度?单次需要 O ( n 2 ) O(n^2) O(n2),想将成功率提高到一个常数,需要重复 n 2 n^2 n2次