算法流程:
A[1,n]
排序问题分解为A[1,n/2]
和A[n/2+1,n]
排序问题符合分而治之的思想:
用树的形式表示抽象递归
T ( n ) = { 2 T ( n 2 ) + O ( n ) , i f n > 1 O ( 1 ) , i f n = 1 T(n)=\begin{cases} 2T(\frac{n}{2})+O(n), &if&n>1\\ O(1),&if&n=1 \end{cases} T(n)={2T(2n)+O(n),O(1),ififn>1n=1
树的深度通常从0开始计,故层数等于n+1,后续统一用深度
可以得到,这个算法的时间复杂度是: T ( n ) = O ( n log n ) T(n)=O(n\log n) T(n)=O(nlogn)
对形如 T ( n ) = a T ( n b ) + f ( n ) T(n)=aT(\frac{n}{b})+f(n) T(n)=aT(bn)+f(n)的递归式:
a
个分支b
速度下降可以得到如下公式:
KaTeX parse error: {align} can be used only in display mode.
当 f ( n ) f(n) f(n)形式为 n k n^k nk时,可简化主定理公式:
KaTeX parse error: {align} can be used only in display mode.
例: T ( n ) = 5 T ( n 2 ) + n 3 T(n)=5T(\frac{n}{2})+n^3 T(n)=5T(2n)+n3
基于枚举策略
蛮力枚举:枚举所有可能的情况
枚举 n + C n 2 n+C_n^2 n+Cn2种可能的区间 [ l , r ] , ( l < r ) [l,r],(l
优化枚举:蛮力枚举存在重复计算
S ( l , r ) = Σ i = 1 r X [ i ] = S ( l , r − 1 ) + X [ r ] S(l,r)=\Sigma_{i=1}^rX[i]=S(l,r-1)+X[r] S(l,r)=Σi=1rX[i]=S(l,r−1)+X[r],时间复杂度为 O ( n 2 ) O(n^2) O(n2)
分而治之
问题在于,如何高效地求解 S 3 S_3 S3
求解 S 3 S_3 S3的时间复杂度分析:
伪代码:
输入:数组X,数组下标low,high
输出:最大子数组之和Smax
if low=high then
| return X[low]
end
else
| mid <- (low+high)/2
| S1 <- MaxSubArray(X,low,mid)
| S2 <- MaxSubArray(X,mid+1,high)
| S3 <- CrossingSubArray(X,low,mid,high)
| Smax <- Max(S1,S2,S3)
| return Smax
end
$T(n)=
\left{
\begin{align}
&1,\quad&n=1\
&2T(\frac n2)+O(n),\quad&n>1
\end{align}
\right.
\$
按照上面提到的递归树求解的方法,可以得到: T ( n ) = n log n T(n)=n\log n T(n)=nlogn
逆序对:当 i < j i
蛮力枚举
对于数组的每个元素 A [ i ] A[i] A[i],枚举 j ( j > i ) j(j>i) j(j>i),并统计逆序对数目。
伪代码:
输入:一个大小为n的数组A[1..n]
输出:数组A[1..n]中逆序对数目Sum
Sum <- 0
for i <- 1 to n do
| for j <- i+1 to n do
| | if A[i]>A[j] then
| | | Sum <- Sum+1
| | end
| end
end
return Sum
这一算法时间复杂度为 O ( n 2 ) O(n^2) O(n2)
分而治之
策略一:直接求解
对每个 A [ j ] ∈ A [ m + 1 , n ] A[j]\in A[m+1,n] A[j]∈A[m+1,n],枚举 A [ i ] ∈ A [ 1.. m ] A[i]\in A[1..m] A[i]∈A[1..m]并统计逆序对数目。
求解 S 3 S_3 S3的算法运行时间: O ( n 2 ) O(n^2) O(n2)
分而治之框架的运行时间: T ( n ) = 2 T ( n 2 ) + O ( n 2 ) T(n)=2T(\frac n2)+O(n^2) T(n)=2T(2n)+O(n2)
直接求解的分而治之较蛮力枚举并未提高算法运行时间。
策略二:排序求解
求解 S 3 S_3 S3的算法运行时间: O ( n log 2 n ) O(n\log^2 n) O(nlog2n)
分治框架的算法运行时间: T ( n ) = 2 T ( n 2 ) + O ( n log n ) T(n)=2T(\frac n2)+O(n\log n) T(n)=2T(2n)+O(nlogn)
合并问题解的同时对数组进行排序
策略三:归并求解
伪代码:
输入:数组A[1..n],数组下标left,right
输出:数组A[left..right]的逆序对数,递增数组A[left..right]
if left >= right then
| return A[left..right]
end
mid <-(left+right)/2
S1 <- CountInver(A,left,mid)
S2 <- CountInver(A,mid+1,right)
S3 <- MergeCount(A,left,mid,right)
S <- S1+S2+S3
return S,A[left..right]
时间复杂度: T ( n ) = O ( n log n ) T(n)=O(n\log n) T(n)=O(nlogn)
归并排序:简化分解,侧重合并
快速排序:侧重分解,简化合并
数组划分
伪代码
Partition(A,p,r)
输入:数组A,起始位置p,终止位置r
输出:划分位置q
x <- A[r]//选取主元
i <- p-1
for j <- p to r-1 do
| if A[j] <= x then
| | exchange A[i+1] with A[j]
| | i <- i+1
| end
end
exchange A[i+1] with A[r]
q <- i+1
return q
快速排序的伪代码
输入:数组A,起始位置p,终止位置r
输出:有序数组A
if p
最好情况: O ( n log n ) O(n\log n) O(nlogn)
最坏情况: O ( n 2 ) O(n^2) O(n2)
快速排序看似不优于归并排序。
输入:
输出:
固定位置划分求解
选取固定位置主元,小于主元的元素个数 q − p q-p q−p:
子问题始终唯一,无需合并问题解。
伪代码
输入:数组A,起始位置p,终止位置r,元素次序k
输出:第k小元素x
q <- Partition(A,p,r)
if k=(q-p+1) then
| x <- A[q]
end
if k<(q-p+1) then
| x <-Selection(A,p,q-1,k)
end
if k>(q-p+1) then
| x <- Selection(A,q+1,r,k-(q-p+1))
end
return x
复杂度分析:
最好情况: T ( n ) = Ω ( n ) T(n)=\Omega(n) T(n)=Ω(n)
期望时间复杂度: T ( n ) = Θ ( n ) T(n)=\Theta(n) T(n)=Θ(n)
最坏情况: T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)