递归与分治策略之线性时间选择(随机划分线性选择)

题目描述

给定线性序集中n个元素和一个整数k,1≤k≤n,要求找出这n个元素中第k小的元素。

输入包括两行:
第一行两个数:n k (n表示数组长度 k表示第k小的数)
第二行输入数组的n个元素。
输出:第k小的数

例如输入:
8 5
44 39 27 11 88 85 40 33
则输出:
40

解题方法-随机划分线性选择

我们以上面的实例为例,则该算法的大致思路如下:
初始情况:

01234567
4439271188854033

我们现在要找到该数组中的第5小的数

开始思路跟快速排序是一样的。
首先 i=0 j=7 分别指向数组的首尾
我们选取一个基准,在这里我们全部以数组的首部为基准!

在真正的算法中我们是在i和j之间选取一个随机数作为小标,并将该下标表示的数与数组首部交换,然后再以首部为基准(这个基准就成了我们随机选择的了)!

我们选择a[i]=44位基准,进行类似于快速排序的交换

  • j=7开始从右向左扫描,找到33<44,我们将他们交换,得到如下的结果:
01234567
3339271188854044

此时:i=1 j=7

  • i=1开始从左往右扫描,找到 88>44 ,我们将它与基准进行交换,得到如下的结果:
01234567
3339271144854088

此时:i=4 j=6

  • j=6开始从右往左开始扫描,找到40<44,我们将他们交换,得到如下的结果:
01234567
3339271140854488

此时:i=5 j=6

  • i=5开始从左往右扫描,找到85>44,我们将他们进行交换,得到如下的结果:
01234567
3339271140448588

此时:i=5 j=5

由于此时i==j==5,故扫描停止
此时,a[i]左边的元素都小于它,右边的元素都大于大。
根据a[i]的下标为5,我们知道该基准是第6小的数,由于我们要找的是第5小的数,故接下来我们只需要在如下数组进行查找即可:

01234
3339271140

接下来我们在进行上面的过程,此时有:基准=33 i=0 j=4

  • j=4开始从右往左开始查找,找到11<33,我能将他们进行交换后得到结果如下:
01234
1139273340

此时:i=1 j=3

  • i=1开始从左往右开始查找,找到39>33,我能将他们进行交换后得到结果如下:
01234
1133273940

此时:i=1 j=3

  • j=3开始从右往左开始查找,找到27<33,我能将他们进行交换后得到结果如下:
01234
1127333940

此时:i=2 j=2

由于此时i==j==2,故扫描停止
此时,a[i]左边的元素都小于它,右边的元素都大于大。
根据a[i]的下标为2,我们知道该基准是第3小的数,由于我们要找的是第5小的数,故接下来我们只需要在如下数组进行查找即可:

12
3940

由于左边去掉了i+1=3个数,故只需要在上面数组中查找第k-(i+1)=2小的数即可!

在数组

01
3940

中我们选取:基准=39 i=0 j=1 k=2

  • j=1开始,从右往左搜索,当j==i==0是为找到小于基准的数,故:
    基准39是第i+1=1小的数,由于i+1=1<k=2,故我们需要查找的数在基准的右边数组:
0
40

由于左边去掉了一个元素,我们只要查找该数组第k-(i+1)=1小的数。
由于此时i==j说明该数组只有一个元素,故直接返回,我们得到40就是最终结果。

代码描述

#include<stdio.h>
#include<string.h>

/** * @author zjq~ * @time 2017/07/03 * @function 线性时间选择(随机划分线性选择) */

int randomSelect(int a[], int i, int j, int k) {
    //输出传过来的数据
// printf("-----------------------------------\n");
// printf("i=%d j=%d k=%d\n",i,j,k);
// for(int x=i;x<=j;x++){
// printf("%d ",a[x]);
// }
// putchar('\n');
//
// printf("-----------------------------------\n");

    if(i<j) {
        int l=i,r=j,base=a[l];  //设置左边游标位置 右边游标位置 以及基准(理论上是随机确定基准的下标)
        while(l<r) {
            while(a[r]>base) {      //从右往左扫描
                r--;
            }

            //找到右边一个比基准小的
            if(l<r) {               //判断是否j>i
                a[l++]=a[r];
            }

            while(a[l]<base) {      //接下来,从左往右扫描
                l++;
            }
            //找到左边一个比基准大的

            if(l<r) {               //判断是否i<j
                a[r--]=a[l];
            }
        }
        //扫描完成 ,填充基准 此时 l==r
        a[l]=base;

        if(k<l-i+1) {
            return randomSelect(a,i,l-1,k);
        } else if(k==l-i+1) {
            return a[l];
        } else {
            return randomSelect(a,l+1,j,k+i-1-l);
        }

    } else {
        return a[i];
    }
}

int main() {
    int a[100];
    int n,k;
    scanf("%d %d",&n,&k);
    for(int i=0; i<n; i++) {
        scanf("%d",&a[i]);
    }

    int keyValue=randomSelect(a,0,n-1,k);
    printf("%d\n",keyValue);
}

最后

在最坏的情况下,该算法的时间复杂度为 O(n2) ,平均时间复杂度为 O(n)
在下一篇博文中我们会来谈谈如何能在最坏情况下用 O(n) 的时间完成任务!

    原文作者:递归与分治算法
    原文地址: https://blog.csdn.net/zjq_1314520/article/details/74166880
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞