08基于特定分治策略的选择第 k 小元素问题
1.问题
我们将如何从一给定的无序数组中选择特定序位的元素的问题定义为选择问题。
常见的选择问题有:选最大、选最小、选中位数、选第二大……
若使用符号可以统一描述为: 设 L 是 n 个元素的集合,从 L 中选取第 k 小的元素,其中 0 <= k < n. 这里的第 k 小元素是指,当 L 按从小到大排好序之后,排在其第 k 个位置的元素。
2.解析
BFPRT算法:基于分治思想的、解决从n个元素中选出第k小或第k大元素问题的算法,也被称为BFPRT算法。它由Blum、Floyd、Pratt、Rivest、Tarjan提出,因此得名。
BFPRT算法的基本思想:
1). BFPRT算法类似快速排序算法,它先将 L 划分成每5个一组,共 ⌈n/5⌉ 个组
2). 然后递归求取数组L的中位数,并以该中位数m*作为对序列进行划分的标准。
3). 每一趟将小于m*的元素交换到左边,将大于m*的元素交换到右边,然后将m*插入到它们的中间。把每个元素与m*比较,小的构成L1,大的构成L2.
最终得到的序列——m*左边的元素都小于等于m*,m*右边的元素都大于等于pivot。
为简化问题,我们可以先假设n是5的倍数。
若n不是5的倍数,可以先把多余的数放在一边,由前面数组得到m*后再将它们与m*比较,随之为它们分配位置;还可以事先将L的元素个数用极小的数(如-0xffff)补成5的倍数,再执行算法程序(但此法只便于化解第一次的情况)。
3.设计
将 L 划分成每5个一组,共 ⌈n/5⌉ 个组;
每组找一个中位数,把这些中位数放到集合M中;
m* ←- getMedian(M,⌈|M|/2⌉) //选M的中位数m*,将L中的数划分
把A和D中的每个元素与m*比较,小于等于的构成L1,大的构成L2;
if k=|L1|
then 输出m*
else if k<|L1|
then Bfprt(left,|L1|-1,k)
else
then Bfprt(|L1|+1, right, k)
end
额外补充:
顶函数符号:⌈ ⌉, ⌈ x ⌉表示取大于等于x的最小整数(向上取整)。
例子:⌈2.5⌉=3, ⌈−2.5⌉=−2, ⌈2⌉=2.
底函数符号:⌊ ⌋, ⌊ x ⌋表示取小于等于x的最大整数(向下取整)。
例子:⌊2.5⌋=2, ⌊−2.5⌋=−3, ⌊2⌋=2.
4.分析
剩余的tn/10为已确定的中位数。
Bfprt()=O(n).
5.源码
#include<iostream>
#include <algorithm>
#include<cstdlib>
#include<cmath>
#define N 10
using namespace std;
//插入排序,升序
void insertSort(int a[], int left, int right) {
int i, j, temp;
for (i = left + 1; i <= right; i++) {
j = i - 1;
temp = a[i];
while (j >= left && a[j] > temp) {
a[j + 1] = a[j];
j--;
}
a[j + 1] = temp;
}
}
void swap(int* a, int* b) {//交换值
int temp = *a;
*a = *b;
*b = temp;
}
//得到每组中位数所构成集合的中位数
int getMedian(int* const left, int* const right) {
if (left >= right-1) {
return *left;
}
int* l, * r;
int dis = 0;
for (l = left; l <= right; l+=5) {
if (l + 4 <= right) {
r = l + 4;
}
else {
r = right;
}
//用插入排序将每组排序
insertSort(left,l-left,r-left);
//将中位数交换到每组的第一个
swap(left + dis, l + (r - l) / 2);
dis++;
}
return getMedian(left, left + dis);
}
//改编快速排序,升序,返回第k位值
int Bfprt(int* const left, int* const right, int* const k) {
int* l = left, *r = right;
int pivot = getMedian(left, right);
while (l < r) {
while (l < r && *r >= pivot) {
r--;
}
*l = *r;
while (l < r && *l <= pivot) {
l++;
}
*r = *l;
}
*l = pivot;
if (l == k) return *l;
else if (l > k) return Bfprt(left, l - 1, k);
else Bfprt(l+1, right, k);
}
void printArray(int a[], int n) {//打印数组,每五个换行
for (int i = 0; i < n; i++) {
cout << a[i] << "\t";
if ((i+1) % 5 == 0) {
cout << endl;
}
}
}
int main() {
int arr[N] = {2,4,1,7,8,6,9,5,3,10};
/*insertSort(arr, 0, N - 1);
printArray(arr, N);*/
int k = 1;//1
cout << "Please enter k between 1 and 10:" ;
cin >> k;
cout<< "The value of index k is:"<<Bfprt(arr, arr+N-1, arr+k-1);
return 0;
}