分而治之,模仿了法国皇帝拿破仑在1805年12月2日奥斯特里茨战役中采用的接触策略。奥地利、俄罗斯联军的人数比拿破仑的军队多出大约15000人。奥俄联军向法军右翼发动大规模进攻。拿破仑预见到这一进攻,他向联军中央部位发起冲击,将他们的军队一分为二。因为这两支较小的军队无法分别于拿破仑匹敌,均遭受严重损失,被迫撤退。拿破仑将一支大型军队分割(divide)为两支较小的军队,再分别治服这两支较小的军队,从而治服(conquer)整个大型军队。
分而治之(divide-and-conquer)方法将同一策略应用于问题的一个实例。也就是说,它将问题的一个实例划分为两个或更多个较小的实例。这些较小的实例通常也是原问题的实例。如果可以轻松获得较小问题实例的答案,那么通过合并这些答案,就能得出原实例的答案。如果这些较小的实例还是太大,难以轻松解决,可以将它们划分为再小一些的实例。一直持续这一实例划分过程,直到其规模小到可以轻松获得答案为止。
分而治之方法是一种自顶向下(top-down)方法。也就是,通过向下获得较小实例的答案,以获得一个问题顶级实例的答案。其实,这就是递归例程使用的方法。
1. 二分查找
用分而治之的术语来说,二分查找是在一个有序(非递减顺序)数组中查找键x的位置时,首先将x与数组的中间项进行对比。如果它们相等,则算法完成。如果不相等,则将数组分为两个子数组,一个子数组中包含中间项左侧的所有元素,另一个子数组包含其右侧的所有元素。如果x小于中间项,则将这一过程再应用于左侧子数组。否则,将其应用于其右侧子数组。也就是将x与适当子数组的中间项进行比较。如果它们相等,则算法完成。如果不相等,则将子数组划分为两个更小的数组。一直重复这一过程,直到找出x,或者最终确认x不在数组中。
#####二分查找(递归版本):
问题:判断x是否在一个大小为n的有序数组S中?
输入:正整数n;键值x;有序(非递减顺序)数组S,索引范围为1~n.
输出:location,x在数组S中的位置(若x不在数组S中,则为0).
index location(index low, index high)
{
index mid;
if (low > high)
reutrn 0;
else {
mid = (low + high) / 2;
if (x == S[mid])
return mid;
else if ( x < S[mid])
return location(low, mid-1);
else
return location(mid+1, high);
}
}
####二分查找(迭代版本):
index location(type[] S)
{
index low, high, mid;
low = 1; high = n;
while (low <= high)
{
mid = (low + high) / 2;
if (x == S[mid])
return mid;
else if (x < S[mid])
high = mid - 1;
else
low = mid + 1;
}
return 0;
}
2. 合并排序
我们用两路合并(two-way merging)表示将两个有序数组合并为一个有序数组。重复应用此合并过程,可以完成对一个数组的排序。例如,对于一个包含16个元素的数组来说,可以将其分为两个大小各为8的子数组,对这两个数组进行排序,然后合并它们,生成有序数组。同样,可以将每个大小为8的子数组进一步划分为两个大小为4的子数组,然后对这些子数组进行排序和合并。最终,子数组的大小将变为1,大小为1的数组显然是有序的。这一过程称为“合并排序”。
合并排序:
问题:将n个元素的数组排列为非递减顺序。
输入:正整数n;数组S,其索引范围为1~n。
输出:数组S,其中的元素按非递减顺序排列。
void mergsort(int n, type[] S)
{
if (n > 1){
int h = n / 2;
int m = n - h;
type U[1..h], V[1..m];
将S[1]至S[h]复制到U[1]至U[h];
将S[h+1]至S[n]复制到V[1]至V[m];
mergsort(h, U);
mergsort(m, V);
merge(h, m, U, V, S);
}
}
void merge(int h, int m, type[] U, type[] V, type[] S)
{
index i, j, k;
i = 1; j = 1; k = 1;
while ( i <= h && j <= m)
{
if (U[i] < V[j])
{
S[k] = U[i];
i++;
}
else
{
S[k] = V[j];
j++;
}
k++;
}
if ( i > h)
将V[j]至V[m]复制到S[k]至S[h+m];
else
将U[i]至U[h]复制到S[k]至S[h+m];
}