线性时间选择算法
期望为线性时间的随机选择算法
期望为线性时间的随机选择算法 是一种解决选择问题的分治算法。它是以快速排序算法为模型的,与快速排序算法一样,仍然将输入数组进行递归划分;但,与快速排序算法不同的是,快速排序算法需递归处理划分的两边,而选择算法则是只处理一边。这一差异导致其性能上有所区别:快速排序算法的期望运行时间为 Θ(nlgn),而选择算法的期望运行时间为 Θ(n)。
代码如下:
//期望为线性时间的选择算法
public static int randomizedSelect(int[] array,int p,int r,int i) {
if(i > array.length) {
return -1;
}
if(p == r) {
return array[p];
}
int q = randomized_partition(array,p,r);
int k = q - p + 1;
if(i == k) {
return array[q];
}else if(i < k) {
return randomizedSelect(array,p,q-1,i);
}else {
return randomizedSelect(array,q+1,r,i-k);
}
}
//分区操作
private static int randomized_partition(int[] array, int start, int end) {
int pivot = (int) (start+Math.random()*(end-start+1));
int smallIndex = start - 1;
swap(array, pivot, end);
for (int i = start; i <= end; i++)
if (array[i] <= array[end]) {
smallIndex++;
if (i > smallIndex)
swap(array, i, smallIndex);
}
return smallIndex;
}
//交换数组元素位置
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
然而,由于划分操作randomized_partition的局限性,该选择算法的最坏情况运行时间为Θ(n2)。下面,将介绍一种改进的选择算法。
最坏情况为线性时间的选择算法
最坏情况为线性时间的选择算法 和 randomizedSelect 一样,select 算法通过对输入数组的递归划分来找出所需元素,但是,在划分的过程中 select 算法总能保证得到对数组的一个“好”的划分。select 使用的也是来自快速排序的确定性划分算法 partition ,但做了修改------把划分的主元也作为输入参数。
通过select算法,可确定一个有n>1个不同元素的输入数组中第p大的元素。
select 算法步骤如下:
(1) 将输入数组的n个元素划分为 group=(n+4)/5 组,每组5个元素,且最多只有1组由剩下的 n%5 个元素组成。
(2) 寻找这group组中每一组的中位数(通过对每组元素进行插入排序,然后确定每组有序元素的中位数),并将每组的中位数移至输入数组的前排位置(便于后续处理)。
(3) 对第二步中找出来的group个中位数,递归调用select以找出其中位数mid(如果有偶数个中位数,为了方便,约定mid是下中位数)
(4) 通过修改后的 partition 算法,把中位数的中位数mid作为主元对输入数组进行划分。
(5) 若 p=k (p是 partition 返回值,k是要选择的第k大的元素),则返回 a[p] ;若 p<k ,则在低区递归调用select查找第k大的元素;若 p>k ,则在高区递归调用select查找第 p-k 大的元素。
代码如下:
//最坏情况为线性时间的选择算法
public static int select(int[] a,int l,int r,int k){
if(k > a.length) {
return -1;
}
if(r-l <= 4){
insertionSort(a, l, r); //用简单插入排序进行排序
return a[l+k-1];
}
int group = (r-l+5)/5;
for(int i = 0;i<group;i++){
int left = l+5*i;
int right = (l+i*5+4) > r ? r : l+i*5+4;
int mid = (left+right)/2;
insertionSort(a, left, right);
swap(a, l+i, mid); //把各组中位数移至数组前排
}
int pivot = select(a,l,group-1,(group+1)/2); //找出中位数的中位数
int p = partition(a,l,r,pivot); //用中位数的中位数作为基准的位置
k = k-1;
if (k == p)
return a[p];
else if (k < p)
return select(a, l, p-1, k);
else
return select(a, p+1, r, k-p);
}
private static int partition(int[] array, int start, int end,int pivot) {
int smallIndex = start-1;
swap(array, pivot, end);
for (int i = start; i <= end; i++)
if (array[i] <= array[end]) {
smallIndex++;
if (i > smallIndex)
swap(array, i, smallIndex);
}
return smallIndex;
}
public static void insertionSort(int[] array,int l,int r) {
int temp;
for(int j = l+1; j <= r; j++) {
temp = array[j];
int i = j-1;
while(i >= l && array[i] > temp) {
array[i+1] = array[i];
i--;
}
array[i+1] = temp;
}
}
测试代码:
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a = new int[] { 1, 1, 1, 5, 6, 22, 44, 2, 11, 66 };
int aim = 8;
System.out.println("=====================");
int res = algorithm.randomizedSelect(a, 0, a.length-1, aim);
System.out.println(res);
System.out.println("=====================");
int ress = algorithm.select(a, 0, a.length-1, aim);
System.out.println(ress);
//输出均为 22
}