分而治之策略不仅被君主和殖民者成功地用来统治殖民地,也可以用来设计有效的计算机算法。它可以用来解决以下问题:最小最大问题、矩阵乘法、一个娱乐数学————残缺棋盘问题、排序、选择和一个计算几何问题————在二维空间中寻找距离最近的点。
分而治之算法把一个问题实例分解为若干个小型而独立的实例,从而可以再并行计算机上执行;那些小而独立的实例,从而可以在并行计算机的不同处理器上完成。
分而治之方法与软件设计的模块化非常相似。一个问题的小实例可以直接用方法求解。而要解决一个问题的大实例,可以:
1)把它分解为两个或者多个更小的实例;
2)分别解决每个小实例;
3)把这些小实例的解组合成原始大实例的解。小实例通常是原问题的实例,可以使用分而治之策略递归求解。
用分而治之的方法可以实现快速排序。 把n个元素划分为3段:左段left、中间段middle和右段right。中段仅有一个元素,左段的元素都不大于中间段的元素,右段的元素都不小于中间段的元素。因此可以对left和right独立排序,并且排序后不用归并。middle的元素称为支点(pivot)或分割元素(partitioning element)。 以下是快速排序的简单描述:
//对a[0:n-1]快速排序
从a[0:n-1]中选择一个元素作为支点,组成中间段。
把剩余元素分为左段left和右段right。使左段的元素关键字都不大于支点关键字,右端的元素关键字都不小于支点的关键字
对左端递归快速排序
对右端递归快速排序
最终结果按照左端、中间段和右段排列
考虑元素序列[4,8,3,7,1,5,6,2]。假设选择元素6作为支点。因此6属于middle;4,3,1,5,2属于left;8和7属于right。left排序的结果为 :1,2,3,4,5;right排序结果为7,8。把右段right的元素放在支点之后,左段left的元素放在支点之前,即可得到有序序列[1,2,3,4,5,6,7,8]。在对数据段[4,3,1,5,2]递归快速排序时,如果选择3为支点,则左段包含1和2,右段包含4和5。左段和右段分别排序后,和支点组成有序段[1,2,3,4,5]。
递归快速排序的驱动程序:
template
void quickSort(T a[],int n)
{
if(n<1) return 0;
int max=indexOfmax(a,n);
swap(a[n-1],a[max]);
quickSort(a,0,n-2);
}
上述排序实现了,将数组a的最大值移动到数组的最右端,之后调用递归函数quickSort函数,该函数的实现如下:
template<class T>
void quickSort(T a[],int leftEnd,int rightEnd)
{
if(leftEnd>rightEnd)
return 0;
int leftCursor=leftEnd;//从左到右移动的索引
int rightCursor=rightEnd+1;//从右到左移动的索引
T pivot=a[leftEnd];
while(true)
{
//寻找左侧不小于支点的元素
do
{
leftCursor++;
}while(a[leftCurSor]//寻找右侧不大于支点的元素
do
{
rightCursor--;
}while(a[rightCurSor]>pivot);
if(leftCursor>=rightCursor) break;
swap(a[leftCursor],a[rightCursor]);
}
a[leftEnd]=a[rightCursor];
a[rightCursor]=pivot;
quickSort(a,leftEnd,rightCursor-1);//对左侧排序
quickSort(a,rightCursor+1,rightEnd); //对右侧排序
}
以上程序所需要的递归栈空间为O(n)。若使用栈来模拟递归,则需要的空间可以减少为O(log n)。在模拟过程中,首先对数据段left和right中较小的进行排序,把较大者的边界放入栈中。
在最坏的情况下,例如数据段left总是空,这时快速排序用时为O(n^2)。
在最好的情况下,即数据段left和right的元素数目总是大致相同,这时的快速排序用时为O(nlog n).
而快速排序的平均复杂度也是O(nlog n)。
(本文摘自数据结构、算法与应用 第二版)