问题的描述
快速选择
给定一个长度为 n n n的整数数列,以及一个整数 k k k,请用快速选择算法求出数列从小到大排序后的第 k k k个数。
输入输出数据要求
输入格式
第一行包含两个整数
n
n
n和
k
k
k。
第二行包含
n
n
n个整数(所有整数均在
1
∼
109
1∼109
1∼109范围内),表示整数数列。
输出格式
输出一个整数,表示数列的第 k k k小的数。
数据范围
1
≤
n
≤
100000
1≤n≤100000
1≤n≤100000
1
≤
k
≤
n
1≤k≤n
1≤k≤n
输入样例:
5 3
2 4 1 5 3
输出样例:
3
思路分析
总体思想
- 快速选择算法是建立在快速排序的基础上的,一种对快速排序算法的改进。快速选择算法依旧以分治为基本思想,与快速排序不同的是,快速选择是有条件的分治。
- 快速选择算法的主要思路如下:
- 取分界点
- 调整分界点两侧的区间使满足条件
- 判断第k小的数字在分界点左侧还是在分界点右侧,并根据k所在的侧再进行以上步骤。
- 可以明显看出,只有第三步与快速排序不同。
具体步骤
- 选择数组的左端点的数值为分界点数值。
- 声明两个指针 i , j i,j i,j并令其分别指向数组左端点和右端点。
- 将指针 i , j i,j i,j向数组中间移动。先移动指针 i i i,如果指针 i i i所指向的数据小于分界点数值 x x x,那么继续移动i;否则停止移动指针 i i i,转为移动指针 j j j。如果指针 j j j所指向的数据大于分界点数值 x x x,那么继续移动指针 j j j;否则停止移动指针 j j j。将指针 i i i所指向的数据和指针 j j j所指向的数据进行交换。从开头重复本步骤,直到 i , j i,j i,j相遇。
- 判断第 k k k小的数所在的位置,如果在以 x x x为分解点的左侧,那么对左侧重复以上步骤;否则在对右侧重复以上步骤。
功能代码
void quick_choose(int a[], int k, int l, int r){
//a[]快速选择的数组;k 第k小个数; l left快速选择区间的开始点;r right快速选择区间的结束点
if(l >= r) return; //只有一个数据的数组不需要排序
else{
int x = a[l], i = l - 1, j = r + 1; //选择分界点x,声明左开始位点i,右开始位点j
while(i < j){ //保持i和j不相遇的情况下
do i ++; while(a[i] < x); //先移动i,a[i]小于x继续移动,否则移动j
do j --; while(a[j] > x); //移动j,a[j]大于x继续移动,否则停止
if(i < j) swap(a[i], a[j]); //a[i],a[j]互换位置
} // 循环直到i和j相遇为支,此时i在j右侧,j在i右侧
int sl = j - l + 1; //左侧元素的个数
if(k <= sl) quick_choose(a, k, l, j);//如果第k小的数在左侧,那么对作左侧递归
else quick_choose(a, k - sl, j + 1, r);//如果在右侧,那么对右侧递归,第k小变为第k-sl小
}
}
代码
#include <iostream>
#include <algorithm>
using namespace std;
void quick_choose(int a[], int k, int l, int r){
if(l >= r) return;
else{
int x = a[l], i = l - 1, j = r + 1;
while(i < j){
do i ++; while(a[i] < x);
do j --; while(a[j] > x);
if(i < j) swap(a[i], a[j]);
}
int sl = j - l + 1;
if(k <= sl) quick_choose(a, k, l, j);
else quick_choose(a, k - sl, j + 1, r);
}
}
int main(){
const int N = 1e5 + 10;
int f[N];
int n, k;
cin >> n >> k;
for(int i = 0; i < n; i ++){
scanf("%d", &f[i]);
}
quick_choose(f, k, 0, n - 1);
printf("%d", f[k - 1]);
}