一、问题
设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个位置的数
- n个数形成一个初始集合S;
- 将S中的数从小到大排列,选出中位数m*=S[n/2];
- 按m将S进行区域划分:S中比m小的数按序成为新集合S1,比m*大的数按序成为新集合S2;
- 后续判断过程同解析部分,不断细分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
*/