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

来源:互联网 发布:淘宝专业版店铺装修 编辑:程序博客网 时间:2024/05/16 15:31

前面学习了在期望时间内选择任意顺序统计量,这次就运用这种顺序统计量的选择去实现线性时间的选择。
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/2n/52)3n/106
那么,递归至多作用于(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+an0 即可
该不等式等价于:c10a(n/(n70))
所以当n>140时,c20a这样就可以满足不等式。
因此最坏情况下选择算法的运行时间是线性的。


在边界条件的调试中花了不少的时间,感觉一开始就对整个算法的理解不是很清楚,更别说什么复杂度分析了,所以说明还是基本功不太扎实。

0 0
原创粉丝点击