题目描述
给定线性序集中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
解题方法-随机划分线性选择
我们以上面的实例为例,则该算法的大致思路如下:
初始情况:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
44 | 39 | 27 | 11 | 88 | 85 | 40 | 33 |
我们现在要找到该数组中的第5小的数
开始思路跟快速排序是一样的。
首先 i=0 j=7
分别指向数组的首尾
我们选取一个基准,在这里我们全部以数组的首部为基准!
在真正的算法中我们是在i和j之间选取一个随机数作为小标,并将该下标表示的数与数组首部交换,然后再以首部为基准(这个基准就成了我们随机选择的了)!
我们选择a[i]=44
位基准,进行类似于快速排序的交换
- 从
j=7
开始从右向左扫描,找到33<44
,我们将他们交换,得到如下的结果:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
33 | 39 | 27 | 11 | 88 | 85 | 40 | 44 |
此时:i=1 j=7
- 从
i=1
开始从左往右扫描,找到88>44
,我们将它与基准进行交换,得到如下的结果:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
33 | 39 | 27 | 11 | 44 | 85 | 40 | 88 |
此时:i=4 j=6
- 从
j=6
开始从右往左开始扫描,找到40<44
,我们将他们交换,得到如下的结果:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
33 | 39 | 27 | 11 | 40 | 85 | 44 | 88 |
此时:i=5 j=6
- 从
i=5
开始从左往右扫描,找到85>44
,我们将他们进行交换,得到如下的结果:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
33 | 39 | 27 | 11 | 40 | 44 | 85 | 88 |
此时:i=5 j=5
由于此时i==j==5
,故扫描停止
此时,a[i]
左边的元素都小于它,右边的元素都大于大。
根据a[i]
的下标为5
,我们知道该基准是第6小的数,由于我们要找的是第5
小的数,故接下来我们只需要在如下数组进行查找即可:
0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
33 | 39 | 27 | 11 | 40 |
接下来我们在进行上面的过程,此时有:基准=33 i=0 j=4
- 从
j=4
开始从右往左开始查找,找到11<33
,我能将他们进行交换后得到结果如下:
0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
11 | 39 | 27 | 33 | 40 |
此时:i=1 j=3
- 从
i=1
开始从左往右开始查找,找到39>33
,我能将他们进行交换后得到结果如下:
0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
11 | 33 | 27 | 39 | 40 |
此时:i=1 j=3
- 从
j=3
开始从右往左开始查找,找到27<33
,我能将他们进行交换后得到结果如下:
0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
11 | 27 | 33 | 39 | 40 |
此时:i=2 j=2
由于此时i==j==2
,故扫描停止
此时,a[i]
左边的元素都小于它,右边的元素都大于大。
根据a[i]
的下标为2
,我们知道该基准是第3小的数,由于我们要找的是第5
小的数,故接下来我们只需要在如下数组进行查找即可:
1 | 2 |
---|---|
39 | 40 |
由于左边去掉了i+1=3
个数,故只需要在上面数组中查找第k-(i+1)=2
小的数即可!
在数组
0 | 1 |
---|---|
39 | 40 |
中我们选取:基准=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) 的时间完成任务!