显然,有些人认为二进制搜索是一种分而治之的算法,而有些人则不是。我很快搜索了三个参考文献(似乎都与学术界有关),它们称之为 D&C 算法:
http://www.cs.berkeley.edu/~vazirani/algorithms/chap2.pdf
http://homepages.ius.edu/rwisman/C455/html/notes/Chapter2/DivConq.htm
http://www.csc.liv.ac.uk/~ped/teachadmin/algor/d_and_c.html
我认为 D&C 算法应该至少具有这三个阶段的前两个阶段是普遍的共识:
- 划分,即决定如何将整个问题分成子问题;
- 征服,即独立解决每个子问题;
- [可选地]合并,即将独立计算的结果合并在一起。
第二阶段 - 征服 - 应该递归地应用相同的技术来解决子问题,方法是分成更小的子子问题等等。然而,在实践中,通常使用一些阈值来限制递归方法,如对于小尺寸问题,不同的方法可能会更快。例如,快速排序实现经常使用例如当要排序的数组部分的大小变小时时,冒泡排序。
第三阶段可能是空操作,在我看来,它不会取消算法作为 D&C 的资格。一个常见的例子是for-loop 的递归分解,所有迭代都纯粹使用独立的数据项(即不减少任何形式)。乍一看它可能看起来毫无用处,但实际上它是非常强大的方式,例如并行执行循环,并被 Cilk 和 Intel 的 TBB 等框架使用。
回到最初的问题:让我们考虑一些实现算法的代码(我使用 C++;如果这不是您喜欢的语言,请见谅):
int search( int value, int* a, int begin, int end ) {
// end is one past the last element, i.e. [begin, end) is a half-open interval.
if (begin < end)
{
int m = (begin+end)/2;
if (value==a[m])
return m;
else if (value<a[m])
return search(value, a, begin, m);
else
return search(value, a, m+1, end);
}
else // begin>=end, i.e. no valid array to search
return -1;
}
这里的分割部分是int m = (begin+end)/2;,其余的都是征服部分。该算法以递归 D&C 形式显式编写,即使只采用其中一个分支。不过也可以写成循环形式:
int search( int value, int* a, int size ) {
int begin=0, end=size;
while( begin<end ) {
int m = (begin+end)/2;
if (value==a[m])
return m;
else if (value<a[m])
end = m;
else
begin = m+1;
}
return -1;
}
我认为用循环实现二分查找是很常见的方法;我特意使用了与递归示例中相同的变量名,以便更容易看出共性。因此我们可以再次说,计算中点是除法部分,循环体的其余部分是征服部分。
当然,如果你的考官有不同的想法,可能很难让他们相信这是 D&C。
更新:刚刚想到如果我要开发一个 D&C 算法的通用骨架实现,我肯定会使用二进制搜索作为 API 适用性测试之一,以检查 API 是否足够强大同时又简洁。当然这并不能证明什么:)