算法分析与设计——选第k小的数


一、问题

设L是n个元素的集合,从L中选取第k小的元素,即L按从小到大排序之后,排在第k个位置的元素,其中1<=k<=n,

二、解析

随意取一个子分治过程如下:
假设m*表示当前集合中从小到大排列的中位数,|s| 表示s集合中的元素个数,则:

在这里插入图片描述
若k=|s1|+1,那么此时的m*(即中位数)就是要找的第k小的数,因为按照m划分之后,比m小的有|s1|个,如果恰巧k=|s1|+1,则m*就是要找的第k小的数;

若k<=|s1|,则说明第k小的数在s1中,归纳为在s1中找第k1小的子问题,k1的相对位置不变,即k1=k;

同理,若k>|s1|+1,则归纳为在s2中找k2的子问题,其中k2=k-|s1|-1,也就是要算上s1里一定比k小的|s1|个数,再找k2个数,才是分治前集合的第k个位置

三、设计

输入:n个无序的正整数
输出:从小到达排列中第k个位置的数

  1. n个数形成一个初始集合S;
  2. 将S中的数从小到大排列,选出中位数m*=S[n/2];
  3. 按m将S进行区域划分:S中比m小的数按序成为新集合S1,比m*大的数按序成为新集合S2;
  4. 后续判断过程同解析部分,不断细分S1或S2为新的两个集合,直到找到k;

四、分析

在这里插入图片描述

五、代码

#include<iostream>
#include<vector>
#include<cmath>
#include<time.h>
#include<cstring>
#include<algorithm>
 
using namespace std;

void merge(int *a, int left, int mid, int right) //二分归并算法  
{  
    int k, begin1, begin2, end1, end2;
    begin1 = left;  
    end1 = mid;  
    begin2 = mid + 1;  
    end2 = right;  
    int *temp = (int *)malloc((right - left + 1) * sizeof(int));  
    for(k = 0; begin1 <= end1 && begin2 <= end2; k++) //自小到大排序  
    {  
        if(a[begin1] <= a[begin2])  
            temp[k] = a[begin1++];  
        else  
            temp[k] = a[begin2++];  
    }  
    if(begin1 <= end1) //左剩  
        memcpy(temp + k, a + begin1, (end1 - begin1 + 1) * sizeof(int));  
    else //右剩  
        memcpy(temp + k, a + begin2, (end2 - begin2 + 1) * sizeof(int));  
    memcpy(a + left, temp, (right - left + 1) * sizeof(int)); //排序后复制到原数组  
    free(temp); //释放空间  
}  
void merge_sort(int *a, unsigned int begin, unsigned int end)  
{  
    int mid;  
    if(begin < end)  
    {   
        mid = (end + begin) / 2; 
        merge_sort(a, begin, mid); //分治  
        merge_sort(a, mid + 1, end); //分治  
        merge(a, begin, mid, end);  //合并两个已排序的数列  
    }  
} 

int select(int a[],int left,int right,int k) 
{
	int n=right-left;
	if (n<5)
	{
		merge_sort(a,left,right-1);
		return a[left+k-1];
	}
	int i;
	int s=n/5;
	int *m = new int[s];//中位数数组
	for (i=0;i<s;i++) 
	{
		merge_sort(a,left+i*5,left+i*5+5-1);
		m[i] = a[left+i*5+2];
	}
	merge_sort(m,0,i-1);
	int mid=m[i/2];
	int *a1=new int[n];
	int *a2=new int[n];
	int *a3=new int[n];
	int num1=0,num2=0,num3=0;
	for(int i=left;i<right;i++)
	{
		if(a[i]<mid)
			a1[num1++]=a[i];
		else if(a[i]==mid)
			a2[num2++]=a[i];
		else
			a3[num3++]=a[i];
	}
	if(num1>=k)
		return select(a1,0,num1,k);
	if (num1+num2>=k)
		return mid;
	else
		return select(a3,0,num3,k-num1-num2);
}
 
int main()
{
    int n;
    printf("输入数据规模:\n");
    scanf("%d",&n);
    int a[n];
    printf("输入数据:\n");
    for(int i=0;i<n;i++)
    {
    	scanf("%d",&a[i]);
	}
	int k;
	printf("请输入所求的第几小元素:\n");
	scanf("%d",&k);
	printf("第%d小元素:",k);
	printf("%d\n", select(a,0,n,k));
    system("pause");
    return 0;
}
/*
测试数据:
10
1 2 3 5 6 7 8 9 10 11
4
*/
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值