08分治应用——选择第 k 小元素问题的BFPRT算法

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.源码

源码已在GitHub维护,可点击查看。

#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;
}

参考博客:BFPRT算法:时间复杂度O(n)求第k小的数字(分治算法+快排)

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值