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