随机算法是由一个雇佣问题引出的:
假如你要雇佣一名新的办公室助理,但是你先前的雇佣尝试都失败了,你打算找一个雇佣代理。雇佣代理每天给你推荐一个应聘者。你面试这个人,然后决定是否雇佣他,同时你需要付给雇佣代理一定的费用,以便面试应聘者。除此之外,雇佣一个人也需要花费一大笔钱,因为你必须辞掉目前的办公室助理,同时付给雇佣代理一大笔中介费。
你承诺:任何时候都找最合适的人来担任这项职务,因此你决定在面试完应聘者之后,如果该应聘者比目前的办公室助理更合适,就会辞掉当前的办公室助理,然后聘用新的。
同时你愿意为该策略付费,但希望能够估算出该费用。
下面先给出HIRE-ASSISTANT过程的伪代码,该伪代码表示雇佣策略。
假设应聘候选人编号为1…n,该过程中,假设你能在面试完应聘者i之后,可以决定应聘者i是否为你目前见过最佳的人选。
HIRE-ASSISTANT
best = 0
for i = 1 to n
interview candidate i
if candidate i is better than best
best = i
hire candidate i
不管是分析费用,还是分析运行时间,采用的方法都是相同的。在任何情形中,我们都是在计算特定基本操作的执行次数。
例如,面试和雇佣都会产生一定的费用,面试费用较低,比如 c i c_i ci,然后雇佣的费用很高,比如 c h c_h ch。假设以用有n个应聘者,m个被雇佣的人,那么这个算法的总费用为 O ( c i n + c h m ) O(c_in+c_hm) O(cin+chm)。由于面试产生的费用总是 c i n c_in cin,因此我们只关注分析 c h m c_hm chm就可以了,也就是雇佣的费用。
我们很容易就知道,上述的策略是极其依赖于输入序列的,不同的输入序列,会产生不同的情况,运行时间也就不同。如果应聘者的质量严格递增的话,你就需要雇佣n次,总的费用就是 O ( c h n ) O(c_hn) O(chn)。这个时候我们就需要讨论平均情况下的运行时间了,我们可以假设每个应聘者是随机出现的。随机出现就意味着所有应聘者之间存在着一个全序关系,那么一共就有 n ! n! n!种排列的可能,每一种序列等概率出现。
但是大多数情况下,我们并不知道应聘者是不是随机出现的,因此这个时候可以在输入和算法之间加入一个随机数生成器,那么这个时候我们可以称这个算法是随机的。(在实践中,大多数编程环境会提供一个伪随机数生成器,它是一个确定性算法,返回值在统计上看起来是随机的。)
当分析一个随机算法的运行时间时,我们以运行时间的期望值来衡量,一个随机算法的运行时间称为期望运行时间;当概率分布发生在算法的输入的时候,我们讨论的是平均情况运行时间。
指示器随机变量
我们经常采用指示器随机变量来进行分析,它为概率和期望之间的转换提供了一个便利的方法。
给定一个样本空间
S
S
S和一个事件
A
A
A,那么事件
A
A
A对应的指示器随机变量
I
(
A
)
I(A)
I(A)定义为:
I
{
A
}
=
{
1
如果A发生
0
如果A不发生
I\{A\}= \left \{ \begin{aligned} &1 \quad \text{如果A发生}\\ &0 \quad \text{如果A不发生} \end{aligned} \right.
I{A}={1如果A发生0如果A不发生
举个例子,假设我们来确定抛一枚标准硬币时 正面朝上的期望次数。样本空间
S
=
{
T
,
F
}
S=\{T \,, F\}
S={T,F},
T
T
T表示正面朝上,
F
F
F表示反面朝上,其中
P
r
{
T
}
=
P
r
{
F
}
=
1
/
2
Pr\{T\}=Pr\{F\}=1/2
Pr{T}=Pr{F}=1/2。接下来定义一个指示器随机变量
X
T
X_T
XT,对应硬币朝上的事件
T
T
T。这个变量是计数抛硬币时正面朝上的次数,如果正面朝上则值为1,否则为0。
X
T
=
I
{
T
}
=
{
1
如果T发生
0
如果F发生
X_T=I\{T\}= \left \{ \begin{aligned} &1 \quad \text{如果T发生} \\ &0 \quad \text{如果F发生} \end{aligned} \right.
XT=I{T}={1如果T发生0如果F发生
所以在一次抛硬币的时候,正面朝上的期望次数就是指示器变量
X
T
X_T
XT的期望值:
E
[
X
T
]
=
E
[
I
{
T
}
]
=
1
∗
P
r
{
T
}
+
0
∗
P
r
{
F
}
=
1
/
2
E[X_T]=E[I\{T\}]=1*Pr\{T\}+0*Pr\{F\}=1/2
E[XT]=E[I{T}]=1∗Pr{T}+0∗Pr{F}=1/2
因此,抛一枚硬币的时候,正面朝上的期望次数为
1
/
2
1/2
1/2。
一个事件A对应的指示器随机变量的期望值等于事件A发生的概率。
给定一个样本空间S和事件A,设 X A = I { A } X_A=I\{A\} XA=I{A},那么 E [ X A ] = P r { A } E[X_A]=Pr\{A\} E[XA]=Pr{A}。(这里就不证明了,证明过程可以去看算法导论)
指示器随机变量看起来很麻烦,但是在分析重复随机试验的时候是非常有用的。
接下来我们使用指示器随机变量来分析这个雇佣问题。
X
X
X对应我们雇佣新的办公室助理的次数,
X
i
X_i
Xi对应第
i
i
i个应聘者被雇佣 这个指示器随机变量:
X
i
=
I
{
i
被聘用
}
=
{
1
应聘者
i
被聘用
0
应聘者
i
未被聘用
X_i=I\{i\text{被聘用} \}= \left \{ \begin{aligned} &1 \quad \text{应聘者}i\text{被聘用} \\ &0 \quad \text{应聘者}i\text{未被聘用} \end{aligned} \right.
Xi=I{i被聘用}={1应聘者i被聘用0应聘者i未被聘用
X = X 1 + X 2 + . . . + X n X=X_1+X_2+...+X_n X=X1+X2+...+Xn
现在我们分析一下HIRE-ASSISTANT执行的效率:
我们在上文已经假设每个应聘者是随机出现的,那么前
i
i
i个应聘者中的任意一个都等可能的是目前最有资格的,那么应聘者
i
i
i比其他应聘者更有资格的概率是
1
/
i
1/i
1/i,也就是应聘者
i
i
i会以
1
/
i
1/i
1/i的概率被雇佣。
E
[
X
i
]
=
P
r
{
应聘者
i
被雇佣
}
=
1
/
i
E[X_i]=Pr\{\text{应聘者}i\text{被雇佣}\}=1/i
E[Xi]=Pr{应聘者i被雇佣}=1/i
E [ X ] = E [ ∑ i = 1 n X i ] = ∑ i = 1 n E [ X i ] (期望的线性性质) = ∑ i = 1 n 1 / i = l n ( n ) + O ( 1 ) (调和级数) \begin{aligned} E[X] &= E[\sum_{i=1}^{n}X_i] \\ &=\sum_{i=1}^n E[X_i] \quad \text{(期望的线性性质)} \\ &=\sum_{i=1}^n 1/i \\ &=ln(n)+O(1) \quad \text{(调和级数)} \end{aligned} E[X]=E[i=1∑nXi]=i=1∑nE[Xi](期望的线性性质)=i=1∑n1/i=ln(n)+O(1)(调和级数)
根据上述分析的结果,我们可以知道,尽管我们需要面试 n n n个人,但平均起来,实际上大约只雇佣了他们之中的 l n ( n ) ln(n) ln(n)个人。
假设应聘者是随机次序的,那么算法HIRE-ASSISTANT总的雇佣费用平均情况下为 O ( c h l n n ) O(c_hlnn) O(chlnn)。
我们之前也说过,我们不能限制输入的随机性,但是我们可以加一个中间层,不管输入的序列是什么,都产生一个均匀随机的序列,这样执行就不依赖于输入了,而是依赖于随机选择。
RANDOMZED-HIRE-ASSISTANT
randomly permute the list of candidates//随机输入
best = 0
for i = 1 to n
interview candidate i
if candidate i is better than best
best = i
hire candidate i
我们只是做了一个简单的改变,很明显看出,这个随机算法的性能,与假设应聘者以随即次数出现所得结果是一样的。因为和之前相比,只是去掉了假设,换成了使用随机算法使输入随机化。
很多随机算法通过给定输入 变换排列以使输入随机化。接下来讨论两种随机方法,并给予证明。(我用我贫瘠的数学知识,我太废了)
第一种:为数组的每个元素 A [ i ] A[i] A[i]赋予一个随机的优先级 P [ i ] P[i] P[i],然后根据优先级对数组 A A A中的元素进行排序。就是我们常说的置换策略。
例如: A = { 1 , 2 , 3 , 4 } A=\{1,2,3,4\} A={1,2,3,4},随机的优先级 P = { 45 , 23 , 96 , 12 } P=\{45, 23, 96, 12\} P={45,23,96,12}(数值越小,优先级越高),那么就会产生一个数组 B = { 4 , 2 , 1 , 3 } B=\{4,2,1,3\} B={4,2,1,3}。这个过程称为PERMUTE-BY-SORTING:
PERMUTE-BY-SORTING
n = A.length
let P[1...n] be a new array
for i = 0 to n
P[i]=RANDOM(1,n^3)
sort A, using P as sort keys
这里使用 n 3 n_3 n3是为了让 P P P中所有的优先级尽可能唯一,现在假设所有的优先级都唯一。
那接下来我们证明这个过程能产生一个均匀随机排列,即该过程可以等可能地产生数字1~n的每一种排列。换种说法就是,需要证明每一种排列发生的概率是 1 n ! \frac{1}{n!} n!1。
先考虑每个元素
A
[
i
]
A[i]
A[i]分配到第
i
i
i个的特殊排列开始,其实就是
A
=
B
A=B
A=B。假设
E
i
E_i
Ei代表元素
A
[
i
]
A[i]
A[i]分配到第
i
i
i个这个事件,则对所有的
i
i
i,该事件发生的概率是:
P
r
{
E
1
∩
E
2
∩
⋅
⋅
⋅
∩
E
n
−
1
∩
E
n
}
=
P
r
{
E
1
}
∗
P
r
{
E
2
∣
E
1
}
∗
P
r
{
E
3
∣
E
2
∩
E
1
}
⋅
⋅
⋅
P
r
{
E
n
∣
E
n
−
1
∩
⋅
⋅
⋅
∩
E
1
}
\begin{aligned} &Pr\{E_1 \cap E_2 \cap···\cap E_{n-1} \cap E_n\} \\ \\ &=Pr\{E_1\}*Pr\{E_2|E_1\}*Pr\{E_3|E_2 \cap E_1\}···Pr\{E_n|E_{n-1}\cap···\cap E_1\} \end{aligned}
Pr{E1∩E2∩⋅⋅⋅∩En−1∩En}=Pr{E1}∗Pr{E2∣E1}∗Pr{E3∣E2∩E1}⋅⋅⋅Pr{En∣En−1∩⋅⋅⋅∩E1}
其中
P
r
{
E
1
}
Pr\{E_1\}
Pr{E1}是从一个
n
n
n个元素的集合中选取一个优先级最高(即数值最小)的概率,那么
P
r
{
E
1
}
=
1
/
n
Pr\{E_1\}=1/n
Pr{E1}=1/n。
P
r
{
E
2
∣
E
1
}
Pr\{E_2|E_1\}
Pr{E2∣E1}就是从剩下的
n
−
1
n-1
n−1个元素中选取一个优先级最高的概率,那么
P
r
{
E
2
∣
E
1
}
=
1
/
(
n
−
1
)
Pr\{E_2|E_1\}=1/(n-1)
Pr{E2∣E1}=1/(n−1)。以此类推,
P
r
{
E
i
∣
E
i
−
1
∩
.
.
.
∩
E
1
}
=
1
/
(
n
−
i
+
1
)
Pr\{E_i|E_{i-1}\cap...\cap E_1\}=1/(n-i+1)
Pr{Ei∣Ei−1∩...∩E1}=1/(n−i+1)。因此,
P
r
{
E
1
∩
E
2
∩
⋅
⋅
⋅
∩
E
n
−
1
∩
E
n
}
=
(
1
n
)
(
1
n
−
1
)
.
.
.
(
1
n
−
i
+
1
)
.
.
.
(
1
2
)
(
1
1
)
=
1
n
!
Pr\{E_1 \cap E_2 \cap···\cap E_{n-1} \cap E_n\}=\bigg( \frac{1}{n} \bigg) \bigg( \frac{1}{n-1} \bigg)...\bigg( \frac{1}{n-i+1} \bigg)...\bigg( \frac{1}{2} \bigg)\bigg( \frac{1}{1} \bigg)=\frac{1}{n!}
Pr{E1∩E2∩⋅⋅⋅∩En−1∩En}=(n1)(n−11)...(n−i+11)...(21)(11)=n!1
现在已经证明这个特殊的排列的发生的概率是
1
n
!
\frac{1}{n!}
n!1,现在需要扩展到其他的排列。如果
E
i
E_i
Ei代表元素
A
[
i
]
A[i]
A[i]分配到第
ξ
(
i
)
\xi(i)
ξ(i)小的事件,
A
[
i
]
A[i]
A[i]被分配到了
j
j
j位置上,上述的证明同样适用。因此,如果要计算得到任何特定排列的概率,该计算与前面的计算完全相同,于是得到此排列的概率为
1
n
!
\frac{1}{n!}
n!1。
这样我们就证明了PERMUTE-BY-SORTING过程可以产生输入的均匀随机的排列。
第二种方法,就是原地排列给定数组。在进行第 i i i次迭代的时候,元素 A [ i ] A[i] A[i]是从元素 A [ i ] A[i] A[i]到 A [ n ] A[n] A[n]中随机选取的,第 i i i次迭代之后, A [ i ] A[i] A[i]不再改变。 RANDOMIZE-IN-PLACE过程的时间复杂度为 O ( n ) O(n) O(n)。
RANDOMIZE-IN-PLACE
n = A.length
for i = 1 to n
swap A[i] with A[RANDOM(i,n)]
接下来我们我们来证明这个过程可以产生均匀随机排列,和之前一样,证明每个排列生成的概率为 1 n ! \frac{1}{n!} n!1,证明方位使用的是循环不变式。
循环不变式和数学中的数学归纳法一样,都是一种演绎推理法,用于理解和证明算法的正确性。
循环不变式不是一个狭义上的一个式子,而是一个命题,在算法的起始状态、运行过程中和算法而技术时始终保持为真的一个命题。
循环不变式的三条性质:
初始化
:循环的第一次迭代之前,它为真。
保持
:如果循环的某次迭代之前它为真,那么下次迭代之前它仍为真。
终止
:在循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法时正确的。注意:循环不变式的前两条性质类似于数学归纳法,证明了一个基本情况和一个归纳步。此外,终止性不同于数学归纳法的做法,在归纳法中,归纳步是无限地使用的,这里当循环终止的时候,归纳随之终止。
对于RANDOMIZE-IN-PLACE,在进行第 i i i迭代之前,对每个可能的 ( i − 1 ) (i-1) (i−1)排列(有 ( n − i + 1 ) ! (n-i+1)! (n−i+1)!种),那么在子数组 A [ 1... i − 1 ] A[1...i-1] A[1...i−1]包含某个 ( i − 1 ) (i-1) (i−1)排列的概率为 ( n − i + 1 ) ! / n ! (n-i+1)!/n! (n−i+1)!/n!。
我们只需要证明,该循环不变式在第1次迭代之前为真,同时循环的每次迭代能够维持此不变式,并且当循环终止时,这个不变式能够得到证明算法正确的有用性质或结果。
初始化: 在第1次迭代之前,此时 i = 1 i=1 i=1,对于循环不变式来说,子数组 A [ 1...0 ] A[1...0] A[1...0]包含0排列的概率为 n ! / n ! = 1 n!/n!=1 n!/n!=1。我们很容易知道,子数组A是一个空数组,0排列也是空的,没有元素,那么该子数组包含0排列的概率必然为1。因此,该循环不变式在第一次迭代之前是为真的。
保持: 我们假设在进行第 i i i次迭代之前,每种可能的 ( i − 1 ) (i-1) (i−1)排列出现在子数组 A [ 1... i − 1 ] A[1...i-1] A[1...i−1]中的概率为 ( n − i + 1 ) ! / n ! (n-i+1)!/n! (n−i+1)!/n!。我们要证明在第 i i i次迭代之后,每种可能的 i i i排列出现在子数组 A [ 1... i ] A[1...i] A[1...i]中的概率为 ( n − i ) ! / n ! (n-i)!/n! (n−i)!/n!,依然保持循环不变式。
我们先考虑一个特殊的 i i i排列, { x 1 , x 2 , . . . , x i } \{x_1,x_2,...,x_i\} {x1,x2,...,xi},这个排列中包含一个 ( i − 1 ) (i-1) (i−1)排列 { x 1 , x 2 , . . . , x i − 1 } \{x_1,x_2,...,x_{i-1}\} {x1,x2,...,xi−1},接着算法会在 A [ i ] A[i] A[i]中放入 x i x_i xi。
设 E 1 E_1 E1表示前 i − 1 i-1 i−1次迭代已经在 A [ 1... i − 1 ] A[1...i-1] A[1...i−1]中构造了 ( i − 1 ) (i-1) (i−1)排列的事件,根据循环不变式, P r { E 1 } = ( n − i + 1 ) ! / n ! Pr\{E_1\}=(n-i+1)!/n! Pr{E1}=(n−i+1)!/n!。
设
E
2
E_2
E2表示第
i
i
i次迭代在位置
A
[
i
]
A[i]
A[i]放置
x
i
x_i
xi的事件。当
E
1
E_1
E1和
E
2
E_2
E2都恰好发生的时候,
{
x
1
,
x
2
,
.
.
.
,
x
i
}
\{x_1,x_2,...,x_i\}
{x1,x2,...,xi}排列会出现在
A
[
1...
i
]
A[1...i]
A[1...i]中,因此:
P
r
{
E
2
∩
E
1
}
=
P
r
{
E
2
∣
E
1
}
∗
P
r
{
E
1
}
=
1
n
−
i
+
1
∗
(
n
−
i
+
1
)
!
n
!
=
(
n
−
i
)
!
n
!
\begin{aligned} Pr\{E_2\cap E_1\}=Pr\{E_2|E_1\}*Pr\{E_1\}=\frac{1}{n-i+1}*\frac{(n-i+1)!}{n!}=\frac{(n-i)!}{n!} \end{aligned}
Pr{E2∩E1}=Pr{E2∣E1}∗Pr{E1}=n−i+11∗n!(n−i+1)!=n!(n−i)!
这符合循环不变式。
终止: 终止的时候, i = n + 1 i=n+1 i=n+1,子数组 A [ 1... n ] A[1...n] A[1...n]是一个给定n排列的概率为 ( n − ( n + 1 ) + 1 ) / n ! = 1 ! / n ! = 1 (n-(n+1)+1)/n!=1!/n!=1 (n−(n+1)+1)/n!=1!/n!=1。
综上所述,RANDOMIZE-IN-PLACE可以产生一个均匀随机排列。
从上面一大堆我们可以知道,随机算法使得算法不再依赖于输入,通常是解决一个问题最简单、最有效的方法。