第三章:排序与分治法
3 排序与分治法
3.1 排序介绍
3.1.1 内部排序
- 插入类-插入排序
- 选择类-选择排序
- 交换类-冒泡排序
- 归并类-归并排序
3.1.2 外部排序
3.2 分治法
3.2.1 分治设计
- 问题划分为子问题
- 求解子问题
- 合并子问题的解
3.2.2 分治分析
- 即求解递归方程的T(n):
- Divide:
- 划分为a个 n b \frac{n}{b} bn大小的子问题
- D(n)
- Conquer
- 递归调用: a T ( n b ) aT(\frac{n}{b}) aT(bn)
- Combine
- C(n)
- Divide:
3.2.3 例题(1):求解max和min问题
-
问题定义:
- 输入:数组A[1,…,n]
- 输出:A中的max和min
-
基本思想:
- 划分为两个子问题:求解max + 求解min
- 将**A[i]与A[n-i+1]**比较,小元素放在前,大元素放在后
- 使得最小元素出现在A[1,2,…,[ n 2 \frac{n}{2} 2n]]中
【算法一】
-
过程图示:
-
伪码实现:比较次数= O [ 3 × n 2 − 2 ] O[3\times \frac{n}{2}-2] O[3×2n−2]
Max-min(A) Input: 数组A[1,…,n] Output: 数组A[1,…,n]中的max和min For i<-1 To n/2 Do IF A[i] > A[n-i+1] THEN swap(A[i],A[n-i+1]); //当A[i]更大,交换 max <- A[n]; min <- A[1]; //从后往前搜索最大值,从前往后搜索最小值 For i<-2 To [n/2] Do IF A[i] < min THEN min <- A[i]; IF A[n-i+1] > max THEN max <- A[n-i+1]; print max, min;
【算法二】
-
过程图示:
-
伪码实现:
Max-min(low,high) IF high-low = 1 //将子问题拆分为两个数的比较,返回小值在前,大值在后 IF A[low] < A[high] THEN return (A[low], A[high]) ELSE return (A[high], A[low]) ELSE mid <- (low+high)/2 //当还没有拆分完全时,使用二分法拆分 (x1,y1) <- Max-min(low,mid) (x2,y2) <- Max-min(mid+1,high) x <- min{x1,x2} //取最小 y <- max{y1,y2} //取最大 return (x,y)
-
复杂度分析:
-
3.2.4 例题(2):大数乘法
-
问题定义:
- 输入:n位二进制整数X和Y
- 输出:X和Y的乘积
-
简单分治算法:
-
将A=X[0…N/2-1], B=X[N/2…N], C=Y[0…N/2-1], D=Y[N/2…N]
-
X Y = ( A 2 N / 2 + B ) ( C N / 2 + D ) = A C 2 N + ( A D + B C ) 2 N / 2 + B D XY=(A2^{N/2}+B)(C^{N/2}+D)=AC2^{N}+(AD+BC)2^{N/2}+BD XY=(A2N/2+B)(CN/2+D)=AC2N+(AD+BC)2N/2+BD
-
T ( n ) = 4 T ( n 2 ) + θ ( n ) = O ( n 2 ) T(n)=4T(\frac{n}{2})+θ(n)=O(n^2) T(n)=4T(2n)+θ(n)=O(n2)
-
-
改进分治算法
-
A D + B C = ( A − B ) ( D − C ) + A C + B D AD+BC=(A-B)(D-C)+AC+BD AD+BC=(A−B)(D−C)+AC+BD
-
T ( n ) = 3 T ( n 2 ) + θ ( n ) = O ( n l o g 3 ) T(n)=3T(\frac{n}{2})+θ(n)=O(n^{log3}) T(n)=3T(2n)+θ(n)=O(nlog3)
3.2.3 例题(3):棋盘覆盖问题
3.3 基于分治思想的排序算法
3.3.1 merge sort
-
选择一个位置将数组划分成两个部分
-
MergeSort(A,i,j) Input: A[i,…,j] Output:排序后的A[i,…,j] k <-(i+j)/2; MergeSort(A,i,k); //对A[i...k]进行划分 MergeSort(A,k+1,j);//对A[k+1...j]进行划分 l<-i; //指针l从i开始向后遍历到k h<-k+1; //指针h从k+1开始向后遍历到j t=i //t为数据插入目标数组的指针 //A为原数组,B为目标数组 While l < k & h < j Do //随指针扫描进行逐一比较,按序将数据加入目标数组中,对应指针移动 IF A[l] < A[h] THEN B[t] <- A[l]; l <- l+1; t <- t+1; ELSE B[t] <- A[h]; h <- h+1; t <- t+1; IF l<k THEH //第一个子问题有剩余元素,直接将剩余元素并入B For v <- l To k Do B[t] <- A[v]; t <- t+1; IF h<j THEN //第二个子问题有剩余元素 For v <- h To j Do B[t] <- A[v]; t <- t+1; For v <- i To j Do //将归并后的数据复制到A中 A[v] <- B[v];
-
中位数问题:
本算法讨论如何在O(n)时间内从n个不同的数中选取第i大的元素
(中位数其实就是第n/2大的元素)
(于是把输出转换为:x∈X使得X中恰好有i-1个元素<x)
伪码
Select(A,i)
Input: 数组A[1:n], 1<i<n
Output: A[1:n]中的第i-大的数
for j<-1 to n/5
InsertSort(A[(j-1)*5+1 : (j-1)*5+5]); //分组插入排序
swap(A[j], A[(j-1)*5+3]);//将中位数放置于数组前n/5个
x <- Select(A[1: n/5], n/10 );//递归找出中位数的中位数MoM
k <- partition(A[1:n], x);//用MoM进行划分,k为x的下标
if k=i
then return x; //如果k=i,则直接返回MoM
else if k>i
then retrun Select(A[1:k-1],i); //如果k>i,返回第一个部分(值均小于M)第i大的值
else retrun Select(A[k+1:n],i-k); //如果k<i,返回第三个部分(值均大于M)第k-i大的值
3.3.2 quick sort
- 选择一个划分标准x,根据元素与x的大小关系来划分
PartitionSort(A,i,j)
Input: A[i,…,j], x
Output: 排序后的A[i,…,j]
x<-A[i]; //以确定的策略选择x
k=partition(A,i,j,x); //用x完成划分
partitionSort(A,i,k); //递归求解子问题
partitionSort(A,k+1,j);
//通过x对A[i...j]进行划分
Partition(A,i,j,x)
low<-i ; high<-j;
While( low < high ) Do
swap(A[low], A[high]); //对应下述过程,当已经在x左侧找到比x大的数,则与x右侧比x小的数交换
While( A[low] < x ) Do //先交换一次并不影响,大概是因为如果符合的话,还会换回来的
low<-low+1;
While( A[high] >=x ) Do
high<-high-1;
return(high)
-
随机QuickSort算法
temp<-rand(i,j); //产生i,j之间的随机数 x<-A[temp]; //以确定的策略选择x
那么如何计算 p i j p_{ij} pij呢?
我们可以用树来模拟算法中的比较过程,即划分为根节点,小数在左,大数在右。
分析比较过程我们可以知道几个特点:
- 每个根节点都会与其子树中的所有节点比较
- 左子树和右子树中的结点不会比较
- 两个节点最多比较一次
所以,
p
i
j
=
2
j
−
i
+
1
p_{ij}=\frac{2}{j-i+1}
pij=j−i+12,即只要有一个被选为根节点,则发生比较。
3.5 排序问题的下界
-
问题的下界是解决该问题的算法所需要的最小时间复杂性。
-
排序问题的下界是不唯一的
–例如. Ω(n), Ω(n log n) 都是排序的下界
-
如果一个问题的下界是Ω(n log n) 且算法的时间复杂性是 O(n log n), 那么这个算法是最优的
否则可以改进问题的下界或者算法的时间复杂性。
-
又因为排序算法均可以用决策树进行表示,故,排序算法的下界可以用决策树的高度来表示。
-
n!种不同排列,决策树结点。
-
决策树为平衡树是高度最小, log ( n ! ) = Ω ( n log n ) \log(n!)=Ω(n\log n) log(n!)=Ω(nlogn)为排序的下界。
-
描述排序问题的平均下界时:
- 仍然使用平衡决策树进行计算,平均复杂性用从根结点到每个叶子结点的路径长度的总长度描述。
一些题目:
(二分法即可,注意A[mid]≥k,且print up,且low=mid+1)
closest_pair(A)
If A.size <= 1 return INF
sort_by_x(A) //对n个点的横坐标进行排序
m = A.size/2; x= A[m].first //按照x坐标划分子问题
d = min(closest_pair(A[0..m]),closest_pair(A[m+1..n])) //d为左右两区域的最小距离
sort_by_y(A) //根据y坐标值进行排序
B = []
For i = 1 to n
If A[i].first-x >= d continue;//从下往上寻找x∈[m-d,m+d]中的点,m为对应划分点
for j=0 to b.size //对于x处于范围内的每个点,exA2(x2,y2)
//.first为x坐标,.second为y坐标
dx = A[i].first – B[b.size-j].first //寻找y∈[y2-d,y2+d]的点
dy = A[i].second – B[b.size-j].second
If dy >= d break
d = min(d,sqrt(dx*dx+dy*dy)) //取最小距离。
B.push(A[i])
Return d