您的位置:首页 > 其它

最坏情况为线性时间的选择算法---算法导论学习笔记(2)

2016-11-08 21:46 465 查看
前面学习了在期望时间内选择任意顺序统计量,这次就运用这种顺序统计量的选择去实现线性时间的选择。

1.算法概述

1.将集合分成ceil(n/5)个组,即每个组中的元素都为5,最后一组元素数量为n mod 5
2.对每个组分别使用插入排序,并寻找出每个组的中位数。
3.每个组的中位数形成数量为ceil(n/5)的集合,在此集合内再求其中位数,即中位数的中位数,记为x。(这里就要再次递归调用select函数)
4.使用分区函数partition(就是上次笔记里学习到的partition分区),得到索引i,索引位置之前的低分区要比索引位置之后的高分区数量x小1
5.如果i=k,则返回x;如果i<k,则递归调用低分区(left,i-1,k);如果i>k,则递归调用高分区(i+1,right,k-i)


以上就是整个算法的过程,看起来比较的笼统,因为真正涉及到实现的时候有挺多的细节问题要注意到。尤其是在第(5)步递归调用的时候,需要控制好边界条件和分区函数的思路一样。

2.Java实现

public class SelectN {
public static void InsertSort(int[] arr,int start,int end) {
for(int i=start;i<=end;i++) {
for(int j=start;j<i;j++) {
if(arr[i]<arr[j]) {
int x=arr[i];
for(int k=i;k>j;k--) {
arr[k]=arr[k-1];
}
arr[j]=x;
break;
}
}
}
}
public static int partition(int[] arr,int start,int end,int key) {
int i=start;
int j=end;
while(true) {
while(i<end&&arr[i]<=key)
i++;
while(arr[j]>key)
j--;
if(i>=j)
break;
swap(arr,i,j);
}
swap(arr,findk(arr,key),j);       //交换最终结果
return j;
}
//获得元素的索引
public static int findk(int[] arr,int key) {
for(int i=0;i<arr.length;i++)
if(arr[i]==key)
return i;
return -1;
}

public static int select(int[] arr,int left,int right,int k) {
if(right-left<5) {
InsertSort(arr,left,right);
return arr[left + k - 1];
}
int group=(right-left+5)/5;
for(int i=0;i<group;i++) {
int l=left+i*5;
int r;
if(left+i*5+4>right)
r=right;
else
r=left+5*i+4;
InsertSort(arr,l,r);
swap(arr,left+i,(r+l)/2);        //将中位数都放在第一组集合中
}
InsertSort(arr,left,left+group-1);   //对中位数进行排序
int line=select(arr,left,left+group-1,(group+1)/2);
int lower=partition(arr,left,right,line);  //得到中位数的中位数分区后的索引
if(k==lower)                        //若索引等于k,则直接返回
return arr[lower];
else if(k<=lower-1)
return select(arr,left,lower-1,k);
else
return select(arr,lower+1,right,k-lower);//k-lower的意思是在高分区中k(作为整体中)的位置
}

public static void swap(int[] arr,int i,int j) {
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}

public static void main(String...args) {
int[] arr={22,3,1,55,2,53,552,62,35,261,12,5125,3,42,1,23,5,26,4,7,68,4};
//        System.out.println(select(arr,0,arr.length-1,4));
for(int i=1;i<arr.length;i++) {
System.out.println(select(arr,0,arr.length-1,i));
}

}
}


在public static int partition(int[] arr,int start,int end,int key),public static int select(int[] arr,int left,int right,int k)这两个函数的思路我借鉴了算法之线性时间选择(最坏情况下)这位博主的思路,我觉得相比书上的partition,这个代码更加简洁,而且得到的分区效果相同。不过相应我没看懂的地方或者我觉得有问题的地方做了修改(原谅我水平低).值得一提的是原select函数中,应该是元素数量小于75的直接进行插入排序后返回结果,这样做是对的,但是我比较急于测试,所以改成了小于分组大小时直接排序输出。感谢原博主。

3.分析

对于大于中位数的中位数x,大于x的元素个数至少有:

3(⌈1/2⌈n/5⌉⌉−2)⩾3n/10−6

那么,递归至多作用于(7n/10+6)个元素。

算法中的

第(3)步时间复杂度为T(⌈n/5⌉)

第(5)步的时间复杂度为T(7n/10+6)

我们可以得到算法的总时间复杂度为

T(n)≤T(⌈n/5⌉)+T(7n/10+6)+O(n)

假设一个任意常数C,使得T(n)≤cn

又假设O(n)的上界为an

那么带入c,a得到:

T(n)⩽9cn/10+7c+an=cn+(−9n/10+7c+an)

只需要证明: −9n/10+7c+an⩽0 即可

该不等式等价于:c⩾10a(n/(n−70))

所以当n>140时,c⩾20a这样就可以满足不等式。

因此最坏情况下选择算法的运行时间是线性的。

在边界条件的调试中花了不少的时间,感觉一开始就对整个算法的理解不是很清楚,更别说什么复杂度分析了,所以说明还是基本功不太扎实。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: