基本思想
将规模为n的问题规约为规模减小的一个或多个子问题,分别求解每个子问题,然后把子问题的解综合,得到原问题的解。
Divide-and-conquer(p)
if |p|≤c then S(p) //S(p)代表直接求解
divide p into p1,p2,…pk
for i=1 to k do
yi=divide-and-conque(pi) //递归求解每个子问题
end{for}
end{if}
Return Merge(y1,y2,…,yk) //把子问题的解进行综合
二分查找/折半搜索
proc BiFind(a,n)
//在数组 a[1..n]中搜索 x,数组中的元素满足 a[1] ≤ a[2] ≤ … ≤ a[n]。如果找到 x,则返回所在位置(数组元素的下标),否则返回 –1
global a[1..n], n;
integer left,right,middle;
left:=1; right:=n;
while left ≤ right do
middle:=(left+right)/2;
if x=a[middle] then return(middle);
end{if}
if x>a[middle] then left:=middle+1;
else right:=middle-1;
end{if}
end{while}
return(–1); //未找到 x
end{BiFind}
复杂度分析
{ T ( n ) = T ( ⌊ n 2 ⌋ ) + 1 T ( 1 ) = 1 \left\{\begin{array}{c} T(n)=T\left(\left\lfloor\frac{n}{2}\right\rfloor\right)+1 \\ T(1)=1 \end{array}\right. {T(n)=T(⌊2n⌋)+1T(1)=1
设n=2k,T(n)= T(n/2)+1 =T(n/4)+2=T(n/8)+3 =… =T(1)+k=1+logn=Θ(logn)
若用一一比较的算法,则T(n)=O(n)
查找最大最小
MaxMin(i,j,fmax,fmin)
//A[1:n]是n元数组,参数i,j :1≤i≤j≤n,使用该过程将数组A[i..j]中的最大最小元分别赋给fmax和fmin。
global n, A[1..n];
integer i, j;
if i=j then
fmax:=A[i]; fmin:=A[i]; //子数组A[i..j]中只有一个元素
elseif i=j-1 then //子数组A[i..j]中只有两个元素
if A[i]<A[j] then
fmin:=A[i]; fmax:=A[j];
else fmin:=A[j]; fmax:=A[i];
end{if}
else //子数组A[i..j]中的元素多于两个
mid:=⌊(i+j)/2⌋;
MaxMin(i, mid, lmax, lmin);//递归
MaxMin(mid+1, j, rmax, rmin);//递归
fmax:=max(lmax, rmax);
fmin:=min(lmin, rmin);
end{if}
end{MaxMin}
复杂度分析
T ( n ) = { 0 n = 1 1 n = 2 T ( ⌈ n / 2 ⌉ ) + T ( ⌊ n / 2 ⌋ ) + 2 n > 2 \mathrm{T}(n)=\left\{\begin{array}{lc} 0 & n=1 \\ 1 & n=2 \\ T(\lceil n / 2 \rceil)+T(\lfloor n / 2\rfloor)+2 & n>2 \end{array}\right. T(n)=⎩⎨⎧01T(⌈n/2⌉)+T(⌊n/2⌋)+2n=1n=2n>2
设n=2k,T(n)=2T(n/2)+2=4T(n/4)+4+2=2k-1+2k-2=3n/2-2
若用一一比较的算法,则T(n)=2(n-1)
排序算法
插入排序
proc InSort(a, n)
//对 a[1..n]进行插入排序
for i from 2 to n do//将a[i]插入a[1..i-1]
x:=a[i]; integer j;
for j from i-1 by -1 to 1 do
if x<a[j] then a[j+1]:=a[j]; //更小值向后移位
end{if}
end{for}
a[j+1]:=x;//插入更大值之后
end{for}
end{InSort}
T(n)=1+2+…+(n-1) =n(n-1)/2=Θ(n2)
归并排序
proc MergeSort(low, high)
// A[low .. high]是一个全程数组,含有 high-low+1个待排序的元素。
integer low, high;
if low < high then
mid:= ⌊(low+high)/2⌋ //求当前数组的分割点
MergeSort(low, mid) //将第一子数组排序
MergeSort(mid+1, high) //将第二子数组排序
Merge(low, mid, high) //归并两个已经排序的子数组
end{if}
end{MergeSort}
proc Merge(low, mid, high) //已知全程数组 A[low .. high], 其由两部分已经排好序的子数组构成:A[low .. mid]和 A[mid+1 .. high]。本程序的任务是将这两部分子数组合并成一个整体排好序的数组,再存于数组 A[low .. high].
integer h, i, j, k, low, mid, high;
global A[low .. high];
local B[low .. high]; //借用临时数组 B
h:=low, i:=low, j:=mid+1;
// h, j 是拣取游标, i 是向 B 存放元素的游标
while h≤mid and j≤high do //当两个集合都没有取尽时
if A[h]≤A[j] then B[i]:=A[h], h:=h+1;
else B[i]:=A[j], j:=j+1;
end{if}
i:=i+1;
end{while}
if h>mid then //当第一子组元素被取尽,而第二组元素未被取尽时
for k from j to high do
B[i]:=A[k]; i:=i+1;
end{for}
else //当第二子组元素被取尽,而第一组元素未被取尽时
for k from h to mid do
B[i]:=A[k]; i:=i+1;
end{for}
end{if}
//将临时数组 B 中元素再赋给数组 A
for k from low to high do
A[k]:=B[k];
end{for}
end{Merge}
时间复杂度
T ( n ) = { a n = 1 2 T ( n / 2 ) + c n n > 1 T(n)=\left\{\begin{array}{ll} a & n=1 \\ 2 T(n / 2)+c n & n>1 \end{array}\right. T(n)={a2T(n/2)+cnn=1n>1
令n=2k ,
T ( n ) = 2 ( 2 T ( n / 4 ) + c n / 2 ) + c n = 4 T ( n / 4 ) + 2 c n ⋯ ⋯ = 2 k T ( 1 ) + k c n = a n + c n log n \begin{aligned} T(n) &=2(2 T(n / 4)+c n / 2)+c n \\ &=4 T(n / 4)+2 c n \\ & \cdots \cdots \\ &=2^{k} T(1)+k c n \\ &=a n+c n \log n \end{aligned} T(n)=2(2T(n/4)+cn/2)+cn=4T(n/4)+2cn⋯⋯=2kT(1)+kcn=an+cnlogn
当2k < n ≤ 2^k +1^ ,T (n) = O(nlogn)
快速排序
算法思想:将A[1…n]划分成B[1…p]和B[p+1…n],使得B[1…p]中的元素均不大于B[p+1…n]中的元素,然后分别对它们排序,最后接起来即可。
首元素划分:选定A中的首元素,将A中的所有元素与之比较,小于等于的元素构成B[1…p],大于的元素构成B[p+1…n]。
快速排序算法:
proc QuickSort(p,q) //将数组A[1..n]中的元素A[p], A[p+1], ... , A[q] 按不降次序排列,并假定A[n+1]是一个确定数,且大于A[1..n]中所有的数。划分后j成为划分元素的位置。
integer p,q;
global n, A[1..n];
if p<q then
j:=q+1;//保持对称性
Partition(p,j); //首元素划分
QuickSort(p,j-1);
QuickSort(j+1,q);
end{if}
end{QuickSort}
最坏复杂度T(n)=T(n-1)+n-1=T(n-2)+n-2+n-1=1+2+…+n-1=(n-1)(n-2)/2=O(n2)
首元素划分算法:
proc Partition(m,p)
// 用首元素划分数组A[m,p-1],划分后首元素放中间。
integer m, p, i;
global A[m ..p-1];
v:=A[m]; i:=m;
loop
loop i:=i+1; until A[i]>v; end{loop} //自左向右查
loop p:=p-1; until A[p]≤v; end{loop} //自右向左查
if i<p then
Swap(A[i],A[p]); //交换 A[i]和 A[p]的位置
else go to *;
end{if}
end{loop}
*: A[m]:=A[p]; A[p]:= v; // 划分元素在位置 p
end{Partition}
平均复杂度T(n) = 2(n+1)logn=O(nlogn)
选择问题
问题描述:确定数组A[1…n]的第k小元素。
若使用某种排序算法将A按不降次序排序,从排好序的数组中检出第k个元素。最坏时间复杂度O(nlogn)
划分法
proc PartSelect(A, n, k) //在数组 A[1..n]中找第 k 小元素 t,并将其存放于位置 k,即 A[k]=t。而剩下的元素按着以 t 为划分元素的划分规则存放。再令 A[n+1]=+∞.
integer n, k, m, r, j;
m:=1; r:=n+1; A[n+1]:= +∞;
loop
j:=r;
Partition(m,j);
case
k=j : return // 返回 j,当前数组的元素 A[j]是第 j 小元素
k<j : r:=j; // j 是新的下标上界
else : m:=j+1; k:=k-j; //j+1 是新的下标下界
end{case}
end{loop}
end{PartSelect}
矩阵乘法
矩阵加法和乘法
假定 A, B 都是 n×n 矩阵,它们的 i 行 j 列元素分别记为 A(i,j)和 B(i,j)。
如果用 S 和 C 分别记 A+B 和 A*B, 则有
S ( i , j ) = A ( i , j ) + B ( i , j ) 1 ≤ i , j ≤ n C ( i , j ) = ∑ k = 1 n A ( i , k ) ∗ B ( k , j ) 1 ≤ i , j ≤ n \begin{array}{lc} S(i, j)=A(i, j)+B(i, j) & 1 \leq i, j \leq n \\ C(i, j)=\sum_{k=1}^{n} A(i, k) * B(k, j) & 1 \leq i, j \leq n \end{array} S(i,j)=A(i,j)+B(i,j)C(i,j)=∑k=1nA(i,k)∗B(k,j)1≤i,j≤n1≤i,j≤n
可见,矩阵加法运算的时间复杂度是 Θ(n2) ,而矩阵乘法的时间复杂度是 Θ(n3)
分块矩阵乘法
设n=2k,将A、B各分为4个n/2阶矩阵
T ( n ) = { b n ≤ 2 8 T ( n / 2 ) + d n 2 n > 2 T(n)=\left\{\begin{array}{ll} b & n \leq 2 \\ 8 T(n / 2)+d n^{2} & n>2 \end{array}\right. T(n)={b8T(n/2)+dn2n≤2n>2
T ( n ) = b n 3 / 8 + 4 d ( n 2 − 16 ) / 3 = Θ ( n 3 ) T(n)=b n^{3} / 8+4 d\left(n^{2}-16\right) / 3=\Theta(n^3) T(n)=bn3/8+4d(n2−16)/3=Θ(n3)
Strassen矩阵乘法
P = ( A 11 + A 22 ) ( B 11 + B 22 ) Q = ( A 21 + A 22 ) B 11 R = A 11 ( B 12 − B 22 ) S = A 22 ( B 21 − B 11 ) T = ( A 11 + A 12 ) B 22 U = ( A 21 − A 11 ) ( B 11 + B 12 ) V = ( A 12 − A 22 ) ( B 21 + B 22 ) C 11 = P + S − T + V C 12 = R + T C 21 = Q + S C 22 = P + R − Q + U \begin{array}{l} P=\left(A_{11}+A_{22}\right)\left(B_{11}+B_{22}\right) \\ Q=\left(A_{21}+A_{22}\right) B_{11} \\ R=A_{11}\left(B_{12}-B_{22}\right) \\ S=A_{22}\left(B_{21}-B_{11}\right) \\ T=\left(A_{11}+A_{12}\right) B_{22} \\ U=\left(A_{21}-A_{11}\right)\left(B_{11}+B_{12}\right) \\ V=\left(A_{12}-A_{22}\right)\left(B_{21}+B_{22}\right) \\ C_{11}=P+S-T+V \\ C_{12}=R+T \\ C_{21}=Q+S \\ C_{22}=P+R-Q+U \end{array} P=(A11+A22)(B11+B22)Q=(A21+A22)B11R=A11(B12−B22)S=A22(B21−B11)T=(A11+A12)B22U=(A21−A11)(B11+B12)V=(A12−A22)(B21+B22)C11=P+S−T+VC12=R+TC21=Q+SC22=P+R−Q+U
T ( n ) = { b n ≤ 2 7 T ( n / 2 ) + a n 2 n > 2 = Θ ( n 2.81 ) T(n)=\left\{\begin{array}{ll} b & n \leq 2 \\ 7 T(n / 2)+a n^{2} & n>2 \end{array}\right.=\Theta(n^{2.81}) T(n)={b7T(n/2)+an2n≤2n>2=Θ(n2.81)
快速Fourier变换
N 个数据的离散 Fourier 变换
A j = ∑ 0 ≤ k ≤ N − 1 a k e 2 π i j k / N , 0 ≤ j A_{j}=\sum_{0 \leq k \leq N-1} a_{k} e^{2 \pi i j k / N}, \quad 0 \leq j Aj=0≤k≤N−1∑ake2πijk/N,0≤j
令 ω = e 2 π i / N \omega=e^{2 \pi i / N} ω=e2πi/N,相当于计算多项式 a ( x ) = ∑ 0 ≤ k ≤ N − 1 a k x k a(x)=\sum_{0 \leq k \leq N-1} a_{k} x^{k} a(x)=∑0≤k≤N−1akxk在 ω j \omega^j ωj处的值。T(N)=N*2(N-1)
采用分治算法,将求 N = 2n 个数据的 Fourier 变换归结为求两次具有 n 个数据的 Fourier 变换。
b ( x ) = a 1 + a 3 x + a 5 x 2 + … + a N − 1 x ( N − 2 ) / 2 c ( x ) = a 0 + a 2 x + a 4 x 2 + … + a N − 2 x ( N − 2 ) / 2 \begin{array}{l} b(x)=a_{1}+a_{3} x+a_{5} x^{2}+\ldots+a_{N-1} x^{(N-2) / 2} \\ c(x)=a_{0}+a_{2} x+a_{4} x^{2}+\ldots+a_{N-2} x^{(N-2) / 2} \end{array} b(x)=a1+a3x+a5x2+…+aN−1x(N−2)/2c(x)=a0+a2x+a4x2+…+aN−2x(N−2)/2
a ( x ) = b ( x 2 ) x + c ( x 2 ) a(x)=b\left(x^{2}\right) x+c\left(x^{2}\right) a(x)=b(x2)x+c(x2)
a ( ω j ) = b ( ω 2 j ) ω j + c ( ω 2 j ) a ( ω j + n ) = − b ( ω 2 j ) ω j + c ( ω 2 j ) , j = 0 , 1 , ⋯ , n − 1 \begin{array}{l} a\left(\omega^{j}\right)=b\left(\omega^{2 j}\right) \omega^{j}+c\left(\omega^{2 j}\right) \\ a\left(\omega^{j+n}\right)=-b\left(\omega^{2 j}\right) \omega^{j}+c\left(\omega^{2 j}\right) \end{array}, \quad j=0,1, \cdots, n-1 a(ωj)=b(ω2j)ωj+c(ω2j)a(ωj+n)=−b(ω2j)ωj+c(ω2j),j=0,1,⋯,n−1
时间复杂度
T ( N ) = { a , if N = 1 2 T ( N / 2 ) + c N , if N > 1 = O ( N l o g N ) T(N)=\left\{\begin{array}{l} a, \text { if } N=1 \\ 2 T(N / 2)+c N, \text { if } N>1 \end{array}\right. =O(NlogN) T(N)={a, if N=12T(N/2)+cN, if N>1=O(NlogN)
Proc FFT(N,a,w,A)
//N=2n,w是n次单位根,a是已知的N元数组,代表多项式a(x)的系数,A是计算出来的N元数组,A[j]=a(w^j), j=0,1,…,N-1.
real b[ ], c[ ]; int j;
complex B[ ], C[ ], wp[ ];
if N=1 then A[0]:=a[0];
else
n:=N/2;
for j from 0 to n-1 do
b[j]:=a[2*j+1]; c[j]:=a[2*j];
end{for}
end{if}
FFT(n,b,w*w,B);
FFT(n,c,w*w,C);
wp[0]:=1;
for j from 0 to n-1 do
wp[j+1]:=w*wp[j];
A[j]:= C[j]+B[j]*wp[j];
A[j+n]:= C[j]-B[j]*wp[j];
end{for}
end{FFT}
分治算法中常见的递归方程
T ( n ) = ∑ i = 1 k a i T ( n − i ) + f ( n ) T(n)=\sum_{i=1}^{k} a_{i} T(n-i)+f(n) T(n)=i=1∑kaiT(n−i)+f(n)
可用迭代求解、递归树分析求解
T ( n ) = a T ( n / b ) + f ( n ) T(n)=aT(n/b)+f(n) T(n)=aT(n/b)+f(n)
可用主定理分析:
设a≥1,b >1为常数,f(n)为函数,T(n)为非负整数,且
T ( n ) = a T ( n / b ) + f ( n ) , c = log b a T(n)=aT(n/b)+f(n),c=\log b^a T(n)=aT(n/b)+f(n),c=logba
则有
T ( n ) = { Θ ( n c ) Θ ( n c log n ) Θ ( f ( n ) ) f ( n ) = O ( n c − ε ) f ( n ) = Θ ( n c ) f ( n ) = Ω ( n c + ε ) T\left( n \right) =\left\{ \begin{matrix} \begin{array}{l} \Theta \left( n^c \right)\\ \Theta \left( n^c\log n \right)\\ \Theta \left( f\left( n \right) \right)\\ \end{array}& \begin{array}{l} f\left( n \right) =O\left( n^{c-\varepsilon} \right)\\ f\left( n \right) =\Theta \left( n^c \right)\\ f\left( n \right) =\Omega \left( n^{c+\varepsilon} \right)\\ \end{array}\\ \end{matrix} \right. T(n)=⎩⎨⎧Θ(nc)Θ(nclogn)Θ(f(n))f(n)=O(nc−ε)f(n)=Θ(nc)f(n)=Ω(nc+ε)