基础知识 算法基础 增量方法 插入排序1 2 3 4 5 6 7 8 INSERTION-SORT(A) for j = 2 to A.length key = A[j] i = j - 1 while i > 0 and A[j] > key A[i + 1] = A[i] i = i - 1 A[i + 1] = key
循环不变式循环不变式主要用来帮助我们理解算法的正确性。关于循环不变式,我们必须证明三条性质:
初始化 :循环的第一次迭代之前,它为真保持 :如果循环的某次迭代之前它为真,那么下次迭代之前它仍然为真终止 :在循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法是正确的 分治法分治法的思想:将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解 分治模式在每层递归时都有三个步骤:
分解 原问题为若干子问题,这些子问题是原问题的规模较小的实例解决 这些子问题,递归地求解各子问题。然而,若子问题的规模足够小,则直接求解合并 这些子问题的解成原问题的解 归并排序分解 :分解待排序的n个元素的序列成各具n/2个元素的两个子序列解决 :使用归并排序递归地排序两个子序列合并 :合并两个已排序的子序列以产生已排序的答案我们通过调用一个辅助过程 MERGE(A, p, q, r)
来完成合并,其中A A A 是一个数组, p p p 、q q q 和r r r 是数组下标,满足p ≤ q < r p \leq q < r p ≤ q < r 。该过程假设子数组A [ p . . q ] A[p..q] A [ p . . q ] 和A [ q + 1.. r ] A[q+1..r] A [ q + 1 . . r ] 都已排好序 过程 MERGE
需要Θ ( n ) \Theta(n) Θ ( n ) 的时间,其中n = r − p + 1 n=r-p+1 n = r − p + 1 是带合并元素的总数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 MERGE(A, p, q, r) n1 = q - p + 1 n2 = r - q let L[1..n1 + 1] and R[1..n2 + 1] be new arrays for i = 1 to n1 L[i] = A[p + i - 1] for j = 1 to n2 R[j] = A[q + j] L[n1 + 1] = ∞ R[n2 + 1] = ∞ i = 1 j = 1 for k = p to r if L[i] <= R[j] A[k] = L[i] i = i + 1 else A[k] = R[j] j = j + 1
1 2 3 4 5 6 MERGE-SORT(A, p, r) if p < r q = ⌊(p + r)/2⌋ MERGE-SORT(A, p, q) MERGE-SORT(A, q+1, r) MERGE(A, p, q, r)
分析分治算法假设T ( n ) T(n) T ( n ) 是规模为n n n 的一个问题的运行时间:
若问题规模足够小,则直接求解需要常量时间Θ ( 1 ) \Theta(1) Θ ( 1 ) 假设把原问题分解为a a a 个子问题,每个子问题的规模是原问题的1 / b 1/b 1 / b 。为了求解一个规模为n / b n/b n / b 的子问题,需要T ( n / b ) T(n/b) T ( n / b ) 的时间,所以需要a T ( n / b ) aT(n/b) a T ( n / b ) 的时间来求解a a a 个子问题 分解问题成子问题需时间D ( n ) D(n) D ( n ) 合并子问题的解成原问题的解需时间C ( n ) C(n) C ( n ) 那么得到递归式:
T ( n ) = { Θ ( 1 ) n ≤ c a T ( n / b ) + D ( n ) + C ( n ) o t h e r T(n) = \begin{cases} \Theta(1) \qquad & n \leq c \\ aT(n/b)+D(n)+C(n) \qquad & other \end{cases} T ( n ) = { Θ ( 1 ) a T ( n / b ) + D ( n ) + C ( n ) n ≤ c o t h e r
对于归并排序,其最坏情况运行时间T ( n ) T(n) T ( n ) 的递归式:
T ( n ) = { Θ ( 1 ) n = 1 2 T ( n / 2 ) + Θ ( n ) n > 1 = Θ ( n l g n ) T(n) = \begin{cases} \Theta(1) \qquad & n = 1 \\ 2T(n/2)+\Theta(n) \qquad & n>1 \end{cases} = \Theta(nlgn) T ( n ) = { Θ ( 1 ) 2 T ( n / 2 ) + Θ ( n ) n = 1 n > 1 = Θ ( n l g n )
函数增长 渐进记号 Θ \Theta Θ 记号对于一个给定的函数g ( n ) g(n) g ( n ) ,用Θ ( g ( n ) ) \Theta(g(n)) Θ ( g ( n ) ) 来表示以下函数的集合:Θ ( g ( n ) ) = { f ( n ) : 存在正常量 c 1 、 c 2 和 n 0 ,使得对所有 n ≥ n 0 ,有 0 ≤ c 1 g ( n ) ≤ f ( n ) ≤ c 2 g ( n ) } \Theta(g(n))=\{f(n):\text{存在正常量}c_1\text{、}c_2\text{和}n_0\text{,使得对所有}n \geq n_0\text{,有}0\leq c_1g(n) \leq f(n) \leq c_2g(n)\} Θ ( g ( n ) ) = { f ( n ) : 存在正常量 c 1 、 c 2 和 n 0 ,使得对所有 n ≥ n 0 ,有 0 ≤ c 1 g ( n ) ≤ f ( n ) ≤ c 2 g ( n ) }
我们称g ( n ) g(n) g ( n ) 是f ( n ) f(n) f ( n ) 的一个渐进紧确界
O O O 记号对于一个给定的函数g ( n ) g(n) g ( n ) ,用O ( g ( n ) ) O(g(n)) O ( g ( n ) ) 来表示以下函数的集合:O ( g ( n ) ) = { f ( n ) : 存在正常量 c 和 n 0 ,使得对所有 n ≥ n 0 ,有 0 ≤ f ( n ) ≤ c g ( n ) } O(g(n))=\{f(n):\text{存在正常量}c\text{和}n_0\text{,使得对所有}n \geq n_0\text{,有}0\leq f(n) \leq cg(n)\} O ( g ( n ) ) = { f ( n ) : 存在正常量 c 和 n 0 ,使得对所有 n ≥ n 0 ,有 0 ≤ f ( n ) ≤ c g ( n ) }
我们称g ( n ) g(n) g ( n ) 是f ( n ) f(n) f ( n ) 的一个渐进上界
Ω \Omega Ω 记号对于一个给定的函数g ( n ) g(n) g ( n ) ,用Ω ( g ( n ) ) \Omega(g(n)) Ω ( g ( n ) ) 来表示以下函数的集合:Ω ( g ( n ) ) = { f ( n ) : 存在正常量 c 和 n 0 ,使得对所有 n ≥ n 0 ,有 0 ≤ c g ( n ) ≤ f ( n ) } \Omega(g(n))=\{f(n):\text{存在正常量}c\text{和}n_0\text{,使得对所有}n \geq n_0\text{,有}0\leq cg(n) \leq f(n)\} Ω ( g ( n ) ) = { f ( n ) : 存在正常量 c 和 n 0 ,使得对所有 n ≥ n 0 ,有 0 ≤ c g ( n ) ≤ f ( n ) }
我们称g ( n ) g(n) g ( n ) 是f ( n ) f(n) f ( n ) 的一个渐进下界
o o o 记号对于一个给定的函数g ( n ) g(n) g ( n ) ,用o ( g ( n ) ) o(g(n)) o ( g ( n ) ) 来表示以下函数的集合:o ( g ( n ) ) = { f ( n ) : 存在正常量 c 和 n 0 ,使得对所有 n ≥ n 0 ,有 0 ≤ f ( n ) < c g ( n ) } o(g(n))=\{f(n):\text{存在正常量}c\text{和}n_0\text{,使得对所有}n \geq n_0\text{,有}0\leq f(n) < cg(n)\} o ( g ( n ) ) = { f ( n ) : 存在正常量 c 和 n 0 ,使得对所有 n ≥ n 0 ,有 0 ≤ f ( n ) < c g ( n ) } 我们称g ( n ) g(n) g ( n ) 是f ( n ) f(n) f ( n ) 的一个非渐进紧确上界
ω \omega ω 记号对于一个给定的函数g ( n ) g(n) g ( n ) ,用ω ( g ( n ) ) \omega(g(n)) ω ( g ( n ) ) 来表示以下函数的集合:ω ( g ( n ) ) = { f ( n ) : 存在正常量 c 和 n 0 ,使得对所有 n ≥ n 0 ,有 0 ≤ c g ( n ) < f ( n ) } \omega(g(n))=\{f(n):\text{存在正常量}c\text{和}n_0\text{,使得对所有}n \geq n_0\text{,有}0\leq cg(n) < f(n)\} ω ( g ( n ) ) = { f ( n ) : 存在正常量 c 和 n 0 ,使得对所有 n ≥ n 0 ,有 0 ≤ c g ( n ) < f ( n ) } 我们称g ( n ) g(n) g ( n ) 是f ( n ) f(n) f ( n ) 的一个非渐进紧确下界
比较各种函数传递性f ( n ) = Θ ( g ( n ) ) ∧ g ( n ) = Θ ( h ( n ) ) → f ( n ) = Θ ( h ( n ) ) f(n) = \Theta(g(n)) \land g(n) = \Theta(h(n)) \rightarrow f(n) = \Theta(h(n)) f ( n ) = Θ ( g ( n ) ) ∧ g ( n ) = Θ ( h ( n ) ) → f ( n ) = Θ ( h ( n ) ) f ( n ) = O ( g ( n ) ) ∧ g ( n ) = O ( h ( n ) ) → f ( n ) = O ( h ( n ) ) f(n) = O(g(n)) \land g(n) = O(h(n)) \rightarrow f(n) = O(h(n)) f ( n ) = O ( g ( n ) ) ∧ g ( n ) = O ( h ( n ) ) → f ( n ) = O ( h ( n ) ) f ( n ) = Ω ( g ( n ) ) ∧ g ( n ) = Ω ( h ( n ) ) → f ( n ) = Ω ( h ( n ) ) f(n) = \Omega(g(n)) \land g(n) = \Omega(h(n)) \rightarrow f(n) = \Omega(h(n)) f ( n ) = Ω ( g ( n ) ) ∧ g ( n ) = Ω ( h ( n ) ) → f ( n ) = Ω ( h ( n ) ) f ( n ) = o ( g ( n ) ) ∧ g ( n ) = o ( h ( n ) ) → f ( n ) = o ( h ( n ) ) f(n) = o(g(n)) \land g(n) = o(h(n)) \rightarrow f(n) = o(h(n)) f ( n ) = o ( g ( n ) ) ∧ g ( n ) = o ( h ( n ) ) → f ( n ) = o ( h ( n ) ) f ( n ) = ω ( g ( n ) ) ∧ g ( n ) = ω ( h ( n ) ) → f ( n ) = ω ( h ( n ) ) f(n) = \omega(g(n)) \land g(n) = \omega(h(n)) \rightarrow f(n) = \omega(h(n)) f ( n ) = ω ( g ( n ) ) ∧ g ( n ) = ω ( h ( n ) ) → f ( n ) = ω ( h ( n ) ) 自反性f ( n ) = Θ ( f ( n ) ) f(n) = \Theta(f(n)) f ( n ) = Θ ( f ( n ) ) f ( n ) = O ( f ( n ) ) f(n) = O(f(n)) f ( n ) = O ( f ( n ) ) f ( n ) = Ω ( f ( n ) ) f(n) = \Omega(f(n)) f ( n ) = Ω ( f ( n ) ) 对称性f ( n ) = Θ ( g ( n ) ) ↔ g ( n ) = Θ ( f ( n ) ) f(n) = \Theta(g(n)) \leftrightarrow g(n) = \Theta(f(n)) f ( n ) = Θ ( g ( n ) ) ↔ g ( n ) = Θ ( f ( n ) ) 转置对称性f ( n ) = O ( g ( n ) ) ↔ g ( n ) = Ω ( f ( n ) ) f(n) = O(g(n)) \leftrightarrow g(n) = \Omega(f(n)) f ( n ) = O ( g ( n ) ) ↔ g ( n ) = Ω ( f ( n ) ) f ( n ) = o ( g ( n ) ) ↔ g ( n ) = ω ( f ( n ) ) f(n) = o(g(n)) \leftrightarrow g(n) = \omega(f(n)) f ( n ) = o ( g ( n ) ) ↔ g ( n ) = ω ( f ( n ) ) 类比f ( n ) = O ( g ( n ) ) → a ≤ b f(n) = O(g(n)) \rightarrow a \leq b f ( n ) = O ( g ( n ) ) → a ≤ b f ( n ) = Ω ( g ( n ) ) → a ≥ b f(n) = \Omega(g(n)) \rightarrow a \geq b f ( n ) = Ω ( g ( n ) ) → a ≥ b f ( n ) = Θ ( g ( n ) ) → a = b f(n) = \Theta(g(n)) \rightarrow a = b f ( n ) = Θ ( g ( n ) ) → a = b f ( n ) = o ( g ( n ) ) → a < b f(n) = o(g(n)) \rightarrow a < b f ( n ) = o ( g ( n ) ) → a < b f ( n ) = ω ( g ( n ) ) → a > b f(n) = \omega(g(n)) \rightarrow a > b f ( n ) = ω ( g ( n ) ) → a > b 标准记号与常用函数 多项式对于一个d d d 次渐进正的多项式p ( n ) p(n) p ( n ) ,有:
p ( n ) = Θ ( n d ) p(n)=\Theta(n^d) p ( n ) = Θ ( n d )
指数任意底大于1的指数函数比任意多项式函数增长得快:
n b = o ( a n ) , a > 1 n^b = o(a^n),a>1 n b = o ( a n ) , a > 1
对数对所有实数a > 0 a>0 a > 0 ,b > 0 b>0 b > 0 ,c > 0 c>0 c > 0 和n n n ,有:
a = b l o g b a a = b^{log_b a} a = b l o g b a
l o g c ( a b ) = l o g c a + l o g c b log_c(ab) = log_c a + log_c b l o g c ( a b ) = l o g c a + l o g c b
l o g b a n = n l o g b a log_b a^n = n log_b a l o g b a n = n l o g b a
l o g b a = l o g c a l o g c b log_b a = \frac{log_c a}{log_c b} l o g b a = l o g c b l o g c a
l o g b ( 1 / a ) = − l o g b a log_b(1/a) = -log_b a l o g b ( 1 / a ) = − l o g b a
l o g b a = 1 l o g a b log_b a = \frac{1}{log_a b} l o g b a = l o g a b 1
a l o g b c = c l o g b a a^{log_b c} = c^{log_b a} a l o g b c = c l o g b a
任意正的多项式函数都比任意多底数函数增长得快:
l g b n = o ( n a ) lg^b n = o(n^a) l g b n = o ( n a )
阶乘斯特林近似公式 :
n ! = 2 π n ( n e ) n ( 1 + Θ ( 1 n ) ) n!=\sqrt{2\pi n}(\frac{n}{e})^n(1+\Theta(\frac{1}{n})) n ! = 2 π n ( e n ) n ( 1 + Θ ( n 1 ) )
n ! = 2 π n ( n e ) n e a n , 1 12 n + 1 < a n < 1 12 n n!=\sqrt{2\pi n}(\frac{n}{e})^n e^{a_n}, \frac{1}{12n+1}<a_n<\frac{1}{12n} n ! = 2 π n ( e n ) n e a n , 1 2 n + 1 1 < a n < 1 2 n 1
给出了一个更紧确的上界和下界:
n ! = o ( n n ) n!=o(n^n) n ! = o ( n n )
n ! = ω ( 2 n ) n!=\omega(2^n) n ! = ω ( 2 n )
l g ( n ! ) = Θ ( n l g n ) lg(n!)=\Theta(nlgn) l g ( n ! ) = Θ ( n l g n )
多重函数假设f ( n ) f(n) f ( n ) 为实数集上的一个函数,对非负整数i i i ,我们递归地定义:
f ( i ) ( n ) = { n i = 1 f ( f ( i − 1 ) ( n ) ) i > 1 f^{(i)}(n) = \begin{cases} n \qquad & i = 1 \\ f(f^{(i-1)}(n)) \qquad & i>1 \end{cases} f ( i ) ( n ) = { n f ( f ( i − 1 ) ( n ) ) i = 1 i > 1
多重对数函数定义多重对数函数为:
l g ∗ n = m i n { i ≥ 0 : l g ( i ) n ≤ 1 } lg*n=min\{i \geq 0:lg^{(i)}n \leq 1\} l g ∗ n = m i n { i ≥ 0 : l g ( i ) n ≤ 1 }
多重对数是一个增长非常慢 的函数:
l g ∗ 2 = 1 lg*2=1 l g ∗ 2 = 1 l g ∗ 4 = 2 lg*4=2 l g ∗ 4 = 2 l g ∗ 16 = 3 lg*16=3 l g ∗ 1 6 = 3 l g ∗ 65536 = 4 lg*65536=4 l g ∗ 6 5 5 3 6 = 4 l g ∗ ( 2 65536 ) = 5 lg*(2^{65536})=5 l g ∗ ( 2 6 5 5 3 6 ) = 5 分治策略代入法 :我们猜测一个界,然后用数学归纳法证明这个界是正确的递归树法 :将递归式转换为一棵树,其结点表示不同层次的递归调用产生的代价。然后采用边界和技术来求解递归式主方法 :可求解形如下面公式的递归式的界:T ( n ) = a T ( n / b ) + f ( n ) , a ≥ 1 , b > 1 T(n)=aT(n/b)+f(n),a\geq 1,b>1 T ( n ) = a T ( n / b ) + f ( n ) , a ≥ 1 , b > 1
最大子数组问题寻找A的和最大的非空连续子数组,这样的连续子数组为最大子数组
使用分治策略的求解方法A [ l o w . . h i g h ] A[low..high] A [ l o w . . h i g h ] 的任何连续子数组A [ i . . j ] A[i..j] A [ i . . j ] 所处的位置必然是一下三种情况之一:
完全位于子数组A [ l o w . . m i d ] A[low..mid] A [ l o w . . m i d ] 中,因此l o w ≤ i ≤ j ≤ m i d low \leq i \leq j \leq mid l o w ≤ i ≤ j ≤ m i d 完全位于子数组A [ m i d + 1.. h i g h ] A[mid+1..high] A [ m i d + 1 . . h i g h ] 中,因此m i d < i ≤ j ≤ h i g h mid < i \leq j \leq high m i d < i ≤ j ≤ h i g h 跨越了中点,因此l o w ≤ i ≤ m i d < j ≤ h i g h low \leq i \leq mid < j \leq high l o w ≤ i ≤ m i d < j ≤ h i g h 过程 FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
接收数组A A A 和下标l o w low l o w ,m i d mid m i d 和h i g h high h i g h 为输入,返回一个下标元组划定跨越中点的最大子数组的边界,并返回最大子数组中值的和:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high) left-sum = -∞ sum = 0 for i = mid downto low sum = sum + A[i] if sum > left-sum left-sum = sum max-left = i right-sum = -∞ sum = 0 for j = mid + 1 to high sum = sum + A[j] if sum > right-sum right-sum = sum max-right = j return (max-left, max-right, left-sum + right-sum)
1 2 3 4 5 6 7 8 9 10 11 12 FIND-MAXINUM-SUBARRAY(A, low, high) if high == low return (low, high, A[low]) else mid = ⌊(low + high)/2⌋ (left-low, right-high, left-sum) = FIND-MAXINUM-SUBARRAY(A, low, mid) (right-low, right-high, right-sum) = FIND-MAXINUM-SUBARRAY(A, mid+1, high) (cross-low, cross-high, cross-zum) = FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high) if left-sum >= right-sum and left-sum >= cross-sum return (left-low, left-high, left-sum) elseif right-sum >= left-sum and right-sum >= cross-sum return (right-low, right-high, right-sum) else return (cross-low, cross-high, cross-sum)
分治算法的分析FIND-MAXINUM-SUBARRAY
运行时间T ( n ) T(n) T ( n ) 的递归式:
T ( n ) = { Θ ( 1 ) n = 1 2 T ( n / 2 ) + Θ ( n ) n > 1 = Θ ( n l g n ) T(n) = \begin{cases} \Theta(1) \qquad & n = 1 \\ 2T(n/2) + \Theta(n) \qquad & n>1 \end{cases} = \Theta(nlgn) T ( n ) = { Θ ( 1 ) 2 T ( n / 2 ) + Θ ( n ) n = 1 n > 1 = Θ ( n l g n )
矩阵乘法的Strassen算法 基础的矩阵乘法复杂度 Θ ( n 3 ) \Theta (n^3) Θ ( n 3 )
1 2 3 4 5 6 7 8 9 SQUARE-MATRIX-MULTIPLY(A, B) n = A.rows let C be a new n * n matrix for i = 1 to n for j = 1 to n c_ij = 0 for k = 1 to n c_ij = c_ij + a_ik * b_kj return C
简单的分治算法假定将A A A ,B B B 和C C C 均分解为4个n / 2 × n / 2 n/2 \times n/2 n / 2 × n / 2 的子矩阵:
A = [ A 11 A 12 A 21 A 22 ] , B = [ B 11 B 12 B 21 B 22 ] , C = [ C 11 C 12 C 21 C 22 ] A = \begin{bmatrix} A_{11} & A_{12} \\ A_{21} & A_{22} \end{bmatrix} , B = \begin{bmatrix} B_{11} & B_{12} \\ B_{21} & B_{22} \end{bmatrix} , C = \begin{bmatrix} C_{11} & C_{12} \\ C_{21} & C_{22} \end{bmatrix} A = [ A 1 1 A 2 1 A 1 2 A 2 2 ] , B = [ B 1 1 B 2 1 B 1 2 B 2 2 ] , C = [ C 1 1 C 2 1 C 1 2 C 2 2 ]
可以将公式C = A ⋅ B C = A \cdot B C = A ⋅ B 改写为:
[ C 11 C 12 C 21 C 22 ] = [ A 11 A 12 A 21 A 22 ] ⋅ [ B 11 B 12 B 21 B 22 ] \begin{bmatrix} C_{11} & C_{12} \\ C_{21} & C_{22} \end{bmatrix} = \begin{bmatrix} A_{11} & A_{12} \\ A_{21} & A_{22} \end{bmatrix} \cdot \begin{bmatrix} B_{11} & B_{12} \\ B_{21} & B_{22} \end{bmatrix} [ C 1 1 C 2 1 C 1 2 C 2 2 ] = [ A 1 1 A 2 1 A 1 2 A 2 2 ] ⋅ [ B 1 1 B 2 1 B 1 2 B 2 2 ]
1 2 3 4 5 6 7 8 9 10 SQUARE-MATRIX-MULTIPLY-RECURSIVE(A, B) n = A.rows let C be a new n * n matrix if n == 1 c_11 = a_11 * b_11 else partition A, B and C as in equations C_11 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_11, B_11) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_12, B_21) C_12 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_11, B_12) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_12, B_22) C_21 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_21, B_11) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_22, B_21) C_22 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_21, B_12) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_22, B_22)
SQUARE-MATRIX-MULTIPLY-RECURSIVE
运行时间T ( n ) T(n) T ( n ) 的递归式:
T ( n ) = { Θ ( 1 ) n = 1 8 T ( n / 2 ) + Θ ( n 2 ) n > 1 = Θ ( n 3 ) T(n) = \begin{cases} \Theta(1) \qquad & n = 1 \\ 8T(n/2) + \Theta(n^2) \qquad & n>1 \end{cases} = \Theta(n^3) T ( n ) = { Θ ( 1 ) 8 T ( n / 2 ) + Θ ( n 2 ) n = 1 n > 1 = Θ ( n 3 )
Strassen方法Strassen
运行时间T ( n ) T(n) T ( n ) 的递归式:
T ( n ) = { Θ ( 1 ) n = 1 7 T ( n / 2 ) + Θ ( n 2 ) n > 1 = Θ ( n l g 7 ) T(n) = \begin{cases} \Theta(1) \qquad & n = 1 \\ 7T(n/2) + \Theta(n^2) \qquad & n>1 \end{cases} = \Theta(n^{lg_7}) T ( n ) = { Θ ( 1 ) 7 T ( n / 2 ) + Θ ( n 2 ) n = 1 n > 1 = Θ ( n l g 7 )
代入法求解递归式代入法 求解递归式分为两步:
猜测解的形式 用数学归纳法求出解的常数,并证明解是正确的 例如,确定下列递归式的上界:
T ( n ) = 2 T ( ⌊ n / 2 ⌋ ) + n T(n)=2T(\lfloor n/2 \rfloor)+n T ( n ) = 2 T ( ⌊ n / 2 ⌋ ) + n
猜测其解为T ( n ) = O ( n l g n ) T(n)=O(nlgn) T ( n ) = O ( n l g n ) 首先假定次上界对所有正数m < n m<n m < n 都成立,特别对于m = ⌊ n / 2 ⌋ m=\lfloor n/2 \rfloor m = ⌊ n / 2 ⌋ ,有T ( ⌊ n / 2 ⌋ ) ≤ c ⌊ n / 2 ⌋ l g ( ⌊ n / 2 ⌋ ) T(\lfloor n/2 \rfloor) \leq c\lfloor n/2 \rfloor lg(\lfloor n/2 \rfloor) T ( ⌊ n / 2 ⌋ ) ≤ c ⌊ n / 2 ⌋ l g ( ⌊ n / 2 ⌋ ) 。将其带入递归式,有:
T ( n ) ≤ 2 ( c ⌊ n / 2 ⌋ l g ( ⌊ n / 2 ⌋ ) ) + n ≤ c n l g ( n / 2 ) + n = c n l g n − c n l g 2 + n = c n l g n − c n + n ≤ c n l g n T(n) \leq 2(c\lfloor n/2 \rfloor lg(\lfloor n/2 \rfloor)) + n \leq cn lg(n/2) + n \\ = cnlgn-cnlg2+n\\ =cnlgn-cn+n\\ \leq cnlgn T ( n ) ≤ 2 ( c ⌊ n / 2 ⌋ l g ( ⌊ n / 2 ⌋ ) ) + n ≤ c n l g ( n / 2 ) + n = c n l g n − c n l g 2 + n = c n l g n − c n + n ≤ c n l g n 其中,只要c ≤ 1 c \leq 1 c ≤ 1 ,最后一步都会成立
递归树求解递归式在递归树 中,每个结点表示一个单一子问题的代价,子问题对应某次递归函数调用 我们将树中的每层中的代价求和,得到每层代价,然后将所有层的代价求和,得到所有层次的递归调用的总代价
主方法求解递归式令a ≤ 1 a\leq 1 a ≤ 1 和b > 1 b>1 b > 1 是常数,f ( n ) f(n) f ( n ) 是一个函数,T ( n ) T(n) T ( n ) 是定义在非负整数上的递归式:
T ( n ) = a T ( n / b ) + f ( n ) T(n)=aT(n/b)+f(n) T ( n ) = a T ( n / b ) + f ( n )
若对某个常数ϵ > 0 \epsilon >0 ϵ > 0 有f ( n ) = O ( n l o g b a − ϵ ) f(n)=O(n^{log_ba-\epsilon}) f ( n ) = O ( n l o g b a − ϵ ) ,则T ( n ) = Θ ( n l o g b a ) T(n)=\Theta(n^{log_ba}) T ( n ) = Θ ( n l o g b a ) 若对于某个常数k ≥ 0 k\geq 0 k ≥ 0 有f ( n ) = Θ ( n l o g b a l g k n ) f(n)=\Theta(n^{log_ba}lg^kn) f ( n ) = Θ ( n l o g b a l g k n ) ,则T ( n ) = Θ ( n l o g b a l g k + 1 n ) T(n)=\Theta(n^{log_ba}lg^{k+1}n) T ( n ) = Θ ( n l o g b a l g k + 1 n ) 若对某个常数ϵ > 0 \epsilon >0 ϵ > 0 有f ( n ) = Ω ( n l o g b a + ϵ ) f(n)=\Omega(n^{log_ba+\epsilon}) f ( n ) = Ω ( n l o g b a + ϵ ) ,且对某个常数c < 1 c<1 c < 1 和所有足够大的n n n 有a f ( n / b ) ≤ c f ( n ) af(n/b) \leq cf(n) a f ( n / b ) ≤ c f ( n ) ,则T ( n ) = Θ ( f ( n ) ) T(n)=\Theta(f(n)) T ( n ) = Θ ( f ( n ) ) 随机算法 雇佣问题1 2 3 4 5 6 7 HIRE-ASSISTANT(n) best = 0 for i = 1 to n interview candidate i if candidate i is better than candidate best best = i hire candidate i
最坏情况分析在最坏情况下,当应聘者质量按出现的次序严格递增时,总费用是O ( c h n ) O(c_hn) O ( c h n )
随机算法如果一个算法的行为不仅由输入决定,而且也由随机数生成器 产生的数值决定,则称这个算法是随机的
指示器随机变量给定一个样本空间S S S 和一个事件A A A ,那么事件A A A 对应的指示器随机变量 I { A } I\{A\} I { A } 定义为:
I { A } = { 1 i f A o c c u r s 0 i f A d o e s n o t o c c u r I\{A\} = \begin{cases} 1 \qquad & if\ A \ occurs \\ 0 \qquad & if \ A \ does\ not\ occur \end{cases} I { A } = { 1 0 i f A o c c u r s i f A d o e s n o t o c c u r
给定一个样本空间S S S 和S S S 中的一个事件A A A ,设X A = I { A } X_A=I\{A\} X A = I { A } ,那么E [ X A ] = P r { A } E[X_A]=Pr\{A\} E [ X A ] = P r { A }
用指示器随机变量分析雇佣问题应聘者i i i 比应聘者1到i − 1 i-1 i − 1 更有资格的概率为1 / i 1/i 1 / i E [ X i ] = 1 / i E[X_i]=1/i E [ X i ] = 1 / i 所以有:E [ X ] = E [ ∑ i = i n X i ] = ∑ i = 1 n E [ X i ] = ∑ i = 1 n 1 / i = l n n + O ( 1 ) E[X]\\ =E[\sum_{i=i}^n X_i]\\ = \sum_{i=1}^nE[X_i]\\ =\sum_{i=1}^n1/i\\ =lnn+O(1) E [ X ] = E [ ∑ i = i n X i ] = ∑ i = 1 n E [ X i ] = ∑ i = 1 n 1 / i = l n n + O ( 1 ) 算法 HIRE-ASSISTANT
总的雇佣费用平均情形下为O ( c h l n n ) O(c_hlnn) O ( c h l n n )
随机算法过程 RANDOMIZED-HIRE-ASSISTANT
的雇佣费用期望是O ( c h l n n ) O(c_hlnn) O ( c h l n n ) :
1 2 3 4 5 6 7 8 RANDOMIZED-HIRE-ASSISTANT(n) randomly permute the list of candidates best = 0 for i = 1 to n interview candidate i if candidate i is better than candidate best best = i hire candidate i
随机排列数组假设所有优先级都不同,则过程 PERMUTE-BY-SORTING
产生输入的均匀随机排列:
1 2 3 4 5 6 PERMUTE-BY-SORTING(A) n = A.length let P[1..n] be a new array for i = 1 to n P[i] = RANDOM(1, n^3) sort A, using P as sort keys
过程 RANDOMIZE-IN-PLACE
可计算出一个均匀随机排列:
1 2 3 4 RANDOMIZE-IN-PLACE(A) n = A.length for i = 1 to n swap A[i] with A[RANDOM(i, n)]
排序和顺序统计量 堆排序 堆堆 是一个数组,它可以被看成一个近似的完全二叉树
树上的每一个结点对应数组中的一个元素 除了最底层外,该树是完全充满的 A . l e n g t h A.length A . l e n g t h 表示数组元素的个数A . h e a p − s i z e A.heap-size A . h e a p − s i z e 表示有多少个堆元素存储在该数组中0 ≤ A . h e a p − s i z e ≤ A . l e n g t h 0 \leq A.heap-size \leq A.length 0 ≤ A . h e a p − s i z e ≤ A . l e n g t h 一个堆中的结点的高度 就为该结点到叶结点最长简单路径上边的数目
计算父结点、左孩子和右孩子的下标:
1 2 3 4 5 6 7 8 PARENT(i) return ⌊i/2⌋ LEFT(i) return 2i RIGHT(i) return 2i + 1
在最大堆 中,最大堆性质 指除了根以外的所有结点i i i 都要满足:
A [ P A R E N T ( i ) ] ≥ A [ i ] A[PARENT(i)] \geq A[i] A [ P A R E N T ( i ) ] ≥ A [ i ]
在最小堆 中,最小堆性质 指除了根以外的所有结点i i i 都要满足:
A [ P A R E N T ( i ) ] ≤ A [ i ] A[PARENT(i)] \leq A[i] A [ P A R E N T ( i ) ] ≤ A [ i ]
维护堆的性质假定根结点为 LEFT(i)
和 RIGHT(i)
的二叉树都是最大堆,MAX-HEAPIFY
通过让A [ i ] A[i] A [ i ] 的值在最大堆中“逐级下降”,从而使得以下标i i i 为根结点的子树重新遵循最大堆的性质:
1 2 3 4 5 6 7 8 9 10 11 12 MAX-HEAPIFY(A, i) l = LEFT(i) r = RIGHT(i) if l <= A.heap-size and A[l] > A[i] largest = l else largest = i if r <= A.heap-size and A[r] > A[largest] largest = r if largest != i exchange A[i] with A[largest] MAX-HEAPIFY(A, largest)
因为每个孩子的子树的大小至多为2 n / 3 2n/3 2 n / 3 (最坏情况发生在树的最底层恰好半满的时候),我们可以用下面递归式刻画 MAX-HEAPIFY
的运行时间:
T ( n ) ≤ T ( 2 n / 3 ) + Θ ( 1 ) = O ( l g n ) = O ( h ) T(n) \leq T(2n/3)+\Theta(1) = O(lgn) = O(h) T ( n ) ≤ T ( 2 n / 3 ) + Θ ( 1 ) = O ( l g n ) = O ( h )
建堆可以用自底向上的方法利用过程 MAX-HEAPIFY
把一个大小为n = A . l e n g t h n=A.length n = A . l e n g t h 的数组A [ 1.. n ] A[1..n] A [ 1 . . n ] 转换为最大堆:
1 2 3 4 BUILD-MAX-HEAP(A) A.heap-size = A.length for i = ⌊A.length/2⌋ downto 1 MAX-HEAPIFY(A, i)
在一个高度为h h h 的结点上运行 MAX-HEAPIFY
的代价是O ( h ) O(h) O ( h ) ,我们可以将 BUILD-MAX-HEAP
的总代价表示为:
∑ h = 0 ⌊ l g n ⌋ ⌈ n 2 h + 1 ⌉ O ( h ) = O ( n ∑ h = 0 ⌊ l g n ⌋ h 2 h ) = O ( n ∑ h = 0 ∞ h 2 h ) = O ( n ) \sum^{\lfloor lgn \rfloor}_{h=0} \lceil \frac{n}{2^{h+1}} \rceil O(h) = O(n \sum^{\lfloor lgn \rfloor}_{h=0} \frac{h}{2^h}) = O(n \sum^{\infty}_{h=0} \frac{h}{2^h}) =O(n) h = 0 ∑ ⌊ l g n ⌋ ⌈ 2 h + 1 n ⌉ O ( h ) = O ( n h = 0 ∑ ⌊ l g n ⌋ 2 h h ) = O ( n h = 0 ∑ ∞ 2 h h ) = O ( n )
因此,我们可以在线性时间 内,把一个无序数组构造成一个最大堆
堆排序算法HEAPSORT
过程的时间复杂度是O ( n l g n ) O(nlgn) O ( n l g n ) :
1 2 3 4 5 6 HEAPSORT(A) BUILD-MAX-HEAP(A) for i = A.length downto 2 exchange A[1] with A[i] A.heap-size = A.heap-size - 1 MAX-HEAPIFY(A, 1)
优先队列优先队列是一种用来维护由一组元素构成的集合S S S 的数据结构,其中的每一个元素都有一个相关的值,称为关键字。一个最大优先队列支持以下操作:
INSERT(S, x)
:把元素x x x 插入集合S S S 中MAXIMUM(S)
:返回S S S 中具有最大键字的元素EXTRACT-MAX(S)
:去掉并返回S S S 中的具有最大键字的元素INCREASE-KEY(S, x, k)
:将元素x x x 的关键字增加到k k k ,这里假设k k k 的值不小于x x x 的原关键字值优先队列可以用堆来实现:HEAP-MAXMIUM
时间复杂度Θ ( 1 ) \Theta(1) Θ ( 1 ) :
1 2 HEAP-MAXMIUM(A) return A[1]
HEAP-EXTRACT-MAX
时间复杂度O ( l g n ) O(lgn) O ( l g n ) :
1 2 3 4 5 6 7 8 HEAP-EXTRACT-MAX(A) if A.heap-size < 1 error "heap underflow" max = A[1] A[1] = A[A.heap-size] A.heap-size = A.heap-size - 1 MAX-HEAPIFY(A, 1) return max
HEAP-INCREASE-KEY
时间复杂度O ( l g n ) O(lgn) O ( l g n ) :
1 2 3 4 5 6 7 HEAP-INCREASE-KEY(A, i, key) if key < A[i] error "new key is smaller than current key" A[i] = key while i > 1 and A[PARENT(i)] < A[i] exchange A[i] with A[PARENT(i)] i = PARENT(i)
MAX-HEAP-INSERT
时间复杂度O ( l g n ) O(lgn) O ( l g n ) :
1 2 3 4 MAX-HEAP-INSERT(A, key) A.heap-size = A.heap-size + 1 A[A.heap-size] = -∞ HEAP-INCRERASE-KEY(A, A.heap-size, key)
快速排序 快速排序描述对一个子数组A [ p . . r ] A[p..r] A [ p . . r ] 进行快速排序的三步分治过程:
分解 :数组A [ p . . r ] A[p..r] A [ p . . r ] 被划分为两个(可能为空)子数组A [ p . . q − 1 ] A[p..q-1] A [ p . . q − 1 ] 和A [ q + 1.. r ] A[q+1..r] A [ q + 1 . . r ] ,使得A [ p . . q − 1 ] A[p..q-1] A [ p . . q − 1 ] 中的每一个元素都小于等于A [ q ] A[q] A [ q ] ,而A [ q ] A[q] A [ q ] 也小于等于A [ q + 1.. r ] A[q+1..r] A [ q + 1 . . r ] 中的每个元素。其中,计算下标q q q 也是划分过程的一部分解决 :通过递归调用快速排序,对子数组A [ p . . q − 1 ] A[p..q-1] A [ p . . q − 1 ] 和A [ q + 1.. r ] A[q+1..r] A [ q + 1 . . r ] 进行排序合并 :因为子数组都是原址排序的,所以不需要合并操作:数组A [ p . . r ] A[p..r] A [ p . . r ] 已经有序1 2 3 4 5 QUICKSORT(A, p, r) if p < r q = PARTITION(A, p, r) QUICKSORT(A, p, q-1) QUICKSORT(A, q+1, r)
PARTITION
过程实现了对子数组A [ p . . r ] A[p..r] A [ p . . r ] 的原址重排:
1 2 3 4 5 6 7 8 9 PARTITION(A, p, r) x = A[r] i = p - 1 for j = p to r-1 if A[j] <= x i = i + 1 exchange A[i] with A[j] exchange A[i+1] with A[r] return i+1
快速排序性能 最坏情况划分当划分产生的两个子问题分别包含了n − 1 n-1 n − 1 个元素和0个元素时,为最坏情况 此时算法递归式可以表示为:
T ( n ) = T ( n − 1 ) + T ( 0 ) + Θ ( n ) = T ( n − 1 ) + Θ ( n ) = Θ ( n 2 ) T(n)=T(n-1)+T(0)+\Theta(n) =T(n-1) + \Theta(n) = \Theta(n^2) T ( n ) = T ( n − 1 ) + T ( 0 ) + Θ ( n ) = T ( n − 1 ) + Θ ( n ) = Θ ( n 2 )
最好情况划分在可能的最平衡的划分中,PARTITION
得到的两个子问题的规模都不大于n / 2 n/2 n / 2 此时算法递归式可以表示为:
T ( n ) = 2 T ( n / 2 ) + Θ ( n ) = Θ ( n l g n ) T(n)=2T(n/2)+\Theta(n)=\Theta(nlgn) T ( n ) = 2 T ( n / 2 ) + Θ ( n ) = Θ ( n l g n )
平衡的划分任何一种常数比例 的划分都会产生深度为Θ ( l g n ) \Theta(lgn) Θ ( l g n ) 的递归树,其中每一层的时间代价都是O ( n ) O(n) O ( n ) 因此,只要划分是常数比例 的,算法的运行时间总是O ( n l g n ) O(nlgn) O ( n l g n )
对于平均情况的直观观察当好和差的划分交替出现时,快速排序的时间复杂度与全是好的划分时一样,仍然是O ( n l g n ) O(nlgn) O ( n l g n ) 。区别只是O O O 符号中隐含的常数因子要略大一些
快速排序随机化版本通过对序列p , . . , r p,..,r p , . . , r 的随机抽样,我们期望在平均情况下,对输入数组的划分是比较均衡的:
1 2 3 4 RANDOMIZED-PARTITION(A, p, r) i = RANDOM(p, r) exchange A[r] with A[i] return PARTITION(A, p, r)
1 2 3 4 5 RANDOMIZED-QUICKSORT(A, p, r) if p < r q = RANDOMIZED-PARTITION(A, p, r) RANDOMIZED-QUICKSORT(A, p, q-1) RANDOMIZED-QUICKSORT(A, q+1, r)
快速排序分析 最坏情况分析快速排序的最坏情况运行时间是Θ ( n 2 ) \Theta(n^2) Θ ( n 2 )
期望运行时间快速排序的期望运行时间是O ( n l g n ) O(nlgn) O ( n l g n )
线性时间排序 排序算法的下界 决策树模型比较排序可以被抽象为一棵决策树 决策树 是一棵完全二叉树,它可以表示在给定输入规模情况下,某一给定排序算法对所有元素的比较操作 在决策树中,每个内部结点都以i : j i:j i : j 标记,其中i i i 和j j j 满足1 ≤ i , j ≤ n 1 \leq i,j \leq n 1 ≤ i , j ≤ n ,n n n 是输入序列中的元素个数 每一个内部结点表示一次比较a i ≤ a j a_i \leq a_j a i ≤ a j
左子树表示一旦我们确定a i ≤ a j a_i \leq a_j a i ≤ a j 之后的后续比较 右子树表示一旦我们确定a i > a j a_i > a_j a i > a j 之后的后续比较 对于一个正确的比较排序算法来说,n n n 个元素的n ! n! n ! 种可能的排列都应该出现在决策树的叶结点上。而且,每一个叶结点都必须是可以从根结点经由某条路径到达的
最坏情况的下界在最坏情况下,任何比较排序算法都需要做Ω ( n l g n ) \Omega(nlgn) Ω ( n l g n ) 次比较: 考虑一棵高度为h h h ,具有l l l 个可达叶结点的决策树,它对应一个对n n n 个元素所做的比较排序。因为输入数据的n ! n! n ! 种可能的排列都是叶结点,所以有n ! ≤ l n! \leq l n ! ≤ l 。由于在一个高度为h h h 的二叉树中,叶结点的数目 不多于2 h 2^h 2 h ,所以有:
n ! ≤ l ≤ 2 h n! \leq l \leq 2^h n ! ≤ l ≤ 2 h
对该式两边取对数,有h ≥ l g ( n ! ) = Ω ( n l g n ) h \geq lg(n!) = \Omega(nlgn) h ≥ l g ( n ! ) = Ω ( n l g n )
计数排序计数排序假设n n n 个输入元素中的每一个都是在0到k k k 区间内的一个整数 ,其中k k k 为某个整数。当k = O ( n ) k=O(n) k = O ( n ) 时,排序的运行时间为Θ ( n ) \Theta(n) Θ ( n )
在计数排序的代码中,假设输入是一个数组A [ 1.. n ] A[1..n] A [ 1 . . n ] ,A . l e n g t h = n A.length = n A . l e n g t h = n ,B [ 1.. n ] B[1..n] B [ 1 . . n ] 存放排序的输出,C [ 0.. k ] C[0..k] C [ 0 . . k ] 提供临时存储空间:
1 2 3 4 5 6 7 8 9 10 11 COUNTING-SORT(A, B, k) let C[0..k] be a new array for i = 0 to k C[i] = 0 for j = 1 to A.length C[A[j]] = C[A[j]] + 1 for i = 1 to k C[i] = C[i-1] + C[i] for j = A.length downto 1 B[C[A[j]]] = A[j] C[A[j]] = C[A[j]] - 1
计数排序时间复杂度Θ ( k + n ) \Theta(k+n) Θ ( k + n ) ,当k = O ( n ) k=O(n) k = O ( n ) 时,时间复杂度Θ ( n ) \Theta(n) Θ ( n )
基数排序假设n n n 个d d d 位的元素存放在数组A A A 中,其中第1位是最低位,第d d d 位是最高位:
1 2 3 RADIX-SORT(A, d) for i = 1 to d use a stable sort to sort array A on digit i
给定一个b b b 位数和任何正整数r ≤ b r\leq b r ≤ b ,如果 RADIX-SORT
使用的稳定排序算法对数据取值区间是0到k k k 的输入进行排序耗时Θ ( n + k ) \Theta(n+k) Θ ( n + k ) ,那么它就可以在Θ ( ( b / r ) ( n + 2 r ) ) \Theta((b/r)(n+2^r)) Θ ( ( b / r ) ( n + 2 r ) ) 时间内将这些数据排好序
桶排序桶排序假设输入数据服从均匀分布,平均情况下它的时间代价为O ( n ) O(n) O ( n )
假设输入是一个包含n n n 个元素的数组A A A ,且每个元素A [ i ] A[i] A [ i ] 满足0 ≤ A [ i ] < 1 0 \leq A[i] < 1 0 ≤ A [ i ] < 1 。算法还需要一个临时数组B [ 0.. n − 1 ] B[0..n-1] B [ 0 . . n − 1 ] 来存放链表(即桶),并假设存在一种用于维护这些链表的机制:
1 2 3 4 5 6 7 8 9 10 BUCKET-SORT(A) n = A.length let B[0..n-1] be a new array for i = 0 to n-1 make B[i] an empty list for i = 1 to n insert A[i] into list B[⌊nA[i]⌋] for i = 0 to n-1 sort list B[i] with insertion sort concatenate the lists B[0]..B[n-1] together in order
桶排序的期望运行时间为:
Θ ( n ) + n ⋅ O ( 2 − 1 / n ) = Θ ( n ) \Theta(n)+n \cdot O(2-1/n) = \Theta(n) Θ ( n ) + n ⋅ O ( 2 − 1 / n ) = Θ ( n )
中位数和顺序统计量 最小值和最大值假设该集合元素存放在数组A A A 中,且A . l e n g t h = n A.length = n A . l e n g t h = n :
1 2 3 4 5 6 MINIMUM(A) min = A[1] for i = 2 to A.length if min > A[i] min = A[i] return min
为了确定最小值,必须要做n − 1 n-1 n − 1 次比较
同时找到最小值和最大值最多3 ⌊ n / 2 ⌋ 3\lfloor n/2 \rfloor 3 ⌊ n / 2 ⌋ 次比较就可以同时找到最小值和最大值: 首先,我们将一对输入元素相互进行比较,然后把较小的与当前最小值比较,把较大的与当前最大值进行比较。这样,对每两个元素共需3次比较:
如果n n n 是奇数 ,我们就将最小值和最大值的初值设为第一个元素的值,然后成对地处理余下的元素 如果n n n 是偶数 ,就对前两个元素做一次比较,以决定最小值和最大值的初值,然后成对处理余下的元素 期望为线性时间的选择算法RANDOMIZED-SELECT
返回数组A [ p . . r ] A[p..r] A [ p . . r ] 中第i i i 小的元素:
1 2 3 4 5 6 7 8 9 10 11 RANDOMIZED-SELECT(A, p, r, i) if p == r return A[p] q = RANDOMIZE-PARTITOOPN(A, p, r) k = q - p + 1 if i == k return A[q] else if i < k return RANDOMIZED-SELECT(A, p, q-1, r) else return RANDOMIZED-SELECT(A, q+1, r, i-k)
RANDOMIZED-SELECT
的最坏情况运行时间为Θ ( n 2 ) \Theta(n^2) Θ ( n 2 ) ,期望运行时间为Θ ( n ) \Theta(n) Θ ( n )
最坏情况为线性时间的选择算法通过执行下列步骤,算法 SELECT
可以确定一个有n > 1 n>1 n > 1 个不同元素的输入数组中的第i i i 小的元素
将输入数组的n n n 个元素划分为⌊ n / 5 ⌋ \lfloor n/5 \rfloor ⌊ n / 5 ⌋ 组,每组5个元素,且至多只有一组由剩下的n m o d 5 n \ mod \ 5 n m o d 5 个元素组成 寻找这⌈ n / 5 ⌉ \lceil n/5 \rceil ⌈ n / 5 ⌉ 组中每一组的中位数:首先对每组元素进行插入排序,然后确定每组有序元素的中位数 对第2步中找出的⌈ n / 5 ⌉ \lceil n/5 \rceil ⌈ n / 5 ⌉ 个中位数,递归调用 SELECT
以找出其中位数x x x (如果有偶数个中位数,为了方便,约定x x x 是较小的中位数) 利用修改过的 PARTITION
版本,按中位数的中位数x x x 对输入数组进行划分。让k k k 比划分的低区中的元素数目多1,因此x x x 是第k k k 小的元素,并且有n − k n-k n − k 个元素在划分的高区 如果i = k i=k i = k ,则返回x x x 。如果i < k i<k i < k ,则在低区递归调用 SELECT
来找出第i i i 小的元素。如果i > k i>k i > k ,则在高区递归查找第i − k i-k i − k 小的元素
在第2步找出的中位数中,至少有一半大于或等于中位数的中位数x x x 。因此,在这⌈ n / 5 ⌉ \lceil n/5 \rceil ⌈ n / 5 ⌉ 个组中,除了当n n n 不能被5整除时产生的所含元素少于5的那个组和包含x x x 的那个组之外,至少有一半的组中有3个元素大于x x x 。不算这两个组,大于x x x 的元素个数至少为:
3 ( ⌈ 1 2 ⌈ n 5 ⌉ ⌉ − 2 ) ≥ 3 n 10 − 6 3(\lceil \frac{1}{2} \lceil \frac{n}{5} \rceil\rceil - 2) \geq \frac{3n}{10}-6 3 ( ⌈ 2 1 ⌈ 5 n ⌉ ⌉ − 2 ) ≥ 1 0 3 n − 6
类似地,至少有3 n / 10 − 6 3n/10-6 3 n / 1 0 − 6 个元素小于x x x 。因此,在最坏情况下,在第5步中,SELECT
的递归调用最多作用于7 n / 10 + 6 7n/10+6 7 n / 1 0 + 6 个元素。 由此可以得到如下递归式:
T ( n ) ≤ { Θ ( 1 ) n < 140 T ( ⌈ n / 5 ⌉ ) + T ( 7 n / 10 + 6 ) + O ( n ) n ≥ 140 = O ( n ) T(n) \leq \begin{cases} \Theta(1) \qquad & n < 140 \\ T(\lceil n/5 \rceil) + T(7n/10+6) + O(n) \qquad & n\geq 140 \end{cases} = O(n) T ( n ) ≤ { Θ ( 1 ) T ( ⌈ n / 5 ⌉ ) + T ( 7 n / 1 0 + 6 ) + O ( n ) n < 1 4 0 n ≥ 1 4 0 = O ( n )
高级设计和分析技术 动态规划我们通常按如下4个步骤来设计一个动态规划算法:
刻画一个最优解的结构特征 递归地定义最优解的值 计算最优解的值,通常采用自底向上的方法 利用计算出的信息构造一个最优解 钢条切割给定一段长度为n n n 英寸的钢条和一个价格表p i ( i = 1 , 2 , . . . , n ) p_i(i=1,2,...,n) p i ( i = 1 , 2 , . . . , n ) ,求切割钢条方案,使得销售收益r n r_n r n 最大
自顶向下 CUT-ROD
过程,加入了备忘机制,时间复杂度Θ ( n 2 ) \Theta(n^2) Θ ( n 2 ) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 MEMORIZED-CUT-ROD(p, n) let r[0..n] be a new array for i = 0 to n r[i] = -∞ return MEMORIZED-CUT-ROD-AUX(p, n, r) MEMORIZED-CUT-ROD-AUX(p, n, r) if r[n] >=0 return r[n] if n == 0 q = 0 else q = -∞ for i = 1 to n q = max(q, p[i] + MEMORIZED-CUT-ROD-AUX(p, n-i, r)) r[n] = q return q
自底向上版本,时间复杂度Θ ( n 2 ) \Theta(n^2) Θ ( n 2 ) :
1 2 3 4 5 6 7 8 9 BOTTOM-UP-CUT-ROD(p, n) let r[0..n] be a new array r[0] = 0 for j = 1 to n q = -∞ for i = 1 to j q = max(q, p[i] + r[j-i]) r[j] = q return r[n]
重构解BOTTOM-UP-CUT-ROD
的扩展版本,它对长度为j j j 的钢条不仅计算最大收益值r j r_j r j ,还保存最优解对应的第一段钢条的切割长度s j s_j s j :
1 2 3 4 5 6 7 8 9 10 11 EXTENDED-BOTTOM-UP-CUT-ROD(p, n) let r[0..n] and s[0..n] be new arrays r[0] = 0 for j = 1 to n q = -∞ for i = 1 to j if q < p[i] + r[j-i] q = p[i] + r[j-i] s[j] = i r[j] = q return r and s
最后输出长度为n n n 的钢条的完整的最优切割方案:
1 2 3 4 5 PRINT-CUT-ROD-SOLUTION(p, n) (r, s) = EXTENDED-BOTTOM-UP-CUT-ROD(p, n) while n > 0 print s[n] n = n - s[n]
矩阵链乘法给定一个n n n 个矩阵的序列(矩阵链)⟨ A 1 , A 2 , . . . , A n ⟩ \langle A_1,A_2,...,A_n\rangle ⟨ A 1 , A 2 , . . . , A n ⟩ ,我们希望计算它们的乘积:
A 1 A 2 . . . A n A_1A_2...A_n A 1 A 2 . . . A n
由于矩阵乘法满足结合律,因此任何加括号的方法都会得到相同的计算结果
我们称有如下性质的矩阵乘积链为完全括号化的 :它是单一矩阵,或者是两个完全括号化的矩阵乘积链的积 给定n n n 个矩阵的链⟨ A 1 , A 2 , . . . , A n ⟩ \langle A_1,A_2,...,A_n\rangle ⟨ A 1 , A 2 , . . . , A n ⟩ ,矩阵A i A_i A i 的规模为p i − 1 ∗ p i ( 1 ≤ i ≤ n ) p_{i-1} * p_i(1 \leq i \leq n) p i − 1 ∗ p i ( 1 ≤ i ≤ n ) ,求完全括号化方案,使得计算乘积A 1 A 2 . . . A n A_1A_2...A_n A 1 A 2 . . . A n 所需标量乘法次数最少
令m [ i , j ] m[i,j] m [ i , j ] 表示计算矩阵A i . . j A_{i..j} A i . . j 所需标量乘法次数的最小值,则A i A i + 1 . . . A j A_iA_{i+1}...A_j A i A i + 1 . . . A j 最小代价括号化方案的递归求解公式为:
m [ i , j ] ≤ { 0 i = j m i n i ≤ k < j { m [ i , k ] + m [ k + 1 , j ] + p i − 1 p k p j } i < j m[i,j] \leq \begin{cases} 0 \qquad & i = j \\ min_{i \leq k < j}\{m[i,k]+m[k+1,j]+p_{i-1}p_kp_j\} \qquad & i <j \end{cases} m [ i , j ] ≤ { 0 m i n i ≤ k < j { m [ i , k ] + m [ k + 1 , j ] + p i − 1 p k p j } i = j i < j
MATRIX-CHAIN-ORDER
时间复杂度O ( n 3 ) O(n^3) O ( n 3 ) ,另外还需要Θ ( n 2 ) \Theta(n^2) Θ ( n 2 ) 的内存空间来保存表m m m 和s s s :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 MATRIX-CHAIN-ORDER(p) n = p.length - 1 let m[1..n,1..n] and s[1..n-1,2..n] be new tables for i = 1 to n m[i,i] = 0 for l = 2 to n //l is the chain length for i = 1 to n-l+1 j = i + l - 1 m[i,j] = ∞ for k = i to j-1 q = m[i, k] + m[k+1, j] + p_i-1p_kp_j if q < m[i,j] m[i,j] = q s[i,j] = k return m and s
调用 PRINT-OPTIMAL-PARENS
可输出⟨ A 1 , A 2 , . . . , A n ⟩ \langle A_1,A_2,...,A_n\rangle ⟨ A 1 , A 2 , . . . , A n ⟩ 的最优括号化方案:
1 2 3 4 5 6 7 8 PRINT-OPTIMAL-PARENS(s, i, j) if i == j print "A" else print "(" PRINT-OPTIMAL-PARENS(s, i, s[i,j]) PRINT-OPTIMAL-PARENS(s, s[i,j]+1, j) print ")"
最长公共子序列(LCS)给定一个序列X = ⟨ x 1 , x 2 . . . x m ⟩ X = \langle x_1,x_2...x_m\rangle X = ⟨ x 1 , x 2 . . . x m ⟩ ,令一个序列Z = ⟨ z 1 , z 2 , . . . , z k ⟩ Z=\langle z_1,z_2,...,z_k\rangle Z = ⟨ z 1 , z 2 , . . . , z k ⟩ 满足如下条件时称为X X X 的子序列 :存在一个严格递增的X X X 的下标序列⟨ i 1 , i 2 , . . . , i k ⟩ \langle i_1,i_2,...,i_k\rangle ⟨ i 1 , i 2 , . . . , i k ⟩ ,对所有j = 1 , 2 , . . . , k j=1,2,...,k j = 1 , 2 , . . . , k ,满足x i = z j x_i=z_j x i = z j 给定两个序列X = ⟨ x 1 , x 2 . . . x m ⟩ X = \langle x_1,x_2...x_m\rangle X = ⟨ x 1 , x 2 . . . x m ⟩ 和Y = ⟨ y 1 , y 2 , . . . , y n ⟩ Y =\langle y_1,y_2,...,y_n\rangle Y = ⟨ y 1 , y 2 , . . . , y n ⟩ ,求X X X 和Y Y Y 长度最长的公共子序列
我们定义c [ i , j ] c[i,j] c [ i , j ] 表示X i X_i X i 和Y j Y_j Y j 的 LCS
的长度,可得如下公式:
c [ i , j ] ≤ { 0 i = 0 ∨ j = 0 c [ i − 1 , j − 1 ] + 1 i , j > 0 ∧ x i = y i m a x ( c [ i , j − 1 ] , c [ i − 1 , j ] ) i , j > 0 ∧ x i ≠ y i c[i,j] \leq \begin{cases} 0 \qquad & i = 0 \lor j = 0 \\ c[i-1,j-1]+1 \qquad & i,j >0 \land x_i = y_i \\ max(c[i,j-1],c[i-1,j]) & i,j >0 \land x_i \neq y_i \end{cases} c [ i , j ] ≤ ⎩ ⎪ ⎪ ⎨ ⎪ ⎪ ⎧ 0 c [ i − 1 , j − 1 ] + 1 m a x ( c [ i , j − 1 ] , c [ i − 1 , j ] ) i = 0 ∨ j = 0 i , j > 0 ∧ x i = y i i , j > 0 ∧ x i = y i
LCS-LENGTH
时间复杂度Θ ( m n ) \Theta(mn) Θ ( m n ) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 LCS-LENGTH(X, Y) m = X.length n = Y.length let b[1..m,1..n] and c[0..m,0..n] be new tables for i = 1 to m c[i,0] = 0 for j = 0 to n c[0,j] = 0 for i = 1 to m for j = 1 to n if xi == yj c[i,j] = c[i-1,j-1] + 1 b[i,j] = "↖" elseif c[i-1,j] >= c[i,j-1] c[i,j] = c[i-1,j] b[i,j] = "↑" else c[i,j] = c[i,j-1] b[i,j] = "←" return c and b
调用 PRINT-LCS
可打印出X X X 和Y Y Y 的一个 LCS
:
1 2 3 4 5 6 7 8 9 10 PRINT-LCS(b, X, i, j) if x == 0 or j == 0 return if b[i,j] == "↖" PRINT-LCS(b, X, i-1, j-1) print xi elseif b[i,j] == "↑" PRINT-LCS(b, X, i-1, j) else PRINT-LCS(b, X, i, j-1)
最优二叉搜索树给定一个n n n 个不同关键字的已排序的序列K = ⟨ k 1 , k 2 , . . . , k n ⟩ , k 1 < k 2 < . . . < k n K=\langle k_1,k_2,...,k_n \rangle ,k_1<k_2<...<k_n K = ⟨ k 1 , k 2 , . . . , k n ⟩ , k 1 < k 2 < . . . < k n ,我们希望用这些关键字构造一棵二叉搜索树,对每个关键字k i k_i k i ,都有一个概率p i p_i p i 表示其搜索频率。有些要搜索的值可能不在K K K 中,因此我们还有n + 1 n+1 n + 1 个“伪关键字”d 0 , d 1 , . . . , d n d_0,d_1,...,d_n d 0 , d 1 , . . . , d n 表示不在K K K 中的值。d 0 d_0 d 0 表示所有小于k 1 k_1 k 1 的值,d n d_n d n 表示所有大于k n k_n k n 的值,对i = 1 , 2 , . . . , n − 1 i=1,2,...,n-1 i = 1 , 2 , . . . , n − 1 ,伪关键字d i d_i d i 表示所有在k i k_i k i 和k i + 1 k_{i+1} k i + 1 之间的值。对每个伪关键字d i d_i d i ,也都有一个概率q i q_i q i 表示对应的搜索频率。每个关键字k i k_i k i 是一个内部结点,而每个伪关键字d i d_i d i 是一个叶结点:
有如下公式:
∑ i = 1 n p i + ∑ i = 0 n q i = 1 \sum^{n}_{i=1}p_i+\sum^n_{i=0}q_i=1 i = 1 ∑ n p i + i = 0 ∑ n q i = 1
在T T T 中进行一次搜索的期望代价为:
E [ c o s t ] = 1 + ∑ i = 1 n d e p t h T ( k i ) ⋅ p i + ∑ i = 0 n d e p t h T ( d i ) ⋅ q i E[cost]=1+\sum^{n}_{i=1}depth_T(k_i) \cdot p_i + \sum^n_{i=0}depth_T(d_i) \cdot q_i E [ c o s t ] = 1 + i = 1 ∑ n d e p t h T ( k i ) ⋅ p i + i = 0 ∑ n d e p t h T ( d i ) ⋅ q i
对于一个给定的概率集合。我们希望构造一棵期望搜索代价最小的二叉搜索树,我们称为最优二叉搜索树
定义e [ i , j ] e[i,j] e [ i , j ] 为在包含关键字k i , . . . , k j k_i,...,k_j k i , . . . , k j 的最优二叉搜素树中进行一次有所的期望代价,其中i ≥ i i \geq i i ≥ i ,j ≤ n j \leq n j ≤ n 且j ≥ i − 1 j\geq i-1 j ≥ i − 1 (当j = i − 1 j=i-1 j = i − 1 时,子树不包含实际关键字,只包含伪关键字d i − 1 d_{i-1} d i − 1 )
当一棵子树成为一个结点的子树时,对于包含关键字k i , . . k j k_i,..k_j k i , . . k j 的子树,子树的期望搜索代价的增加值为:
w ( i , j ) = ∑ l = i j p l + ∑ l = i − 1 j q l w(i,j)=\sum^j_{l=i}p_l+\sum^j_{l=i-1}q_l w ( i , j ) = l = i ∑ j p l + l = i − 1 ∑ j q l
递归公式:
e [ i , j ] ≤ { q i − 1 j = i − 1 m i n i ≤ r ≤ j { e [ i , r − 1 ] + e [ r + 1 , j ] + w ( i , j ) } i ≤ j e[i,j] \leq \begin{cases} q_{i-1} \qquad & j=i-1 \\ min_{i \leq r \leq j}\{e[i,r-1]+e[r+1,j]+w(i,j)\} \qquad & i \leq j \end{cases} e [ i , j ] ≤ { q i − 1 m i n i ≤ r ≤ j { e [ i , r − 1 ] + e [ r + 1 , j ] + w ( i , j ) } j = i − 1 i ≤ j
OPTIMAL-BST
时间复杂度Θ ( n 3 ) \Theta(n^3) Θ ( n 3 ) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 OPTIMAL-BST(p, q, n) let e[1..n+1,0..n],w[1..n+1,0..n] and root[1..n,1..n] be new tables for i = 1 to n+1 e[i,i-1] = q_{i-1} w[i,i-1] = q_{i-1} for l = 1 to n for i = 1 to n-l+1 j = i+l-1 e[i,j] = ∞ w[i,j] = w[i,j-1] + p_j +q_j for r = i to j t = e[i,r-1] + e[r+1,j] + w[i,j] if t < e[i,j] e[i,j] = t root[i,j] = r return e and root
贪心算法 活动选择问题假定有一个n n n 个活动 的集合S = { a 1 , a 2 , . . . , a n } S=\{a_1,a_2,...,a_n\} S = { a 1 , a 2 , . . . , a n } 。这些活动使用同一个资源,而这个资源在某个时刻只能供一个活动使用。每个活动a i a_i a i 都有一个开始时间 s i s_i s i 和结束时间 f i f_i f i ,其中0 ≤ s i < f i < ∞ 0\leq s_i < f_i < \infty 0 ≤ s i < f i < ∞ 。如果被选中,任务a i a_i a i 发生在半开时间区间[ s i , f i ) [s_i,f_i) [ s i , f i ) 期间。如果两个活动a i a_i a i 和a j a_j a j 满足[ s i , f i ) [s_i,f_i) [ s i , f i ) 和[ s j , f j ) [s_j,f_j) [ s j , f j ) 不重叠,则称它们是兼容 的
在活动选择问题 中,我们希望选出一个最大兼容活动集 ,假定活动已按结束时间的单调递增顺序排列:
f 1 ≤ f 2 ≤ . . ≤ f n − 1 ≤ f n f_1\leq f_2 \leq .. \leq f_{n-1} \leq f_n f 1 ≤ f 2 ≤ . . ≤ f n − 1 ≤ f n
最优子结构用c [ i , j ] c[i,j] c [ i , j ] 表示集合S i j S_{ij} S i j 的最优解的大小,则可得递归式:
c [ i , j ] ≤ { 0 S i j = ∅ m a x a k ∈ S i j { c [ i , k ] + c [ k , j ] + 1 } S i j ≠ ∅ c[i,j] \leq \begin{cases} 0 \qquad & S_{ij} = \emptyset \\ max_{a_k \in S_{ij}}\{c[i,k]+c[k,j]+1\} \qquad & S_{ij} \neq \emptyset \end{cases} c [ i , j ] ≤ { 0 m a x a k ∈ S i j { c [ i , k ] + c [ k , j ] + 1 } S i j = ∅ S i j = ∅
贪心选择考虑任意非空子问题S k S_k S k ,令a m a_m a m 是S k S_k S k 中结束时间最早的活动,则a m a_m a m 在S k S_k S k 的某个最大兼容活动子集中
递归贪心算法求解原问题可调用 RECURSIVE-ACTIVITY-SELECTOR(s, f, 0, n)
,在输入活动已按结束时间排序的前提下,时间复杂度Θ ( n ) \Theta(n) Θ ( n ) :
1 2 3 4 5 6 7 8 RECURSIVE-ACTIVITY-SELECTOR(s, f, k, n) m = k + 1 while m <= n and s[m] < f[k] m = m + 1 if m <= n return {a_m} ∪ RECURSIVE-ACTIVITY-SELECTOR(s, f, m, n) else return ∅
迭代贪心算法在输入活动已按结束时间排序的前提下,时间复杂度Θ ( n ) \Theta(n) Θ ( n ) :
1 2 3 4 5 6 7 8 9 GREEDY-ACTIVITY-SELECTOR(s, f) n = s.length A = {a_1} k = 1 for m = 2 to n if s[m] >= f[k] A = A ∪ {a_m} k = m return A
霍夫曼编码 前缀码前缀码,即没有任何码字是其他码字的前缀。前缀码可以保证达到最优数据压缩率
使用二叉树 来表示前缀码:其叶结点为给定的字符。字符的二进制码字用从根结点到该字符叶结点的简单路径表示。其中 0
意味着“转向左孩子”,1
意味着“转向右孩子”:
文件的最优编码方案总是对应一棵满二叉树,即每个非叶结点都有两个孩子结点 若C C C 为字母表且所有字符的出现频率为正数,则最优前缀码对应的树恰好有∣ C ∣ |C| ∣ C ∣ 个叶结点,每个叶结点对应字母表中的一个字符,且恰有∣ C ∣ − 1 |C|-1 ∣ C ∣ − 1 个内部结点
给定一棵对应前缀码的树T T T 。对于字母表C C C 中的每个字符c c c ,令属性c . f r e q c.freq c . f r e q 表示c c c 在文件中出现的频率,令d T ( c ) d_T(c) d T ( c ) 表示c c c 的叶结点在树中的深度。则编码文件需要:
B ( T ) = ∑ c ∈ C c . f r e q ⋅ d T ( c ) B(T)=\sum_{c \in C} c.freq \cdot d_T(c) B ( T ) = c ∈ C ∑ c . f r e q ⋅ d T ( c )
个二进制位,我们将B ( T ) B(T) B ( T ) 定义为T T T 的代价
构造霍夫曼编码假定C C C 是一个n n n 个字符的集合,而其中每个字符c ∈ C c \in C c ∈ C 都是一个对象,其属性c . f r e q c.freq c . f r e q 给出了字符的出现频率。算法使用了一个以属性f r e q freq f r e q 为关键字最小优先队列Q Q Q :
1 2 3 4 5 6 7 8 9 10 HUFFMAN(C) n = |C| Q = C for i = 1 to n-1 allocate a new node z z.left = x = EXTRACT-MIN(Q) z.right = y = EXTRACT-MIN(Q) z.freq = x.freq + y.freq INSERT(Q, z) return EXTRACT-MIN(Q)
假定Q Q Q 是使用最小二叉堆实现,HUFFMAN
时间复杂度O ( n l g n ) O(nlgn) O ( n l g n ) 如果将最小二叉堆换为 van Emde Boas
树,时间复杂度O ( n l g l g n ) O(nlglgn) O ( n l g l g n )
摊还分析 聚合分析这种方法用来确定一个n n n 个操作的序列的总代价的上界T ( n ) T(n) T ( n ) 。因而每个操作的平均代价为T ( n ) / n T(n)/n T ( n ) / n 。我们将平均代价作为每个操作的摊还代价,因此所有操作具有相同的摊还代价
栈操作考虑由n n n 个 PUSH
、POP
和 MULTIPOP
组成的操作序列在一个空栈上的执行情况。其代价至多为O ( n ) O(n) O ( n ) ,一个操作的平均时间为O ( n ) / n = O ( 1 ) O(n)/n=O(1) O ( n ) / n = O ( 1 ) 。所以,所有三种栈操作的摊还代价都是O ( 1 ) O(1) O ( 1 )
二进制计数器递增我们用一个位数组A [ 0.. k − 1 ] A[0..k-1] A [ 0 . . k − 1 ] 作为计数器,其中A . l e n g t h = k A.length = k A . l e n g t h = k 。当计数器中保存的二进制值为x x x 时,x x x 的最低位保存在A [ 0 ] A[0] A [ 0 ] 中,而最高位保存在A [ k − 1 ] A[k-1] A [ k − 1 ] 中。初始时x = 0 x=0 x = 0 ,因此对所有i = 0 , 1 , . . , k − 1 i=0,1,..,k-1 i = 0 , 1 , . . , k − 1 ,A [ i ] = 0 A[i]=0 A [ i ] = 0 。为了将1(模2 k 2^k 2 k )加到计数器的值上,我们使用如下过程:
1 2 3 4 5 6 7 INCEREMENT(A) i = 0 while i < A.length and A[i] == 1 A[i] = 0 i = i + 1 if i < A.length A[i] = 1
一般地,对一个初值为0的计数器,在执行一个由n n n 个 INCEREMENT
操作组成的序列的过程中,A [ i ] A[i] A [ i ] 会翻转⌊ n / 2 i ⌋ \lfloor n/2^i \rfloor ⌊ n / 2 i ⌋ 次。总翻转次数为:
∑ i = 0 k − 1 ⌊ n 2 i ⌋ < n ∑ i = 0 ∞ 1 2 i = 2 n \sum^{k-1}_{i=0}\lfloor \frac{n}{2^i} \rfloor < n \sum^{\infty}_{i=0}\frac{1}{2^i}=2n i = 0 ∑ k − 1 ⌊ 2 i n ⌋ < n i = 0 ∑ ∞ 2 i 1 = 2 n
因此,对一个初值为0的计数器,执行一个n n n 个 INCEREMENT
操作的序列的最坏情况时间为O ( n ) O(n) O ( n ) 。每个操作的平均代价,即摊还代价为O ( n ) / n = O ( 1 ) O(n)/n=O(1) O ( n ) / n = O ( 1 )
核算法用核算法进行摊还分析时,我们对不同操作赋予不同费用,赋予某些造成的费用可能多于或少于其实际代价。我们将赋予一个操作的费用称为它的摊还代价 当一个操作的摊还代价超出其实际代价时,我们将差额存入数据结构中的特定对象,存入的差额称为信用 对于后续操作中摊还代价小于实际代价的情况,信用可以用来支付差额
如果用c i c_i c i 表示第i i i 个操作的真实代价,用c i ^ \hat{c_i} c i ^ 表示其摊还代价,则对任意n n n 个操作的序列,要求:
∑ i = 1 n c i ^ ≥ ∑ i = 1 n c i \sum^n_{i=1} \hat{c_i} \geq \sum^n_{i=1}c_i i = 1 ∑ n c i ^ ≥ i = 1 ∑ n c i
即数据结构所关联的信用必须一直为非负值
栈操作为操作赋予如下摊还代价:
用1美元支付压栈操作的实际代价,将剩余1美元存为信用,用来支付将来出栈操作的代价 由于栈中的每个元素都存有1美元的信用,而栈中的元素始终是非负的,因此可以保证总信用值是非负的 因此,对任意n n n 个 PUSH
、POP
和 MULTIPOP
操作组成的序列,总摊还代价为总实际代价的上界由于总摊还代价为O ( n ) O(n) O ( n ) ,总实际代价也是O ( n ) O(n) O ( n )
二进制计数器递增为操作赋予如下摊还代价:
当进行置位时,用1美元支付置为操作的实际代价,另1美元存为信用,用来支付将来复位操作的代价 由于每位都存有1美元的信用,而计数器中1的个数始终是非负的,因此可以保证总信用值是非负的 因此,对任意n n n 个 INCREMENT
操作,总摊还代价为总实际代价的上界。由于总摊还代价为O ( n ) O(n) O ( n ) ,总实际代价也是O ( n ) O(n) O ( n )
势能法我们将对一个初始数据结构D 0 D_0 D 0 执行n n n 个操作。对每个i = 1 , 2 , . . . , n i=1,2,...,n i = 1 , 2 , . . . , n ,令c i c_i c i 为第i i i 个操作的实际代价,令D i D_i D i 为在数据结构D i − 1 D_{i-1} D i − 1 上执行第i i i 个操作得到的结果数据结构。势函数 Φ \Phi Φ 将每个数据结构D i D_i D i 映射到一个实数Φ ( D i ) \Phi(D_i) Φ ( D i ) ,此值即为关联到数据结构D i D_i D i 的势。第i i i 个操作的摊还代价 c i ^ \hat{c_i} c i ^ 用势函数Φ \Phi Φ 定义为:
c i ^ = c i + Φ ( D i ) − Φ ( D i − 1 ) \hat{c_i}=c_i+\Phi(D_i)-\Phi(D_{i-1}) c i ^ = c i + Φ ( D i ) − Φ ( D i − 1 )
n n n 个操作的总摊还代价为:
∑ i = 1 n c i ^ = ∑ i = 1 n ( c i + Φ ( D i ) − Φ ( D i − 1 ) ) = ∑ i = 1 n c i + Φ ( D n ) − Φ ( D 0 ) \sum^n_{i=1} \hat{c_i} = \sum^n_{i=1}(c_i+\Phi(D_i)-\Phi(D_{i-1}))=\sum^n_{i=1}c_i+\Phi(D_n)-\Phi(D_0) i = 1 ∑ n c i ^ = i = 1 ∑ n ( c i + Φ ( D i ) − Φ ( D i − 1 ) ) = i = 1 ∑ n c i + Φ ( D n ) − Φ ( D 0 )
如果能定义一个势函数Φ \Phi Φ ,使得Φ ( D n ) ≥ Φ ( D 0 ) \Phi(D_n) \geq \Phi(D_0) Φ ( D n ) ≥ Φ ( D 0 ) ,则总摊还代价∑ i = 1 n c i ^ \sum^n_{i=1} \hat{c_i} ∑ i = 1 n c i ^ 给出了总实际代价∑ i = 1 n c i \sum^n_{i=1}{c_i} ∑ i = 1 n c i 的一个上界
我们通常将Φ ( D 0 ) \Phi(D_0) Φ ( D 0 ) 简单定义为0,然后说明对所有i i i ,有Φ ( D i ) ≥ 0 \Phi(D_i) \geq 0 Φ ( D i ) ≥ 0
栈操作对于初始的空栈D 0 D_0 D 0 ,有Φ ( D 0 ) = 0 \Phi(D_0)=0 Φ ( D 0 ) = 0 。由于栈中对象数目永远不可能为负,所以第i i i 步操作得到的栈D i D_i D i 具有非负的势,即:
Φ ( D i ) ≥ 0 = Φ ( D 0 ) \Phi(D_i) \geq 0 = \Phi(D_0) Φ ( D i ) ≥ 0 = Φ ( D 0 )
如果第i i i 个操作是 PUSH
操作,此时栈中包含s s s 个对象,则势差为:
Φ ( D i ) − Φ ( D i − 1 ) = ( s + 1 ) − s = 1 \Phi(D_i)-\Phi(D_{i-1}) = (s+1)-s = 1 Φ ( D i ) − Φ ( D i − 1 ) = ( s + 1 ) − s = 1
PUSH
摊还代价为:
c i ^ = c i + Φ ( D i ) − Φ ( D i − 1 ) = 1 + 1 = 2 \hat{c_i} = c_i+\Phi(D_i)-\Phi(D_{i-1}) = 1+1=2 c i ^ = c i + Φ ( D i ) − Φ ( D i − 1 ) = 1 + 1 = 2
如果第i i i 个操作是 MULTIPOP
操作,将k ′ = m i n ( k , s ) k'=min(k,s) k ′ = m i n ( k , s ) 个对象弹出栈,则势差为:
Φ ( D i ) − Φ ( D i − 1 ) = − k ′ \Phi(D_i)-\Phi(D_{i-1}) =-k' Φ ( D i ) − Φ ( D i − 1 ) = − k ′
MULTIPOP
摊还代价为:
c i ^ = c i + Φ ( D i ) − Φ ( D i − 1 ) = k ′ − k ′ = 0 \hat{c_i} = c_i+\Phi(D_i)-\Phi(D_{i-1}) = k'-k'=0 c i ^ = c i + Φ ( D i ) − Φ ( D i − 1 ) = k ′ − k ′ = 0
如果第i i i 个操作是 POP
操作,此时栈中包含s s s 个对象,则势差为:
Φ ( D i ) − Φ ( D i − 1 ) = ( s − 1 ) − s = − 1 \Phi(D_i)-\Phi(D_{i-1}) = (s-1)-s = -1 Φ ( D i ) − Φ ( D i − 1 ) = ( s − 1 ) − s = − 1
POP
摊还代价为:
c i ^ = c i + Φ ( D i ) − Φ ( D i − 1 ) = 1 − 1 = 0 \hat{c_i} = c_i+\Phi(D_i)-\Phi(D_{i-1}) = 1-1=0 c i ^ = c i + Φ ( D i ) − Φ ( D i − 1 ) = 1 − 1 = 0
每个操作的摊还代价都是O ( 1 ) O(1) O ( 1 ) ,因此,n n n 个操作的总摊还代价为O ( n ) O(n) O ( n ) ,为总实际代价的上界,所以n n n 个操作的最坏情况时间为O ( n ) O(n) O ( n )
二进制计数器递增将计数器执行i i i 次 INCREMENT
操作后的势定义为b i b_i b i :i i i 次操作后计数器中1的个数 假设第i i i 个 INCREMENT
操作将t i t_i t i 个位复位,则其实际代价至多为t i + 1 t_i+1 t i + 1 。势差为:
Φ ( D i ) − Φ ( D i − 1 ) ≤ ( b i − 1 − t i + 1 ) − b i − 1 = 1 − t i \Phi(D_i)-\Phi(D_{i-1}) \leq (b_{i-1}-t_i+1)-b_{i-1}=1-t_i Φ ( D i ) − Φ ( D i − 1 ) ≤ ( b i − 1 − t i + 1 ) − b i − 1 = 1 − t i
摊还代价为:
c i ^ = c i + Φ ( D i ) − Φ ( D i − 1 ) ≤ ( t i + 1 ) + ( 1 − t i ) = 2 \hat{c_i} = c_i+\Phi(D_i)-\Phi(D_{i-1}) \leq (t_i+1)+(1-t_i)=2 c i ^ = c i + Φ ( D i ) − Φ ( D i − 1 ) ≤ ( t i + 1 ) + ( 1 − t i ) = 2
如果计数器从0开始,则Φ ( D 0 ) = 0 \Phi(D_0)=0 Φ ( D 0 ) = 0 。由于对所有i i i 均有Φ ( D i ) ≥ 0 \Phi(D_i)\geq 0 Φ ( D i ) ≥ 0 ,因此,一个n n n 个 INCREMENT
操作的序列的总摊还代价是总实际代价的上界,最坏情况时间为O ( n ) O(n) O ( n )
动态表 表扩张1 2 3 4 5 6 7 8 9 10 11 12 TABLE-INSERT(T, x) if T.size == 0 allocate T.table with 1 slot T.size = 1 if T.num == T.size allocate newtable with 2*T.size slots insert all items in T.table into newtable free T.table T.table = newtable T.size = 2 * T.size insert x into T.table T.num = T.num + 1
第i i i 个操作的代价为:
c i = { i i − 1 = 2 k 1 o t h e r c_i = \begin{cases} i \qquad & i-1 = 2^k \\ 1 \qquad & other \end{cases} c i = { i 1 i − 1 = 2 k o t h e r
n n n 个 TABLE-INSERT
操作的总代价为:
∑ i = 1 n c i ≤ n + ∑ j = 0 ⌊ l g n ⌋ 2 j < n + 2 n = 3 n \sum^n_{i=1}c_i \leq n+\sum^{\lfloor lgn \rfloor}_{j=0}2^j<n+2n=3n i = 1 ∑ n c i ≤ n + j = 0 ∑ ⌊ l g n ⌋ 2 j < n + 2 n = 3 n
图算法 基本的图算法 图的表示 邻接链表对于图G = ( V , E ) G=(V,E) G = ( V , E ) 来说,其邻接链表表示由一个包含∣ V ∣ |V| ∣ V ∣ 条链表的数组A d j Adj A d j 所构成,每个结点有一条链表 对于每个结点u ∈ V u \in V u ∈ V ,邻接链表A d j [ u ] Adj[u] A d j [ u ] 包含所有与结点u u u 之间有边相连的结点v v v
如果G G G 是一个有向图,则对于边( u , v ) (u,v) ( u , v ) 来说,结点v v v 将出现在链表A d j [ u ] Adj[u] A d j [ u ] 里,因此,所有邻接链表的长度之和等于∣ E ∣ |E| ∣ E ∣ 如果G G G 是一个无向图,则对于边( u , v ) (u,v) ( u , v ) 来说,结点v v v 将出现在链表A d j [ u ] Adj[u] A d j [ u ] 里,结点u u u 将出现在链表A d j [ v ] Adj[v] A d j [ v ] 里,因此,所有邻接链表的长度之和等于2 ∣ E ∣ 2|E| 2 ∣ E ∣ 不论是有向图还是无向图,邻接链表存储空间需求均为Θ ( V + E ) \Theta(V+E) Θ ( V + E ) 对邻接链表稍加修改,就可以用来表示权重图 :只需要将边( u , v ) (u,v) ( u , v ) 的权重值w ( u , v ) w(u,v) w ( u , v ) 存放放在结点u u u 的邻接链表里 邻接链表的一个潜在缺陷是无法快速判断一条边( u , v ) (u,v) ( u , v ) 是否是图中的一条边
邻接矩阵图G G G 的邻接矩阵表示由一个∣ V ∣ × ∣ V ∣ |V| \times |V| ∣ V ∣ × ∣ V ∣ 的矩阵A = ( a i j ) A=(a_{ij}) A = ( a i j ) 予以表示,该矩阵满足下述条件:
a i j = { 1 ( i , j ) ∈ E 0 o t h e r a_{ij} = \begin{cases} 1 \qquad & (i,j) \in E \\ 0 \qquad & other \end{cases} a i j = { 1 0 ( i , j ) ∈ E o t h e r
不管一个图有多少条边,邻接矩阵的空间需求均为Θ ( V 2 ) \Theta(V^2) Θ ( V 2 ) 邻接矩阵也可以用来表示权重图 :直接将边( u , v ) ∈ E (u,v) \in E ( u , v ) ∈ E 的权重w ( u , v ) w(u,v) w ( u , v ) 存放在邻接矩阵中的第u u u 行第v v v 列记录上
广度优先搜索(BFS)为了跟踪算法的进展,广度优先搜索在概念上将每个结点涂上白色、灰色或黑色。所有结点在一开始的时候均涂上白色。在算法的推进过程中,这些结点可能会变成灰色或黑色 如果边( u , v ) ∈ E (u,v) \in E ( u , v ) ∈ E 且结点u u u 是黑色,则结点v v v 既可能是灰色也可能是黑色。也就是说,所有与黑色结点邻接的结点都以被发现。对于灰色结点来说,其邻接结点中可能存在未被发现的白色结点 在执行广度优先搜索的过程中将构造出一棵广度优先树
假定输入图G = ( V , E ) G=(V,E) G = ( V , E ) 是以邻接链表所表示的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 BFS(G, s) for each vertex u in G.V - {s} u.color = WHITE u.d = ∞ u.π = NIL s.color = GRAY s.d = 0 s.π = NIL Q = ∅ ENQUEUE(Q, s) while Q != ∅ u = DEQUEUE(Q) for each v in G.Adj[u] if v.color == WHITE v.color = GRAY v.d = u.d + 1 v.π = u ENQUEUE(Q, v) u.color = BLACK
分析广度优先搜索的总运行时间为O ( V + E ) O(V+E) O ( V + E )
最短路径我们定义从源结点s s s 到结点v v v 的最短路径距离 δ ( s , v ) \delta(s,v) δ ( s , v ) 为从结点s s s 到结点v v v 之间所有路径里面最少的边数 如果从结点s s s 到结点v v v 之间没有路径,则δ ( s , v ) = ∞ \delta(s,v) = \infty δ ( s , v ) = ∞ 我们称从结点s s s 到结点v v v 的长度为δ ( s , v ) \delta(s,v) δ ( s , v ) 的路径为s s s 到v v v 的最短路径
设G = ( V , E ) G=(V,E) G = ( V , E ) 为一个有向图或无向图,又假设 BFS
以s s s 为源结点在图G G G 上运行。那么在算法执行过程中,BFS
将发现从源结点s s s 可以到达的所有结点v ∈ V v \in V v ∈ V ,并在算法终止时,对于所有的v ∈ V , v . d = δ ( s , v ) v \in V,v.d = \delta(s,v) v ∈ V , v . d = δ ( s , v ) 。而且,对于任意可以从s s s 到达的结点v ≠ s v \neq s v = s ,从源结点s s s 到结点v v v 的其中一条最短路径为从结点s s s 到结点v . π v.\pi v . π 的最短路径再加上边( v . π , v ) (v.\pi,v) ( v . π , v )
广度优先树我们定义图G G G 的前驱子图 为G π = ( V π , E π ) G_\pi =(V_\pi,E_\pi) G π = ( V π , E π ) ,其中V π = { v ∈ V : v . π ≠ N I L } ∪ { s } V_\pi = \{v\in V:v.\pi \neq NIL\} \cup \{s\} V π = { v ∈ V : v . π = N I L } ∪ { s } ,E π = { ( v . π , v ) : v ∈ V π − { s } } E_\pi = \{(v.\pi,v):v \in V_\pi - \{s\}\} E π = { ( v . π , v ) : v ∈ V π − { s } } 当运行在一个有向或无向图G = ( V , E ) G=(V,E) G = ( V , E ) 上时,BFS
过程所建造出来的π \pi π 属性使得前驱子图G π = ( V π , E π ) G_\pi=(V_\pi,E_\pi) G π = ( V π , E π ) 成为一棵广度优先树
PRINT-PATH
可打印出从源结点s s s 到结点v v v 的一条最短路径上的所有结点,这里假定 BFS
已经计算出一棵广度优先树:
1 2 3 4 5 6 7 8 PRINT-PATH(G, s, v) if v == s print s elseif v.π == NIL print "no path from" s "to" v "exists" else PRINT-PATH(G, s, v.π) print v
深度优先搜索(DFS)我们定义图G G G 的前驱子图 为G π = ( V , E π ) G_\pi =(V,E_\pi) G π = ( V , E π ) ,其中E π = { ( v . π , v ) : v ∈ V ∧ v . π ≠ N I L } E_\pi = \{(v.\pi,v):v \in V \land v.\pi \neq NIL\} E π = { ( v . π , v ) : v ∈ V ∧ v . π = N I L } 与广度优先搜索不同,深度优先搜索的前驱子图可能由多颗树 组成,因为搜索可能从多个源结点重复进行 深度优先搜索的前驱子图形成一个由多颗深度优先树 构成的深度优先森林 ,森林E π E_\pi E π 中的边仍然称为树边
像广度优先搜索一样,深度优先搜索算法在搜索过程中也是对结点进行涂色来指明结点的状态。每个结点的初始颜色都是白色,在结点被发现 后变为灰色,在其邻接链表被扫描完成 后变为黑色。该方法可以保证每个结点仅在一棵深度优先树中出现,因此,所有的深度优先树是不相交的
深度优先算法在每个结点盖上一个时间戳 。每个结点v v v 有两个时间戳:第一个时间戳v . d v.d v . d 记录结点v v v 第一次被发现的时间(涂上灰色的时候),第二个时间戳v . f v.f v . f 记录的是搜索完成对v v v 的邻接链表扫描的时间(涂上黑色的时候)
输入图G G G 既可以是无向图,可以是有向图。变量t i m e time t i m e 是一个全局变量,用来计算时间戳:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 DFS(G) for each vertex u in G.V u.color = WHITE u.π = NIL time = 0 for each vertex u in G.V if u.color == WHITE DFS-VISIT(G, u) DFS-VISIT(G, u) time = time + 1 u.d = time u.color = GRAY for each v in G.Adj[u] if v.color == WHITE v.π = u DFS-VISIT(G, v) u.color = BLACK time = time + 1 u.f = time
分析深度优先搜索的总运行时间为O ( V + E ) O(V+E) O ( V + E )
深度优先搜索的性质括号化定理 :在对有向或无向图G = ( V , E ) G=(V,E) G = ( V , E ) 进行的任意深度优先搜索中,对于任意两个结点u u u 和v v v 来说,下面三种情况只有一成立:
区间[ u . d , u . f ] [u.d,u.f] [ u . d , u . f ] 和区间[ v . d , v . f ] [v.d,v.f] [ v . d , v . f ] 完全分离,在深度优先森林中,结点u u u 不是结点v v v 的后代,结点v v v 也不是结点u u u 的后代 区间[ u . d , u . f ] [u.d,u.f] [ u . d , u . f ] 完全包含在区间[ v . d , v . f ] [v.d,v.f] [ v . d , v . f ] 内,在深度优先树中,结点u u u 是结点v v v 的后代 区间[ v . d , v . f ] [v.d,v.f] [ v . d , v . f ] 完全包含在区间[ u . d , u . f ] [u.d,u.f] [ u . d , u . f ] 内,在深度优先树中,结点v v v 是结点u u u 的后代 后代区间的嵌套 :在有向或无向图G G G 的深度优先森林中,结点v v v 是结点u u u 的真后代当且仅当u . d < v . d < v . f < u . f u.d<v.d<v.f<u.f u . d < v . d < v . f < u . f 成立白色路径定理 :在有向或无向图G = ( V , E ) G=(V,E) G = ( V , E ) 的深度优先森林中,结点v v v 是结点u u u 的后代当且仅当在发现结点u u u 的时间u . d u.d u . d ,存在一条从结点u u u 到结点v v v 的全部由白色结点所构成的路径
边的分类树边 :为深度优先森林G π G_\pi G π 中的边,如果结点v v v 是因算法对边( u , v ) (u,v) ( u , v ) 的探索而首先被发现,则( u , v ) (u,v) ( u , v ) 是一条树边后向边 :后向边( u , v ) (u,v) ( u , v ) 是将结点u u u 连接到其在深度优先树中(一个)祖先结点v v v 的边。由于有向图中可以有自循环,自循环也被认为是后向边前向边 :是将结点u u u 连接到其在深度优先树中一个后代结点v v v 的边( u , v ) (u,v) ( u , v ) 横向边 :指其他所有的边。这些边可以连接同一棵深度优先树中的结点,只要其中一个结点不是另外一个结点的祖先,也可以连接不同深度优先树中的两个结点结点v v v 的颜色能够告诉我们关于该条边的一些信息:
结点v v v 为白色 表明该条边( u , v ) (u,v) ( u , v ) 是一条树边 结点v v v 为灰色 表明该条边( u , v ) (u,v) ( u , v ) 是一条后向边 结点v v v 为黑色 表明该条边( u , v ) (u,v) ( u , v ) 是一条前向边或横向边 在对无向图G G G 进行深度优先搜索时,每条边要么是树边 ,要么是后向边
拓扑排序对于一个有向无环图G = ( V , E ) G=(V,E) G = ( V , E ) 来说,其拓扑排序是G G G 中所有结点的一种线性次序,该次序满足如下条件: 如果图G G G 包含边( u , v ) (u,v) ( u , v ) ,则结点u u u 在拓扑排序中处于结点v v v 的前面(如果图G G G 包含环路,则不可能排出一个线性次序)
下面的简单算法可以对一个有向无环图进行拓扑排序,完成时间Θ ( V + E ) \Theta(V+E) Θ ( V + E ) :
1 2 3 4 TOPOLOGICAL-SORT(G) call DFS(G) to compute finishing times v.f for each vertex v as each vertex is finished, insert it onto the front of a linked list return the linked list of vertices
强连通分量有向图G = ( V , E ) G=(V,E) G = ( V , E ) 的强连通分量是一个最大结点集合C ⊆ V C \subseteq V C ⊆ V ,对于该集合中的任意一对结点u u u 和v v v 来说,路径u ⇝ v u \leadsto v u ⇝ v 和路径v ⇝ u v \leadsto u v ⇝ u 同时存在
下面的 Kosaraju
算法使用两次深度优先搜索来计算有向图G − = ( V , E ) G-=(V,E) G − = ( V , E ) 的强连通分量,时间复杂度Θ ( V + E ) \Theta(V+E) Θ ( V + E ) :
1 2 3 4 5 6 7 STRONGLY-CONNECTED-COMPONENTS(G) call DFS(G) to compute finishing times u.f for each vertex u compute G^T call DFS(G^T), but in the main loop of DFS, consider the vertices in order of decreasing u.f (as computed in line 1) output the vertices of each tree in the depth-first forest formed in line 3 as a separate strongly connected component
最小生成树 最小生成树的形成假定有一个连通无向图G = ( V , E ) G=(V,E) G = ( V , E ) 和权重函数w : E → R w:E\rightarrow \Reals w : E → R 。我们希望找出图G G G 的一棵最小生成树
使用贪心策略来生成最小生成树:
1 2 3 4 5 6 GENERIC-MST(G, w) A = ∅ while A does not form a spanning tree find an edge(u,v) that is safe for A A = A ∪ {(u,v)} return A
Kruskal算法Kruskal
算法使用一个不相交集合数据结构来维护几个互不相交的元素集合。每个集合代表当前森林中的一棵树。通过测试 FIND-SET(u)
是否等于 FIND-SET(v)
来判断结点u u u 和结点v v v 是否属于同一棵树,使用 UNION
过程来对两棵树进行合并 时间复杂度O ( E l g V ) O(ElgV) O ( E l g V ) :
1 2 3 4 5 6 7 8 9 10 MST-KRUSKAL(G, w) A = ∅ for each vertex v in G.V MAKE-SET(v) sort the edges of G.E into nondecreasing order by weight w for each edge(u,v) in G.E, taken in nondecreasing order by weight if FIND-SET(u) != FIND-SET(v) A = A ∪ {(u,v)} UNION(u,v) return A
Prim算法Prim
算法每一步在连接集合A A A 和A A A 之外的结点的所有边中,选择一条轻量级边加入到A A A 中
连通图G G G 和最小生成树的根结点r r r 将作为算法的输入。在算法的执行过程中,所有不在 树A A A 中的结点都存放在一个基于k e y key k e y 属性的最小优先队列Q Q Q 中。对每个结点v v v ,属性v . k e y v.key v . k e y 保存的是连接v v v 和树中结点的所有边中最小边的权重。属性v . π v.\pi v . π 给出的是结点v v v 在树中的父结点
若使用二叉最小优先队列,时间复杂度O ( V l g V + E l g V ) = O ( E l g V ) O(VlgV+ElgV)=O(ElgV) O ( V l g V + E l g V ) = O ( E l g V ) ;若使用斐波那契堆来实现最小优先队列,时间复杂度O ( E + V l g V ) O(E+VlgV) O ( E + V l g V ) :
1 2 3 4 5 6 7 8 9 10 11 12 MST-PRIM(G, w, r) for each u in G.V u.key = ∞ u.π = NIL r.key = 0 Q = G.V while Q != ∅ u = EXTRACT-MIN(Q) for each v in G.Adj[u] if v in Q and w(u,v) < v.key v.π = u v.key = w(u,v)
单源最短路径在最短路径问题 中,我们给定一个带权重的有向图G = ( V , E ) G=(V,E) G = ( V , E ) 和权重函数w : E → R w: E\rightarrow \Reals w : E → R ,该权重函数将每条边映射到实数值的权重上 图中一条路径p = ⟨ v 0 , v 1 , . . . , b k ⟩ p=\langle v_0,v_1,...,b_k \rangle p = ⟨ v 0 , v 1 , . . . , b k ⟩ 的权重w ( p ) w(p) w ( p ) 是构成该路径的所有边的权重之和:
w ( p ) = ∑ i = 1 k w ( v i − 1 , v i ) w(p)=\sum^k_{i=1}w(v_{i-1},v_i) w ( p ) = i = 1 ∑ k w ( v i − 1 , v i )
定义从结点au u u 到结点v v v 的最短路径权重 δ ( u , v ) \delta(u,v) δ ( u , v ) 如下:
δ ( u , v ) ≤ { m i n { w ( p ) : u ⇝ p v } i f e x i s t s a p a t h f r o m u t o v ∞ o t h e r \delta(u,v)\leq \begin{cases} min\{w(p):u \leadsto^p v \} \qquad & if \ exists \ a\ path\ from\ u \ to \ v \\ \infty \qquad & other \end{cases} δ ( u , v ) ≤ { m i n { w ( p ) : u ⇝ p v } ∞ i f e x i s t s a p a t h f r o m u t o v o t h e r
从结点u u u 到结点v v v 的最短路径 定义为任何一条权重为w ( p ) = δ ( w , v ) w(p)=\delta(w,v) w ( p ) = δ ( w , v ) 的从u u u 到v v v 的路径p p p
常规概念 负权重的边如果图G = ( V , E ) G=(V,E) G = ( V , E ) 不包含从源结点s s s 可以到达的权重为负值的环路,则对于所有的结点v ∈ V v \in V v ∈ V ,最短路径权重δ ( s , v ) \delta(s,v) δ ( s , v ) 都有精确定义,即使其取值为负数 如果图G G G 包含从s s s 可以达到的权重为负值的环路,则最短路径权重无定义
环路最短路径不能包含权重为负值的环路 最短路径不能包含权重为正值的环路
最短路径的表示π \pi π 值所诱导的前驱子图 G π = ( V π , E π ) G_\pi=(V_\pi,E_\pi) G π = ( V π , E π ) 定义如下:
V π = { v ∈ V : v . π ≠ N I L } ∪ { s } V_\pi = \{v\in V:v.\pi \neq NIL\} \cup \{s\} V π = { v ∈ V : v . π = N I L } ∪ { s }
E π = { ( v . π , v ) ∈ E : v ∈ V π − { s } } E_\pi = \{(v.\pi,v) \in E:v \in V_\pi - \{s\}\} E π = { ( v . π , v ) ∈ E : v ∈ V π − { s } }
在算法终止时,G π G_\pi G π 是一棵最短路径树 最短路径不一定是唯一的,最短路径树也不一定是唯一的
松弛操作对于每个结点v v v ,我们维持一个属性v . d v.d v . d 来记录从原结点s s s 到结点v v v 的最短路径权重的上界。我们称v . d v.d v . d 为s s s 到v v v 的最短路径估计 。我们使用下面运行时间为Θ ( V ) \Theta(V) Θ ( V ) 的算法来对最短路径估计和前驱结点进行初始化:
1 2 3 4 5 INITIALIZE-SINGLE-SOURCE(G, s) for each vertex v in G.V v.d = ∞ v.π = NIL s.d = 0
RELAX
过程对边( u , v ) (u,v) ( u , v ) 在O ( 1 ) O(1) O ( 1 ) 时间内进行松弛操作:
1 2 3 4 RELAX(u, v, w) if v.d > u.d + w(u,v) v.d = u.d + w(u,v) v.π = u
最短路径和松弛操作的性质三角不等式性质 :对于任何边( u , v ) ∈ E (u,v) \in E ( u , v ) ∈ E ,有δ ( s , v ) ≤ δ ( s , u ) + w ( u , v ) \delta(s,v) \leq \delta(s,u)+w(u,v) δ ( s , v ) ≤ δ ( s , u ) + w ( u , v ) 上界性质 :对于所有的结点v ∈ V v \in V v ∈ V ,有v . d ≥ δ ( s , v ) v.d \geq \delta(s,v) v . d ≥ δ ( s , v ) 。一旦v . d v.d v . d 的取值达到δ ( s , v ) \delta(s,v) δ ( s , v ) ,其值将不再发生变化非路径性质 :如果从结点s s s 到结点v v v 之间不存在路径,则有v . d = δ ( s , v ) = ∞ v.d=\delta(s,v)=\infty v . d = δ ( s , v ) = ∞ 收敛性质 :对于某些结点u , v ∈ V u,v \in V u , v ∈ V ,如果s ⇝ u → v s \leadsto u \rightarrow v s ⇝ u → v 是图G G G 中的一条最短路径,并且在对边( u , v ) (u,v) ( u , v ) 进行松弛前的任意时间有u . d = δ ( s , u ) u.d=\delta(s,u) u . d = δ ( s , u ) ,则在之后的所有时间有v . d = δ ( s , v ) v.d=\delta(s,v) v . d = δ ( s , v ) 路径松弛性质 :如果p = ⟨ v 0 , v 1 , . . . , v k ⟩ p=\langle v_0,v_1,...,v_k \rangle p = ⟨ v 0 , v 1 , . . . , v k ⟩ 是从源结点s = v 0 s=v_0 s = v 0 到结点v k v_k v k 的一条最短路径,且对p p p 中的边所进行的松弛的次序为( v 0 , v 1 ) (v_0,v_1) ( v 0 , v 1 ) ,( v 1 , v 2 ) (v_1,v_2) ( v 1 , v 2 ) ,…,( v k − 1 , v k ) (v_{k-1},v_k) ( v k − 1 , v k ) ,则v k . d = δ ( s , v k ) v_k.d=\delta(s,v_k) v k . d = δ ( s , v k ) ,该性质的成立与任何其他的松弛操作无关,即使这些松弛操作是与对p p p 上的边所进行的松弛操作是穿插进行的
Bellman-Ford算法Bellman-Ford
算法解决一般情况下的单元最短路径问题,边的权重可以为负值Bellman-Ford
算法返回 TRUE
值当且仅当输入图不包含可以从源结点到达的权重为负值的环路:
1 2 3 4 5 6 7 8 9 BELLMAN-FORD(G, w, s) INITIALIZE-SINGLE-SOURCE(G, s) for i = 1 to |G.V| - 1 for each edge(u,v) in G.E RELAX(u, v, w) for each edge(u,v) in G.E if v.d > u.d + w(u,v) return FALSE return TRUE
Bellman-Ford
算法总运行时间O ( V E ) O(VE) O ( V E )
有向无环图中的单源最短路径问题根据结点的拓扑排序次序来对带权重的有向无环图G = ( V , E ) G=(V,E) G = ( V , E ) 进行边的松弛操作,我们便可以在O ( V , E ) O(V,E) O ( V , E ) 时间内计算出从单个源结点到所有结点之间的最短路径:
1 2 3 4 5 6 DAG-SHORTEST-PATHS(G, w, s) topologically sort the vertices of G INITIALIZE-SINGLE-SOURCE(G, s) for each vertex u, taken in topologically sorted order for each vertex v in G.Adj[u] RELAX(u, v, w)
Dijkstra算法Dijkstra
算法要求所有边的权重都为非负值:
1 2 3 4 5 6 7 8 9 DIJKSTRA(G, w, s) INITIALIZE-SINGLE-SOURCE(G, s) S = ∅ Q = G.V while Q != ∅ u = EXTRACT-MIN(Q) S = S ∪ {u} for each vertex v in G.Adj[u] RELAX(u, v, w)
Dijkstra
算法总运行时间依赖于最小优先队列的实现:
通过利用结点的编号为1 − ∣ V ∣ 1 - |V| 1 − ∣ V ∣ 来维持最小优先队列。在这种情况下,每次 INSERT
和 DECREASE-KEY
操作的执行时间为O ( 1 ) O(1) O ( 1 ) ,每次 EXTRACT-MIN
的操作时间为O ( V ) O(V) O ( V ) ,算法总运行时间为O ( V 2 + E ) = O ( V 2 ) O(V^2+E)=O(V^2) O ( V 2 + E ) = O ( V 2 ) 若图为稀疏图,特别地,如果E = o ( V 2 / l g V ) E=o(V^2/lgV) E = o ( V 2 / l g V ) ,则可以通过二叉堆来实现最小优先队列,每次 EXTRACT-MIN
的操作时间为O ( l g V ) O(lgV) O ( l g V ) ,每次 DECREASE-KEY
的操作时间为O ( l g V ) O(lgV) O ( l g V ) ,构建最小二叉堆的成本为O ( V ) O(V) O ( V ) ,算法总运行时间为O ( ( V + E ) l g V ) O((V+E)lgV) O ( ( V + E ) l g V ) 。若所有结点都可以从源结点到达,则该时间为O ( E l g V ) O(ElgV) O ( E l g V ) 若使用斐波那契堆来时间最小优先队列,每次 EXTRACT-MIN
的操作时间为O ( l g V ) O(lgV) O ( l g V ) ,每次 DECREASE-KEY
的操作时间为O ( 1 ) O(1) O ( 1 ) ,算法总运行时间为O ( V l g V + E ) O(VlgV+E) O ( V l g V + E ) 差分约束和最短路径 差分约束系统设向量x = ( x 1 , x 2 , . . . , x n ) x=(x_1,x_2,...,x_n) x = ( x 1 , x 2 , . . . , x n ) 为差分约束系统A x ≤ b Ax \leq b A x ≤ b 的一个解,设d d d 为任意常数,则x + d = ( x 1 + d , x 2 + d , . . . , x n + d ) x+d=(x_1+d,x_2+d,...,x_n+d) x + d = ( x 1 + d , x 2 + d , . . . , x n + d ) 也是该差分约束系统的一个解
约束图给定差分约束系统A x ≤ b Ax \leq b A x ≤ b ,其对应的约束图 是一个带权重的有向图G = ( V , E ) G=(V,E) G = ( V , E ) ,这里:
V = { v 0 , v 1 , . . . , v n } V=\{v_0,v_1,...,v_n\} V = { v 0 , v 1 , . . . , v n }
E = { ( v i , v j ) : v j − v i ≤ b k 是一个约束条件 } ∪ { ( v 0 , v 1 ) , ( v 0 , v 2 ) , . . . ( v 0 , v n ) } E=\{(v_i,v_j):v_j-v_i \leq b_k\text{是一个约束条件}\} \cup \{(v_0,v_1),(v_0,v_2),...(v_0,v_n)\} E = { ( v i , v j ) : v j − v i ≤ b k 是一个约束条件 } ∪ { ( v 0 , v 1 ) , ( v 0 , v 2 ) , . . . ( v 0 , v n ) }
约束图包含一个额外的结点v 0 v_0 v 0 ,用来保证图中至少存在一个结点,从其出发可以到达所以其他的结点 如果x j − x i ≤ b k x_j-x_i \leq b_k x j − x i ≤ b k 是一个差分约束条件,则边( v i , v j ) (v_i,v_j) ( v i , v j ) 的权重为w ( v i , v j ) = b k w(v_i,v_j)=b_k w ( v i , v j ) = b k 所有从结点v 0 v_0 v 0 发出的边的权重为0 给定差分约束系统A x ≤ b Ax \leq b A x ≤ b ,设G = ( V , E ) G=(V,E) G = ( V , E ) 是该差分约束系统所对应的约束图。如果图G G G 不包含权重为负值的环路,则:
x = ( δ ( v 0 , v 1 ) , δ ( v 0 , v 2 ) , . . . , δ ( v 0 , v n ) ) x=(\delta(v_0,v_1),\delta(v_0,v_2),...,\delta(v_0,v_n)) x = ( δ ( v 0 , v 1 ) , δ ( v 0 , v 2 ) , . . . , δ ( v 0 , v n ) )
是该系统的一个可行解。如果图G G G 包含权重为负值的环路,则该系统没有可行解
求解差分约束系统一个有n n n 个未知变量和m m m 个约束条件的差分约束系统所生成的约束图有n + 1 n+1 n + 1 个结点和m + n m+n m + n 条边。使用 Bellman-Ford
算法可以在O ( ( n + 1 ) ( m + n ) ) = O ( n 2 + m n ) O((n+1)(m+n)) = O(n^2+mn) O ( ( n + 1 ) ( m + n ) ) = O ( n 2 + m n ) 时间内求解该系统
所有结点对的最短路径假定结点的编号为1 , 2 , . . . , ∣ V ∣ 1,2,...,|V| 1 , 2 , . . . , ∣ V ∣ ,因此,算法的输入将是一个n × n n \times n n × n 的矩阵W W W ,该矩阵代表的是一个有n n n 个结点的有向图G = ( V , E ) G=(V,E) G = ( V , E ) 的边的权重,即W = ( w i j ) W=(w_{ij}) W = ( w i j ) ,其中:
w i j = { 0 i = j w i j i ≠ j ∧ ( i , j ) ∈ E ∞ i ≠ j ∧ ( i , j ) ∉ E w_{ij} = \begin{cases} 0 \qquad & i=j \\ w_{ij} \qquad & i \neq j \land (i,j) \in E \\ \infty \qquad & i \neq j \land (i,j) \notin E \end{cases} w i j = ⎩ ⎪ ⎪ ⎨ ⎪ ⎪ ⎧ 0 w i j ∞ i = j i = j ∧ ( i , j ) ∈ E i = j ∧ ( i , j ) ∈ / E
我们允许存在负权重的边,但假定图中不包括权重为负值的环路
所有结点对最短路径算法的表格输出也是一个n × n n \times n n × n 的矩阵D = ( d i j ) D=(d_{ij}) D = ( d i j ) ,其中d i j d_{ij} d i j 代表的是从结点i i i 到结点j j j 的一条最短路径的权重 前驱结点矩阵Π = ( π i j ) \Pi =(\pi_{ij}) Π = ( π i j ) ,其中π i j \pi_{ij} π i j 在i = j i=j i = j 或从i i i 到j j j 不存在路径时为N I L NIL N I L ,在其他情况下给出的是从结点i i i 到结点j j j 的某条最短路径上结点j j j 的前驱结点。由矩阵Π \Pi Π 的第i i i 行所诱导的子图一个是一棵根结点为i i i 的最短路径树。对于每个结点i ∈ V i \in V i ∈ V ,定义图G G G 对于结点i i i 的前驱子图为G π , i = ( V π , i , E π , i ) G_{\pi,i}=(V_{\pi,i},E_{\pi,i}) G π , i = ( V π , i , E π , i ) ,其中:
V π , i = { j ∈ V : π i j ≠ N I L } ∪ { i } E π , i = { ( π i j , j ) : j ∈ V π , i − { i } } V_{\pi,i}=\{j\in V:\pi_{ij} \neq NIL\} \cup \{i\} \qquad E_{\pi,i}=\{(\pi_{ij},j):j\in V_{\pi,i}-\{i\}\} V π , i = { j ∈ V : π i j = N I L } ∪ { i } E π , i = { ( π i j , j ) : j ∈ V π , i − { i } }
如果G π , i G_{\pi,i} G π , i 是一棵最短路径树,则下面的过程将打印出从结点i i i 到结点j j j 的一条最短路径:
1 2 3 4 5 6 7 8 PRINT-ALL-PAIRS-SHORTEST-PATH(Π, i, j) if i == j print i elseif π_{ij} == NIL print "no path from" i "to" j "exists" else PRINT-ALL-PAIRS-SHORTEST-PATH(Π, i, π_{ij}) print j
最短路径和矩阵乘法 最短路径的结构考虑从结点i i i 到结点j j j 的一条最短路径p p p ,假定p p p 至多包含m m m 条边,还假定没有权重为负值的环路,且m m m 为有限值。如果i = j i=j i = j ,则p p p 的权重为0且不包含任何边。如果结点i i i 和结点j j j 不同,则将路径分解为i ⇝ p ′ k → j i \leadsto^{p'}k \rightarrow j i ⇝ p ′ k → j ,其中路径p ′ p' p ′ 至多包含m − 1 m-1 m − 1 条边,则有:
δ ( i , j ) = δ ( i , k ) + w k j \delta(i,j)=\delta(i,k)+w_{kj} δ ( i , j ) = δ ( i , k ) + w k j
所有结点对最短路径问题的递归解设l i j ( m ) l^{(m)}_{ij} l i j ( m ) 为从结点i i i 到结点j j j 的至多包含m m m 条边的任意路径中的最小权重。当m = 0 m=0 m = 0 时,从结点i i i 到结点j j j 之间存在一条没有边的最短路径当且仅当i = j i=j i = j ,因此有:
l i j ( m ) = { 0 i = j ∞ i ≠ j l^{(m)}_{ij} = \begin{cases} 0 \qquad & i=j \\ \infty \qquad & i \neq j \end{cases} l i j ( m ) = { 0 ∞ i = j i = j
递归定义:
l i j ( m ) = m i n { l i j ( m − 1 ) , m i n 1 ≤ k ≤ n { l i j ( m − 1 ) + w k j } } = m i n 1 ≤ k ≤ n { l i j ( m − 1 ) + w k j } l^{(m)}_{ij} = min\{l^{(m-1)}_{ij},min_{1 \leq k \leq n}\{l^{(m-1)}_{ij} +w_{kj}\}\} = min_{1 \leq k \leq n}\{l^{(m-1)}_{ij} + w_{kj}\} l i j ( m ) = m i n { l i j ( m − 1 ) , m i n 1 ≤ k ≤ n { l i j ( m − 1 ) + w k j } } = m i n 1 ≤ k ≤ n { l i j ( m − 1 ) + w k j }
最短路径权重可以由下面的公式给出:
δ ( i , j ) = l i j ( n − 1 ) = l i j ( n ) = l i j ( n + 1 ) = . . . \delta(i,j)=l^{(n-1)}_{ij}=l^{(n)}_{ij}=l^{(n+1)}_{ij} = ... δ ( i , j ) = l i j ( n − 1 ) = l i j ( n ) = l i j ( n + 1 ) = . . .
自底向上计算最短路径权重EXTEND-SHORTEST-PATHS
过程可以在给定w w w 和L ( m − 1 ) L^{(m-1)} L ( m − 1 ) 的情况下,计算出L ( m ) L^{(m)} L ( m ) ,算法运行时间Θ ( n 3 ) \Theta(n^3) Θ ( n 3 ) :
1 2 3 4 5 6 7 8 9 EXTEND-SHORTEST-PATHS(L, W) n = L.rows let L' = (l'_{ij}) be a new n * n matrix for i = 1 to n for j = 1 to n l'_{ij} = ∞ for k = 1 to n l'_{ij} = min(l'_{ij}, l_{ik} + w_{kj}) return L'
设A ⋅ B A \cdot B A ⋅ B 表示由算法 EXTEND-SHORTEST-PATHS(A, B)
所返回的矩阵“乘积”,我们可以计算出下面由n − 1 n-1 n − 1 个矩阵所构成的矩阵序列:L ( 1 ) = L ( 0 ) ⋅ W = W L^{(1)} = L^{(0)} \cdot W = W L ( 1 ) = L ( 0 ) ⋅ W = W L ( 2 ) = L ( 1 ) ⋅ W = W 2 L^{(2)} = L^{(1)} \cdot W = W^2 L ( 2 ) = L ( 1 ) ⋅ W = W 2 L ( 3 ) = L ( 2 ) ⋅ W = W 3 L^{(3)} = L^{(2)} \cdot W = W^3 L ( 3 ) = L ( 2 ) ⋅ W = W 3 …L ( n − 1 ) = L ( n − 2 ) ⋅ W = W n − 1 L^{(n-1)} = L^{(n-2)} \cdot W = W^{n-1} L ( n − 1 ) = L ( n − 2 ) ⋅ W = W n − 1
SLOW-ALL-PAIRS-SHORTEST-PATHS
过程可以在Θ ( n 4 ) \Theta(n^4) Θ ( n 4 ) 的时间内计算出矩阵L ( n − 1 ) = W n − 1 L^{(n-1)} = W^{n-1} L ( n − 1 ) = W n − 1 :
1 2 3 4 5 6 7 SLOW-ALL-PAIRS-SHORTEST-PATHS(W) n = W.rows L(1) = W for m = 2 to n-1 let L(m) be a new n * n matrix L(m) = EXTEND-SHORTEST-PATHS(L(m-1), W) return L(n-1)
可以仅用⌈ l g ( n − 1 ) ⌉ \lceil lg(n-1) \rceil ⌈ l g ( n − 1 ) ⌉ 个矩阵乘积来计算矩阵L ( n − 1 ) L^{(n-1)} L ( n − 1 ) :L ( 1 ) = W L^{(1)} = W L ( 1 ) = W L ( 2 ) = W 2 = W ⋅ W L^{(2)} = W^2 = W \cdot W L ( 2 ) = W 2 = W ⋅ W L ( 4 ) = W 4 = W 2 ⋅ W 2 L^{(4)} = W^4 = W^2 \cdot W^2 L ( 4 ) = W 4 = W 2 ⋅ W 2 L ( 8 ) = W 8 = W 4 ⋅ W 4 L^{(8)} = W^8 = W^4 \cdot W^4 L ( 8 ) = W 8 = W 4 ⋅ W 4 …L ( 2 ⌈ l g ( n − 1 ) ⌉ ) = W 2 ⌈ l g ( n − 1 ) ⌉ = W 2 ⌈ l g ( n − 1 ) ⌉ − 1 ⋅ W 2 ⌈ l g ( n − 1 ) ⌉ − 1 L^{(2^{\lceil lg(n-1) \rceil})} = W^{2^{\lceil lg(n-1) \rceil}} = W^{2^{\lceil lg(n-1) \rceil-1}} \cdot W^{2^{\lceil lg(n-1) \rceil-1}} L ( 2 ⌈ l g ( n − 1 ) ⌉ ) = W 2 ⌈ l g ( n − 1 ) ⌉ = W 2 ⌈ l g ( n − 1 ) ⌉ − 1 ⋅ W 2 ⌈ l g ( n − 1 ) ⌉ − 1 由于2 ⌈ l g ( n − 1 ) ⌉ ≥ n − 1 2^{\lceil lg(n-1) \rceil}\geq n-1 2 ⌈ l g ( n − 1 ) ⌉ ≥ n − 1 ,最后有L ( 2 ⌈ l g ( n − 1 ) ⌉ ) = L ( n − 1 ) L^{(2^{\lceil lg(n-1) \rceil})}=L^{(n-1)} L ( 2 ⌈ l g ( n − 1 ) ⌉ ) = L ( n − 1 ) FASTER-ALL-PAIRS-SHORTEST-PATHS
过程使用重复平方技术来计算上述矩阵序列,运行时间为Θ ( n 3 l g n ) \Theta(n^3lgn) Θ ( n 3 l g n ) :
1 2 3 4 5 6 7 8 FASTER-ALL-PAIRS-SHORTEST-PATHS(W) n = W.rows L(1) = W m = 1 while m < n-1 let L(2m) be a new n * n matrix L(2m) = EXTEND-SHORTEST-PATHS(L(m), L(m)) return L(m)
Floyd-Warshall算法 所有结点对最短路径问题的一个递归解设d i j ( k ) d^{(k)}_{ij} d i j ( k ) 为从结点i i i 到结点j j j 的所有中间结点全部取自集合{ 1 , 2 , . . . , k } \{1,2,...,k\} { 1 , 2 , . . . , k } 的一条最短路径的权重。递归定义d i j ( k ) d^{(k)}_{ij} d i j ( k ) 如下:
d i j ( k ) = { w i j k = 0 m i n ( d i j ( k − 1 ) , d i k ( k − 1 ) + d k j ( k − 1 ) ) k ≥ 1 d^{(k)}_{ij} = \begin{cases} w_{ij} \qquad & k=0 \\ min(d^{(k-1)}_{ij},d^{(k-1)}_{ik}+d^{(k-1)}_{kj}) \qquad & k \geq 1 \end{cases} d i j ( k ) = { w i j m i n ( d i j ( k − 1 ) , d i k ( k − 1 ) + d k j ( k − 1 ) ) k = 0 k ≥ 1
矩阵D ( n ) = ( d i j ( n ) ) D^{(n)} = (d^{(n)}_{ij}) D ( n ) = ( d i j ( n ) ) 给出的就是最后的答案:对于所有的i , j ∈ V i,j \in V i , j ∈ V ,d i j ( n ) = δ ( i , j ) d^{(n)}_{ij}=\delta(i,j) d i j ( n ) = δ ( i , j )
自底向上计算最短路径权重FLOYD-WARSHALL
算法的输入为一个n × n n \times n n × n 的矩阵W W W ,返回最短路径权重矩阵D ( n ) D^{(n)} D ( n ) ,运行时间为Θ ( V 3 ) \Theta(V^3) Θ ( V 3 ) :
1 2 3 4 5 6 7 8 9 FLOYD-WARSHALL(W) n = W.rows D(0) = W for k = 1 to n let D(k) = (d(k)_{ij}) be a new n * n matrix for i = 1 to n for j = 1 to n d(k)_{ij} = min(d(k-1)_{ij},d(k-1)_{ik}+d(k-1)_{kj}) return D(n)
构建一条最短路径在 Floyd-Warshall
算法中,有多种不同的方法来构建最短路径
先计算最短路径权重矩阵D D D ,然后从D D D 矩阵来构造前驱矩阵Π \Pi Π 可以在计算矩阵D ( k ) D^{(k)} D ( k ) 的同时计算前驱矩阵Π \Pi Π 。即计算一个矩阵序列Π ( 0 ) \Pi^{(0)} Π ( 0 ) ,Π ( 1 ) \Pi^{(1)} Π ( 1 ) ,…,Π ( n ) \Pi^{(n)} Π ( n ) 。定义π i j ( k ) \pi^{(k)}_{ij} π i j ( k ) 为从结点i i i 到结点j j j 的一条所有中间结点都取自集合{ 1 , 2 , . . . , k } \{1,2,...,k\} { 1 , 2 , . . . , k } 的最短路径上j j j 的前驱结点 当k = 0 k=0 k = 0 时,从i i i 到j j j 的一条最短路径没有中间结点,因此:
π i j ( 0 ) = { N I L i = j ∨ w i j = ∞ i i ≠ j ∧ w i j < ∞ \pi^{(0)}_{ij} = \begin{cases} NIL \qquad & i=j \lor w_{ij} = \infty \\ i \qquad & i \neq j \land w_{ij} < \infty \end{cases} π i j ( 0 ) = { N I L i i = j ∨ w i j = ∞ i = j ∧ w i j < ∞
对于k ≥ 1 k \geq 1 k ≥ 1 ,如果考虑路径i ⇝ k ⇝ j , k ≠ j i \leadsto k \leadsto j,k \neq j i ⇝ k ⇝ j , k = j ,则所选择的结点j j j 的前驱与我们选择的从结点k k k 到结点j j j 的一条中间结点全部取自集合{ 1 , 2 , . . . , k − 1 } \{1,2,...,k-1\} { 1 , 2 , . . . , k − 1 } 的最短路径上的前驱是一样的。否则,所选择的结点j j j 的前驱与选择的从结点i i i 到结点j j j 的一条中间结点全部取自集合{ 1 , 2 , . . . , k − 1 } \{1,2,...,k-1\} { 1 , 2 , . . . , k − 1 } 的最短路径上的前驱是一样的。也就是说,对于k ≥ 1 k \geq 1 k ≥ 1 ,有:
π i j ( k ) = { π i j ( k − 1 ) d i j ( k − 1 ) ≤ d i k ( k − 1 ) + d k j ( k − 1 ) π k j ( k − 1 ) d i j ( k − 1 ) > d i k ( k − 1 ) + d k j ( k − 1 ) \pi^{(k)}_{ij} = \begin{cases} \pi^{(k-1)}_{ij} \qquad & d^{(k-1)}_{ij} \leq d^{(k-1)}_{ik}+d^{(k-1)}_{kj} \\ \pi^{(k-1)}_{kj} \qquad & d^{(k-1)}_{ij} > d^{(k-1)}_{ik}+d^{(k-1)}_{kj} \end{cases} π i j ( k ) = { π i j ( k − 1 ) π k j ( k − 1 ) d i j ( k − 1 ) ≤ d i k ( k − 1 ) + d k j ( k − 1 ) d i j ( k − 1 ) > d i k ( k − 1 ) + d k j ( k − 1 )
有向图的传递闭包定义图G G G 的传递闭包 为图G ∗ = ( V , E ∗ ) G^*=(V,E^*) G ∗ = ( V , E ∗ ) ,其中E ∗ = { ( i , j ) : 如果图 G 中包含一条从结点 i 到结点 j 的路径 } E^*=\{(i,j):\text{如果图}G\text{中包含一条从结点}i\text{到结点}j\text{的路径}\} E ∗ = { ( i , j ) : 如果图 G 中包含一条从结点 i 到结点 j 的路径 }
一种时间复杂度为Θ ( n 3 ) \Theta(n^3) Θ ( n 3 ) 的计算图G G G 的传递闭包的办法是给E E E 的每条边赋予权重1,然后运行 Floyd-Warshall
算法。如果存在一条从结点i i i 到结点j j j 的路径,则有d i j < n d_{ij}<n d i j < n ;否则d i j = ∞ d_{ij}=\infty d i j = ∞
另一种类似办法是:以逻辑或操作(∨ \lor ∨ )和逻辑与操作(∧ \land ∧ )来替换 Floyd-Warshall
算法中的算术操作 min
和 +
,以此节省时间和空间
对于i , j , k = 1 , 2 , . . . , n i,j,k=1,2,...,n i , j , k = 1 , 2 , . . . , n ,定义:如果图G G G 中存在一条从结点i i i 到结点j j j 的所有中间结点都取自集合{ 1 , 2 , . . . , k } \{1,2,...,k\} { 1 , 2 , . . . , k } 的路径,则t i j ( k ) t^{(k)}_{ij} t i j ( k ) 为1;否则,t i j ( k ) t^{(k)}_{ij} t i j ( k ) 为0。递归定义如下:
t i j ( 0 ) = { 0 i ≠ j ∧ ( i , j ) ∉ E 1 i = j ∨ ( i , j ) ∈ E t^{(0)}_{ij} = \begin{cases} 0 \qquad & i \neq j \land (i,j) \notin E \\ 1 \qquad & i = j \lor (i,j) \in E \end{cases} t i j ( 0 ) = { 0 1 i = j ∧ ( i , j ) ∈ / E i = j ∨ ( i , j ) ∈ E
对于k ≥ 1 k \geq 1 k ≥ 1 :
t i j ( k ) = t i j ( k − 1 ) ∨ ( t i k ( k − 1 ) ∧ t k j ( k − 1 ) ) t^{(k)}_{ij} = t^{(k-1)}_{ij} \lor (t^{(k-1)}_{ik} \land t^{(k-1)}_{kj}) t i j ( k ) = t i j ( k − 1 ) ∨ ( t i k ( k − 1 ) ∧ t k j ( k − 1 ) )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 TRANSITIVE-CLOSURE(G) n = |G.V| let T(0) = (t(0)_{ij}) be a new n * n matrix for i = 1 to n for j = 1 to n if i == j or (i,j) in G.E t(0)_{ij} = 1 else t(0)_{ij} = 0 for k = 1 to n let T(k) = (t(k)_{ij}) be a new n * n matrix for i = 1 to n for j = 1 to n t(k)_{ij} = t(k-1)_{ij} || (t(k-1)_{ik} && t(k-1)_{kj}) return T(n)
用于稀疏图的Johnson算法Johnson
算法可以在O ( V 2 l g V + V E ) O(V^2lgV+VE) O ( V 2 l g V + V E ) 时间内找到所有结点对之间的最短路径
Johnson
算法使用的技术称为重新赋予权重 : 如果图G = ( V , E ) G=(V,E) G = ( V , E ) 中所有的边权重w w w 均为非负值,则可以通过对每一个结点运行一次 Dijkstra
算法来找到所有结点对之间的最短路径;如果使用斐波那契堆最小优先队列,该算法的运行时间O ( V 2 l g V + V E ) O(V^2lgV+VE) O ( V 2 l g V + V E ) 如果图G = ( V , E ) G=(V,E) G = ( V , E ) 包含权重为负值的边,但没有权重为负值的环路,则只要计算出一组新的非负权重值,然后使用同样的方法。新赋予的权重w ^ \hat{w} w ^ 必须满足以下两个重要性质:
对于所有结点对u , v ∈ V u,v \in V u , v ∈ V ,一条路径p p p 是在使用权重函数w w w 时从结点u u u 到结点v v v 的一条最短路径,当且仅当p p p 是在使用权重函数w ^ \hat{w} w ^ 时从u u u 到v v v 的一条最短路径 对于所有的边( u , v ) (u,v) ( u , v ) ,新权重w ^ ( u , v ) \hat{w}(u,v) w ^ ( u , v ) 为非负值 重新赋予权重来维持最短路径给定带权重的有向图G = ( V , E ) G=(V,E) G = ( V , E ) ,其权重函数为w : E → R w:E \rightarrow \Reals w : E → R ,设h : V → R h:V \rightarrow \Reals h : V → R 为任意函数,该函数将结点映射到实数上。对于每条边( u , v ) ∈ E (u,v) \in E ( u , v ) ∈ E ,定义:
w ^ ( u , v ) = w ( u , v ) + h ( u ) − h ( v ) \hat{w}(u,v)=w(u,v)+h(u)-h(v) w ^ ( u , v ) = w ( u , v ) + h ( u ) − h ( v )
设p = ⟨ v 0 , v 1 , . . . , v k ⟩ p=\langle v_0,v_1,...,v_k \rangle p = ⟨ v 0 , v 1 , . . . , v k ⟩ 为从结点v 0 v_0 v 0 到结点v k v_k v k 的任意一条理解,那么p p p 是在使用权重函数w w w 时从结点v 0 v_0 v 0 到结点v k v_k v k 的一条最短路径,当且仅当p p p 是在使用权重函数w ^ \hat{w} w ^ 时从结点v 0 v_0 v 0 到结点v k v_k v k 的一条最短路径,即w ( p ) = δ ( v 0 , v k ) w(p)=\delta(v_0,v_k) w ( p ) = δ ( v 0 , v k ) 当且仅当w ^ ( p ) = δ ^ ( v 0 , v k ) \hat{w}(p)=\hat{\delta}(v_0,v_k) w ^ ( p ) = δ ^ ( v 0 , v k ) 。而且,图G G G 在使用权重函数w w w 时不包含权重为负值的环路,当且仅当p p p 在使用权重函数w ^ \hat{w} w ^ 也不包括权重为负值的环路。
计算所有结点对之间的最短路径Johnson
算法在执行过程中需要使用 Bellman-Ford
算法和 Dijkstra
算法作为子程序来计算所有结点对之间的最短路径。该算算假定所有的边都保持在邻接链表里,其返回一个∣ V ∣ × ∣ V ∣ |V| \times |V| ∣ V ∣ × ∣ V ∣ 的矩阵D = d i j , d i j = δ ( i , j ) D=d_{ij},d_{ij}=\delta(i,j) D = d i j , d i j = δ ( i , j ) ,或者报告输入图包含一个权重为负值的环路:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 JOHNSON(G, w) compute G', where G'.V = G.V ∪ {s}, G'.E = G.E ∪ {(s,v):v in G.V} and w(s,v) = 0 for all v in G.V if BELLMAN-FOLD(G', w, s) == FALSE print "the input graph contains a negative-weight cycle" else for each vertex v in G'.V set h(v) to the value of δ(s, v) computed by the Bellman-Ford algorithm for each edge(u, v) in G'.E \hat_w(u,v) = w(u,v) + h(u) - h(v) let D = (d_{uv}) be a new n * n matrix for each vertex u in G.V run DIJKSTRA(G, \hat_w, u) to compute \hat_δ(u, v) for all v in G.V for each vertex v in G.V d_{uv} = \hat_δ(u, v) + h(v) - h(u) return D
如果使用斐波那契堆来实现 Dijkstra
算法里的最小优先队列,则 Johnson
算法的运行时间为O ( V 2 l g V + V E ) O(V^2lgV+VE) O ( V 2 l g V + V E ) ,使用更简单的二叉最小堆实现则运行时间为O ( V E l g V ) O(VElgV) O ( V E l g V )
最大流 流网络 流网络和流流网络G = ( V , E ) G=(V,E) G = ( V , E ) 是一个有向图,图中每条边( u , v ) ∈ E (u,v) \in E ( u , v ) ∈ E 有一个非负的容量值 c ( u , v ) ≥ 0 c(u,v) \geq 0 c ( u , v ) ≥ 0 如果边集合E E E 包含一条边( u , v ) (u,v) ( u , v ) ,则图中不存在反方向的边( v , u ) (v,u) ( v , u ) 在图中不允许自循环,对于每个结点v ∈ V v \in V v ∈ V ,流网络都包含一条路径s ⇝ v ⇝ t s \leadsto v \leadsto t s ⇝ v ⇝ t 流网络图是连通的,且由于除源结点外的每个结点都至少有一条进入的边,有∣ E ∣ ≥ ∣ V ∣ − 1 |E| \geq |V|-1 ∣ E ∣ ≥ ∣ V ∣ − 1
流的形式化定义: 设G = ( V , E ) G=(V,E) G = ( V , E ) 为一个流网络,其容量函数为c c c 。设s s s 为网络的源结点,t t t 为汇点。G G G 中的流是一个实值函数f f f :V × V → R V \times V \rightarrow \Reals V × V → R ,满足下面的两条性质:
容量限制 :对于所有的结点u , v ∈ V u,v \in V u , v ∈ V ,要求0 ≤ f ( u , v ) ≤ c ( u , v ) 0 \leq f(u,v) \leq c(u,v) 0 ≤ f ( u , v ) ≤ c ( u , v ) 流量守恒 :对于所有的结点u ∈ V − { s , t } u \in V- \{s,t\} u ∈ V − { s , t } ,要求:∑ v ∈ V f ( u , v ) = ∑ v ∈ V f ( v , u ) \sum_{v\in V}f(u,v) = \sum_{v\in V}f(v,u) v ∈ V ∑ f ( u , v ) = v ∈ V ∑ f ( v , u )
当( u , v ) ∉ E (u,v) \notin E ( u , v ) ∈ / E 时,从结点u u u 到结点v v v 之间没有流,因此f ( u , v ) = 0 f(u,v)=0 f ( u , v ) = 0
一个流f f f 的值∣ f ∣ |f| ∣ f ∣ 定义如下:
∣ f ∣ = ∑ v ∈ V f ( s , v ) − ∑ v ∈ V f ( v , s ) |f|=\sum_{v \in V}f(s,v)-\sum_{v \in V}f(v,s) ∣ f ∣ = v ∈ V ∑ f ( s , v ) − v ∈ V ∑ f ( v , s )
Ford-Fulkerson方法Ford-Fulkerson
方法循环增加流的值。在开始的时候,对于所有的结点u , v ∈ V u,v \in V u , v ∈ V ,f ( u , v ) = 0 f(u,v)=0 f ( u , v ) = 0 ,给出的初始流值为0。在每次迭代中,我们将图G G G 的流值进行增加,方法就是在一个关联的残存网络 G f G_f G f 中寻找一条增广路径 。重复对流进行这一过程,直到残存网络中不再增加增广路径为止:
1 2 3 4 5 FORD-FULKERSON-METHOD(G, s, t) initialize flow f to 0 while there exists an augmenting path p in the residual network Gf augment flow f along p return f
残存网络残存网络由那些仍有空间对流量进行调整的边构成。流网络的一条边可以允许的额外流量等于该边的容量减去该边上的流量:
如果该差值为正,则将该条边置于图G f G_f G f 中,并将其残存容量设置为c f ( u , v ) = c ( u , v ) − f ( u , v ) c_f(u,v)=c(u,v)-f(u,v) c f ( u , v ) = c ( u , v ) − f ( u , v ) ;同时将边( v , u ) (v,u) ( v , u ) 加入到图G f G_f G f 中,并将其残存容量设置为c f ( v , u ) = f ( u , v ) c_f(v,u)=f(u,v) c f ( v , u ) = f ( u , v ) 如果边( u , v ) (u,v) ( u , v ) 的流量等于其容量,则其c f ( u , v ) = 0 c_f(u,v)=0 c f ( u , v ) = 0 ,该条边将不属于图G f G_f G f 形式化地,假定有一个流网络G = ( V , E ) G=(V,E) G = ( V , E ) ,其源结点为s s s ,汇点为t t t 。设f f f 为图G G G 中的一个流,考虑结点对u , v ∈ V u,v \in V u , v ∈ V ,定义残存容量 c f ( u , v ) c_f(u,v) c f ( u , v ) :
c f ( u , v ) = { c ( u , v ) − f ( u , v ) ( u , v ) ∈ E f ( v , u ) ( v , u ) ∈ E 0 o t h e r c_f(u,v) = \begin{cases} c(u,v)-f(u,v) \qquad & (u,v) \in E\\ f(v,u) \qquad & (v,u) \in E\\ 0 \qquad & other \end{cases} c f ( u , v ) = ⎩ ⎪ ⎪ ⎨ ⎪ ⎪ ⎧ c ( u , v ) − f ( u , v ) f ( v , u ) 0 ( u , v ) ∈ E ( v , u ) ∈ E o t h e r
给定一个流网络G = ( V , E ) G=(V,E) G = ( V , E ) 和一个流f f f ,则由f f f 所诱导的图G G G 的残存网络 为G f = ( V , E f ) G_f=(V,E_f) G f = ( V , E f ) ,其中:
E f = { ( u , v ) ∈ V × V : c f ( u , v ) > 0 } E_f = \{(u,v) \in V \times V:c_f(u,v)>0\} E f = { ( u , v ) ∈ V × V : c f ( u , v ) > 0 }
如果f f f 是G G G 的一个流,f ′ f' f ′ 是对应的残存网络G f G_f G f 中的一个流,定义f ↑ f ′ f \uparrow f' f ↑ f ′ 为流f ′ f' f ′ 对流f f f 的递增,它是一个从V × V → R V \times V \rightarrow \Reals V × V → R 的函数,其定义如下:
( f ↑ f ′ ) ( u , v ) = { f ( u , v ) + f ′ ( u , v ) − f ′ ( v , u ) ( u , v ) ∈ E 0 o t h e r (f \uparrow f')(u,v) = \begin{cases} f(u,v)+f'(u,v)-f'(v,u) \qquad & (u,v) \in E\\ 0 \qquad & other \end{cases} ( f ↑ f ′ ) ( u , v ) = { f ( u , v ) + f ′ ( u , v ) − f ′ ( v , u ) 0 ( u , v ) ∈ E o t h e r
增广路径给定流网络G = ( V , E ) G=(V,E) G = ( V , E ) 和流f f f ,增广路径p p p 是残存网络G f G_f G f 中一条从源结点s s s 到汇点t t t 的简单路径 我们称在一条增广路径p p p 上能够为每条边增加的流量的最大值为路径p p p 的残存容量 ,该容量由下面的表达式给出:
c f ( p ) = m i n { c f ( u , v ) : ( u , v ) 属于路径 p } c_f(p)=min\{c_f(u,v):(u,v)\text{属于路径}p\} c f ( p ) = m i n { c f ( u , v ) : ( u , v ) 属于路径 p }
流网络的切割流网络G = ( V , E ) G=(V,E) G = ( V , E ) 中的一个切割( S , T ) (S,T) ( S , T ) 将结点集合V V V 划分为S S S 和T = V − S T=V-S T = V − S 两个集合,使得s ∈ S s \in S s ∈ S ,t ∈ T t \in T t ∈ T 。若f f f 是一个流,则定义横跨切割( S , T ) (S,T) ( S , T ) 的净流量 f ( S , T ) f(S,T) f ( S , T ) 如下:
f ( S , T ) = ∑ u ∈ S ∑ v ∈ T f ( u , v ) − ∑ u ∈ S ∑ v ∈ T f ( v , u ) f(S,T)=\sum_{u\in S}\sum_{v \in T}f(u,v)-\sum_{u\in S}\sum_{v \in T}f(v,u) f ( S , T ) = u ∈ S ∑ v ∈ T ∑ f ( u , v ) − u ∈ S ∑ v ∈ T ∑ f ( v , u )
切割( S , T ) (S,T) ( S , T ) 的容量 :
c ( S , T ) = ∑ u ∈ S ∑ v ∈ T c ( u , v ) c(S,T)=\sum_{u\in S}\sum_{v \in T}c(u,v) c ( S , T ) = u ∈ S ∑ v ∈ T ∑ c ( u , v )
一个网络的最小切割是整个网络中容量最小的切割
设f f f 为流网络G G G 的一个流,该流网络的源结点为s s s ,汇点为t t t ,设( S , T ) (S,T) ( S , T ) 为流网络G G G 的任意切割,则横跨切割( S , T ) (S,T) ( S , T ) 的净流量为f ( S , T ) = ∣ f ∣ f(S,T)=|f| f ( S , T ) = ∣ f ∣ 流网络G G G 中任意流f f f 的值不能超过G G G 的任意切割的容量 设f f f 为流网络G = ( V , E ) G=(V,E) G = ( V , E ) 中的一个流,该流网络的源结点为s s s ,汇点为t t t ,则下面的条件是等价的:
f f f 是G G G 的一个最大流残存网络G f G_f G f 不包括任何增广路径 ∣ f ∣ = c ( S , T ) |f|=c(S,T) ∣ f ∣ = c ( S , T ) ,其中( S , T ) (S,T) ( S , T ) 是流网络G G G 的某个切割 基本的Ford-Fulkerson算法在 Ford-Fulkerson
方法的每次迭代中,寻找某条增广路径p p p ,然后使用p p p 来对流f f f 进行修改(增加)。通过为每条边( u , v ) ∈ E (u,v) \in E ( u , v ) ∈ E 更新流属性( u , v ) . f (u,v).f ( u , v ) . f 来计算流网络G = ( V , E ) G=(V,E) G = ( V , E ) 中的最大流:
1 2 3 4 5 6 7 8 9 10 FORD-FULKERSON(G, s, t) for each edge(u,v) in G.E (u,v).f = 0 while there exists a path p from s to t in the residual network Gf cf(p) = min{cf(u,v):(u,v)is in p} for each edge(u,v) in p if (u,v) in E (u,v).f = (u,v).f + cf(p) else (u,v).f = (u,v).f - cf(p)
如果f ∗ f^* f ∗ 表示转换后网络中的一个最大流,则 Ford-Fulkerson
算法的运行时间为O ( E ∣ f ∗ ∣ ) O(E|f^*|) O ( E ∣ f ∗ ∣ )
Edmonds-Karp算法通过在算法第3行寻找增广路径的操作中使用广度优先搜索来改善 Ford-Fulkerson
算法的效率: 在残存网络中选择的增广路径是一条从源结点s s s 到汇点t t t 的最短路径,其中每条边的权重为单位距离Edmonds-Karp
算法的运行时间为O ( V E 2 ) O(VE^2) O ( V E 2 )