线性时间选择

题目描述

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

输入描述

第一行有两个正整数n,k.

接下来是n个整数(0<=ai<=1e9)。

输出描述

输出第k小的数

Sample Input

6 3
1 3 5 2 4 6

Sample Output:

3 

算法分析

1、先把数组按照5个数为一组进行分组,最后不足5个的忽略。对每组数进行排序(这里用了冒泡排序)求取其中位数。
2、把上一步的所有中位数移到数组的前面,对这些中位数递归调用线性时间选择算法求得他们的中位数。
3、将上一步得到的中位数作为划分的主元进行整个数组的划分。
4、判断第k个数在划分结果的左边、右边还是恰好是划分结果本身,前两者递归处理,后者直接返回答案。

注:时间复杂度约为O(N);

具体代码

#include<stdio.h>
#include<cstring>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int M = 2e6 + 1;
int a[M];
int Qu(int start, int end, int val)
{
    int pan;
    for (int i = start; i <= end; i++)
    {
        if (a[i] == val)
        {
            pan = i;
            break;
        }
    }
    swap(a[start], a[pan]);//将所有组的中位数的中位数放在每次寻找的首位
    int i = start;
    int len = end + 1;
    int mid = a[start];
    while (1)
    {
        while (a[++i] < mid && i < end)
            ;//记录比所找中位数大的位置,在所有中位数组前寻找
        while (a[--len] > mid);//在所有中位数租后寻找比中位数小的
        if (i >= len)
            break;//找到一个分界点len次分界点前边的数都比mid小后边的数都比mid大,而这个分界点就是mid在数组中的名次(排序后第几位)此时a[len]<mid
        swap(a[i], a[len]);//将比中位数小的放在中位数前边,大的放在中位数后边
    }
    a[start] = a[len];
    a[len] = mid;//再将a[len]和中位数换回,这里a[start]存的是mid中位数
    return len;//返回中位数的名次(这里中位数指的是分组后所有有组中位数的中位数)
}
int select_k(int start, int end, int val)
{
    if (end - start < 75)//当n大于75时3*(n-5)/10>=n/4区间,用中位数至少缩短1/4,(最多缩短1/2),小于75时直接冒泡排序找出第k个输出
    {
        for (int i = start; i < end; i++)//冒泡
            for (int j = i + 1; j <= end; j++)
                if (a[i] > a[j])
                    swap(a[i], a[j]);
        return a[start + val - 1];//找到的排在第k的数
    }
    for (int i = 0; i <= (end - start - 4) / 5; i++)//这里将数组分为(end-start+1)/5个没五个成员一组的小组
    {
        int s = start + 5 * i;//每个小组的第一个数
        int t = s + 4;//每个小组的最后你一个数
        for (int j = 0; j < 3; j++)
            for (int l = s; l < t - j; l++)//组间按照从小到大排序
            {
                if (a[l] > a[l + 1])
                    swap(a[l], a[l + 1]);
            }
        swap(a[start + i], a[s + 2]);//将每组的中位数放在a数组前边;意思是前(end+1-start)/5存的是每个组的中位数
    }
    int mid = select_k(start, start + (end - start - 4) / 5, (end - start + 1) / 10);//找到所有组中位数的中位数
    int midd = Qu(start, end, mid);//换位,找到此中位数的所在的名次(顺序位置)
    int len = midd - start + 1;
    if (val <= len)
        return select_k(start, midd, val);//如果小于所找的中位数位置则在从中位数所在组前寻找
    else
        return select_k(midd + 1, end, val - len);//如果大于则取中位数所在组的后去找
}
int main()
{
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i++)
        scanf("%d", &a[i]);
    int sequence_k = select_k(0, n - 1, k);
    printf("%d\n", sequence_k);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值