减治法在查找算法中的应用
快速查找:选择问题是求一个n个数列表的第k个最小元素的问题,这个数k被称为顺序统计量。对于k=1或k=n来说,这并没有什么意义,我们通常会要找出这样的元素:该元素比列表中一半元素大,比另一半元素小,这样的元素被称为中值。我们当然可以对列表进行排序,之后找出对应下标的值,但是!!!这样一个查找问题,反而要对整个列表排序,是不是有点多余了呢?
这里引入划分的概念我们可以标定一个枢轴(任意元素,一般为首个元素),使得左半部分元素均小于枢轴,右半部分均大于枢轴。划分的方法由两种,Lomuto划分和Hoare划分。这里仅介绍Lomuto。
我们假设有一个数组a[0, n-1],其子数组为a[l, r](0 <= l <= r <= n-1),假定首个元素为枢轴p,将该数组分为三段,顺序放在p之后,依次为,第一段[元素小于p],第二段[元素大于等于p],第三段[尚未处理元素]。算法开始时前两段均为空。
从i = l+1开始,从左到右扫描子数组a[l, r],将第三段的首个元素与p比较,若a[i]>=p,执行i+1,这就相当于将a[i]划入了第二段,同时缩小了第三段;若a[i]<p,需要将s+1(s始终指向第一段的末位元素),同时交换a[i]与a[s],之后i+1。直到第三段为空,交换a[p]与a[s]。
下图为Lomuto划分示意图:
熟悉快速排序的读者估计看出来了,这就是快速排序中的一部分函数,只不过没有接触过Lomuto这种叫法而已。
当然,我们这里使用的方法就是快速选择(“快速”这一方法一开始并非用于排序,而是查找),下面给出查找第k小元素的代码:
public class Main {static int[] a= {89, 45, 68, 90, 29, 34, 17};static int k = 2;public static void main(String[] args) {System.out.println(fastsort(0, a.length-1, k));for (int i = 0; i < a.length; i++) {System.out.print(a[i] + " ");}}private static int Lomuto(int l, int r) {int p = a[l];int s = l;for (int i = l+1; i <= r; i++) {if (a[i] < p) {s = s+1;int temp = a[s];a[s] = a[i];a[i] = temp;}}int temp = a[l];a[l] = a[s];a[s] = temp;return s;}private static int fastsort(int l, int r, int k) {int s = Lomuto(l, r);/*** s在划分之后变成了枢轴所在的位置下标,如果s=k,输出a[s]* 这里要写成l+k-1,如果划分到右侧,只写k会出问题* */if (s == l + k - 1) {return a[s];}else if (s > l + k - 1){return fastsort(l, s-1, k);} else {return fastsort(s+1, r, l+k-1-s);}}
}
不幸的是,这样的算法时间复杂度为O(n^2),比之前基于排序的方法实际上更糟糕,但是分析表明,这种方法的平均情况下效率是线性的。而且基于划分的算法不仅可以查找第k小的元素,还可以给出列表中k个最小元素和n-k个最大元素。