二分思想和分治法
如果你对概念很敏感,会马上意识到这两者的细微不同:二分搜索每次都要舍弃一半,从留下的一半中寻找目标;而分治法把一个大问题分成两个或多个小问题,递归地求这些小问题的解,最后再把它们小心谨慎的合并起来,并且要仔细考虑合并时产生的新的情况。这当然没有错,但你也马上会从这里意识到两者的巨大联系。就拿选取数组中第k个最小的数的算法来说,有一个版本便是从快速排序中修改而来:划分后,舍弃掉不存在的区间,对剩余部分迭代(后文将进行讲解),而快速排序是分治法的典型代表。
正式地把这个问题叙述为:
在O(n)时间内从数组x[0...n-1]中找出第k个最小的元素。可以改变原数组中元素的位置。
下面这段代码就是从快速排序中修改而来,同时考虑到了随机选择划分元素的问题。
扩展:(《续》习题15.2)如何从一个3元数组中选出第2小的?如果从1000000个中选出1000个最小元素、且输入存储在磁带上呢?
分析:前者至多只需3次比较:1和2、1和2中最大的和3、1和2中最小的和3;后者是遍历时用1000大小的最大堆保存1000个当前最小的即可。其实前者是为了说明,如果问题只有几步就可以解决,根本没必要使用复杂的递归函数,直接解就是了;而后者是因为磁带进行随机I/O不方便而已,否则,直接用K=1001划分,那么K前面的1000个就是所求的元素。
如果你对概念很敏感,会马上意识到这两者的细微不同:二分搜索每次都要舍弃一半,从留下的一半中寻找目标;而分治法把一个大问题分成两个或多个小问题,递归地求这些小问题的解,最后再把它们小心谨慎的合并起来,并且要仔细考虑合并时产生的新的情况。这当然没有错,但你也马上会从这里意识到两者的巨大联系。就拿选取数组中第k个最小的数的算法来说,有一个版本便是从快速排序中修改而来:划分后,舍弃掉不存在的区间,对剩余部分迭代(后文将进行讲解),而快速排序是分治法的典型代表。
正式地把这个问题叙述为:
在O(n)时间内从数组x[0...n-1]中找出第k个最小的元素。可以改变原数组中元素的位置。
下面这段代码就是从快速排序中修改而来,同时考虑到了随机选择划分元素的问题。
int partition(int *array, int p, int r) {
int x,i,j;
x = array[r];
i = p-1;
for (j=p;j<=r-1;j++) {
if (array[j]<= x) {
i++;
swap_value(array+i,array+j);
}
}
swap_value(array+i+1,array+r);
return i+1;
}
int random_select(int *array, int p, int r, int i) {
int q,k;
if (p == r)
return array[p];
q = random_partition(array,p,r);
k = q-p+1;
if (i == k)
return array[q];
else if (i<k)
return random_select(array, p, q-1, i);
else
return random_select(array, q+1, r, i-k);
}
扩展:(《续》习题15.2)如何从一个3元数组中选出第2小的?如果从1000000个中选出1000个最小元素、且输入存储在磁带上呢?
分析:前者至多只需3次比较:1和2、1和2中最大的和3、1和2中最小的和3;后者是遍历时用1000大小的最大堆保存1000个当前最小的即可。其实前者是为了说明,如果问题只有几步就可以解决,根本没必要使用复杂的递归函数,直接解就是了;而后者是因为磁带进行随机I/O不方便而已,否则,直接用K=1001划分,那么K前面的1000个就是所求的元素。