线性时间选择【递归分治法】

顾名思义:这篇文章讲解的就是如果用线性时间算法来作出元素选择问题。
问题描述:给定线性序集中n个元素和一个整数k,1<=k<=n.要求找出这n个元素中第k小的元素,即如果将这个n个元素依其线性序排列时,排在第k个位置的元素就是要找的元素,当k==1时,要找的就是最小的元素;当k==n,就是最大的元素;当k=(n+1)/2,称为中位数。
问题分析

在某些特殊的情况下,我们可以实现线性时间选择,对于找最大最小的元素O(n)内可以实现;当k<=n/logn,通过堆排序算法可以在O(n+klogn)=O(n)内实现;当k>=n-n/logn时也一样。
下面是给出的一般的选择问题,从渐近阶的意义上看,这个也可以在O(n)时间内完成。
下面的算法实现参考了《计算机算法与分析》和一些博客,是对其的一个整理。

方法一

算法描述:用一个随机的序列中的数作为枢纽,用快速排序算法,进行一次快排,然后将枢纽值和k值进行比较,以此来确定k值,我并没有做任何的对比所以并不是清楚这种算法的效率有多少,但是搜到的结果表明,这种算法的最坏时间复杂度是O(n^2),相对与另一种是不太理想的。
代码实现如下:

int Partition(int L[],int low,int high);//一次快速排序
int RandomizedSelect(int L[],int p,int r,int k);//方法一 最坏情况需要O(n^2)时间,平均性能比较好
int Partition(int L[],int low,int high){
    int Privotkey=L[low];
    while(low<high){
        while(low<high && L[high]>Privotkey)//将不想要的情况都循环结束。
            high--;
        L[low]=L[high];
        while(low<high && L[low]<Privotkey)
            low++;
        L[high]=L[low];
    }
    L[low]=Privotkey;
    return low;
}
int RandomizedSelect(int L[],int p,int r,int k){
    int i,j;
    if(p==r)
        return L[p];
    i=Partition(L,p,r);
    j=i-p+1;//比i小的数有j个
    if(k<=j)
        return RandomizedSelect(L,p,i,k);
    else
        return RandomizedSelect(L,i+1,r,k-j);
}

如果大家有兴趣可以继续调试以及测试时间。

方法二:这种方法相对上面的改良点在于不是使用随机的枢钮值,而是采用划分的方法,经过一些数学的计算,确定时间复杂度O(n),所以比较推崇这种。

选择一个数组序列。
如:1 0 9 2 3 4 8 7 5 6
按照5个为一组进行划分,最后多的自动归为一组,找到每组的中位数,然后对所有的中位数进行排序,找到最终的中位数,如果为偶数取中间的两个中较大的那个。
以上则是3和6,取6,value=6;然后进行快排,找到value的位置,注意这个寻找的过程,编程中有很多要注意的地方,下面我会指出来。回到正题,value找到的位置是p=7,然后对比自己所要找的k,如果比较,k比较大,则对后半段递归,只不过k值要换成k-p;反之,则是对前半段递归,k值不变。
算法的通俗描述就是这样,至于一些计算的术语是这样,大家看的懂最好毕竟专业:

术语描述:
   线性时间选择,通过寻找一个好的划分基准,使得按照这个划分基准,划分出的两个子数组的长度都至少为原数组长度的e倍,e大于0小于1,这时可以保证算法最坏时间复杂度为O(n)。

一个好的划分基准:
1、 先将n个元素划分成[n/5+n%5]个,并取出每一组的中位数
2、 递归调用select函数,寻找这些中位数的中位数,以此作为划分基准
不失一般性(其实我认为已经失掉一般性了。。。。。。。。,但所有人都这么说),假设所有元素互不相同,则利用这种方法选择出来的基准x,至少有3*(n-5)/10(即2*(n-5)/5+1/2*(n-5)/5)个元素小于x,同理,也至少有3*(n-5)/10个元素大于x,。而当n>=75时,3*(n-5)/10>=n/4,所以按此基准划分所得两个数组的长度都至少缩短1/4。

代码实现:

//方法二 最坏情况时间复杂度也是O(n)。
int Select(int L[],int low,int high,int k);//>75比较合适的复杂线性时间选择算法
int Partition2(int L[],int low,int high,int value);//修改过后的快速排序算法
int Findmiddata(int L[],int low,int high);
int InsertSort(int L[],int a,int b);
int Partition2(int L[],int low,int high,int value){
    int l,temp,i,t;
    for(i=low;i<=high;i++)
        if(value==L[i])
        l=i;
    while(low<high){
        while(value>=L[low] && low<high)//有时候在循环中注意外部循环的条件不能控制内部循环
            low++;
        while(value<=L[high] && low<high)
           high--;
        temp=L[low];//对应交换
        L[low]=L[high];
        L[high]=temp;
       if(low>=high)
         break;
    }
    //以下是确定value的值需要插入的位置
    if(L[low]>=value)
        t=low;
    else if(L[low]<value &&L[high]>=value)
        t=high;
    else
        t=high+1;
      if(l>t)
        for(i=l;i>t-1;i--)
         L[i]=L[i-1];
      else
        for(i=l;i<t-1;i++)
         L[i]=L[i+1];
         L[t-1]=value;//注意这里的t是第几个的t,不是下标
        return t;
}
int Select(int L[],int low,int high,int k){
   int x,p,i;
   x=Findmiddata(L,low,high);//ok
   p=Partition2(L,low,high,x);
   printf("找到的中心点的值是:%d\n",x);
   printf("在序列中要进行交换的位置%d\n",p);
   printf("所有的序列如下:\n");
   for(i=0;i<=high;i++)
   printf("   %d",L[i]);
   printf("\n");
   if(p==k)
    return L[p-1];
   if(k<p){
    if(p-2==low)
        return L[low];
    Select(L,low,p-2,k);
   }
   else
    Select(L,p,high,k-p);
}
int Findmiddata(int L[],int low,int high){//切记不要用原有的数据虽说可以减少使用空间但是数据很容易乱
   int i,temp,mid;
   for(i=0;i<(high-low+5)/5;i++){
    if((low+i*5+4)<=high)
      mid=InsertSort(L,low+i*5,low+i*5+4);
   else
      mid=InsertSort(L,low+i*5,high);
    temp=L[mid];
    L[mid]=L[i];
    L[i]=temp;
   }
   mid=InsertSort(L,0,(high-low+5)/5-1);//尤其注意处理余数的时候,不要有遗漏
   return L[mid];
}
int InsertSort(int L[],int a,int b){
   int i,j,temp;
   for(i=a+1;i<=b;i++)
   {
       temp=L[i];
       for(j=i;j>a && L[j-1]>=temp;j--)
        L[j]=L[j-1];
       L[j]=temp;
   }
   if((b-a+1)%2==0)
    return (b+a)/2+1;
   else
    return (b+a)/2;
}

给出上面的截图:
这里写图片描述

遗留的问题:

算法实现的最终我测试的时候出了问题,就是对于后半段的递归很不顺利,他就一直报错,我把后半段拿到前面进行测试结果又是对的,如果谁有解决的方案欢迎给出。

编码注意事项:

1.途中的代码注释给出的;
2.分组的时候注意对余数处理的妥当;
3.中位数的取值要得当;
4.找到value值后寻找其位置,就是Partition2函数,一定要确定value值得最终位置后才能交换,这里面low,high有三种情况,分析清楚;

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值