BFPRT算法原理与应用
一、BFPRT算法原理
BFPRT算法(也称为中位数的中位数算法)是基于快速排序算法基础上的变种算法,用于寻找一个无序数组中第K小的数(或第K大的数)。 快速排序算法的核心思想是把一个无序数组的第一个数作为划分元素,将数组分为两个部分,左边部分的数都小于划分元素,右边部分的数都大于划分元素,然后分别对左边和右边的部分进行递归调用。但是快速排序算法的时间复杂度并不稳定,最坏情况下时间复杂度为O(n^2),这时候就需要使用BFPRT算法来解决这个问题。 BFPRT算法的核心思想是通过对数组进行特殊的划分,确定第K小的数所在的区间。它首先将数组分成若干个5个元素一组的区间,对每个小区间进行排序,然后取出每个小区间的中位数,把这些中位数作为一个新的数组。 然后递归寻找这个数组的中位数(即第N/2小的数),假设这个数是x,那么以x为中心,将数组分成两部分,如果新数组中数的个数比x小,说明第K小的数在新数组后半部分,否则在新数组前半部分。对于所选择的部分,再次递归调用进行查找,直到找到第K小的数。
int partition_median(int arr[], int left, int right) {
// 将数组以每5个元素为一组分组,最后剩下的不足5个的单独一组
for (int i = left; i <= right; i += 5) {
int sub_right = i + 4;
if (sub_right > right) {
sub_right = right;
}
// 将小组进行插入排序,可以提高查找中位数的效率
for (int j = i + 1; j <= sub_right; j++) {
int key = arr[j];
int k = j - 1;
while (k >= i && arr[k] > key) {
arr[k + 1] = arr[k];
k--;
}
arr[k + 1] = key;
}
// 将每个小组的中位数插入到数组的前面,方便后面进行递归查找
int mid = (i + sub_right) / 2;
int temp = arr[mid];
arr[mid] = arr[left + (i - left) / 5];
arr[left + (i - left) / 5] = temp;
}
// 递归查找所有中位数的中位数
int median = (left + (right - left) / 10);
if (left < right) {
int pivotIndex = partition_median(arr, left, right);
int i = partition(arr, left, right, pivotIndex);
if (median < i) {
// 第K小的数在数组左半部分
return partition_median(arr, left, i - 1);
}
else if (median > i) {
// 第K小的数在数组右半部分
return partition_median(arr, i + 1, right);
}
else {
// 找到第K小的数,返回其值
return arr[i];
}
}
else {
// 数组中只有一个元素,直接返回该元素的值
return arr[left];
}
}
二、BFPRT算法应用
- 寻找无序数组的中位数或第K小/大的数:使用定位排序算法,将数组划分成若干个小组,对每个小组进行排序,然后将每个小组的中位数插入到一个新数组中,对新数组递归查找其中位数,以此确定第K小/大的数的位置。
int bfprt(int arr[], int left, int right, int k) {
return partition_median(arr, left, right, k);
}
int main() {
int arr[] = {3, 2, 1, 5, 6, 4};
int k = 2; // 寻找第二小的数
int n = sizeof(arr) / sizeof(arr[0]);
int ans = bfprt(arr, 0, n - 1, k - 1);
cout << "第" << k << "小的数是:" << ans << endl;
}
- 求无序数组的中位数与排序时间的实现:使用标准库函数qsort排序,然后计算数组的中位数。这里定义了一个类,其中构造函数中调用了qsort函数,并获取数组的中位数,计算排序所需的时间。
class Median {
public:
double median_;
double sort_time_;
Median(int arr[], int n) {
clock_t start_time = clock();
qsort(arr, n, sizeof(int), cmp);
sort_time_ = (double)(clock() - start_time) / CLOCKS_PER_SEC;
if (n % 2 == 0) {
median_ = (double)(arr[n / 2 - 1] + arr[n / 2]) / 2.0;
} else {
median_ = (double)arr[n / 2];
}
}
static int cmp(const void* a, const void* b) {
return *(int*)a - *(int*)b;
}
};
int main() {
int arr[] = {3, 2, 1, 5, 6, 4};
int n = sizeof(arr) / sizeof(arr[0]);
int k = (n + 1) / 2;
Median m(arr, n);
cout << "排序所需时间: " << m.sort_time_ << "s" << endl;
cout << "无序数组的中位数: " << m.median_ << endl;
}
三、BFPRT算法的优缺点
优点:
- 时间复杂度稳定,最坏情况下也只需要O(n)的时间复杂度。
- 与快速排序算法相比,BFPRT算法在大量数据的排序上表现更加优秀。
- 在寻找数组中第K小/大的数时,其所需时间不超过O(n)。 缺点:
- 在查找较小的K值时,效率可能会变慢。尤其是当K比较小,但N非常大的时候,会造成很大的时间开销。
- 算法比较复杂,实现起来比较困难。
四、BFPRT算法的应用案例
- 使用BFPRT算法实现一道LeetCode的问题,寻找乱序数组中的第K大的元素。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
int arr[n];
for (int i = 0; i < n; i++) {
arr[i] = nums[i];
}
int ans = bfprt(arr, 0, n - 1, n - k);
return ans;
}
};
int main() {
Solution s;
vector<int> nums = {3,2,1,5,6,4};
int k = 2;
int ans = s.findKthLargest(nums, k);
cout << "第" << k << "大的数是:" << ans << endl;
}
- 使用BFPRT算法对海量数据进行快速排序。
// 将数组划分成若干个小区间,对每个小区间排序,然后获取各个小区间的中位数即可
template <typename T>
void bfprt_sort(vector<T>& nums, int left, int right, int k) {
if (left >= right) {
return;
}
if (right - left + 1 <= 5) {
sort(nums.begin()+left, nums.begin()+right+1);
return;
}
int pivotIndex = partition_median(nums, left, right);
int index = partition(nums, left, right, pivotIndex);
if (k < index) {
bfprt_sort(nums, left, index-1, k);
} else if (k > index) {
bfprt_sort(nums, index+1, right, k);
} else {
return;
}
}
template <typename T>
void big_data_sort(vector<T>& nums) {
int len = nums.size();
int left = 0;
int right = len - 1;
int group_num = len / 5;
int mod_num = len % 5;
vector<T> helper;
for (int i = 0; i < group_num; i++) {
sort(nums.begin() + i*5, nums.begin() + i*5 + 5);
helper.push_back(nums[i*5+2]);
}
if (mod_num > 0) {
sort(nums.begin() + group_num*5, nums.begin() + group_num*5 + mod_num);
helper.push_back(nums[group_num*5 + mod_num/2]);
}
// 新数组中的中位数即为整个数组的中位数
int median_index = helper.size() / 2;
bfprt_sort(helper, 0, helper.size()-1, median_index);
T median = helper[median_index];
// 使用整个数组的中位数进行划分
int pivot_index = partition_big_data(nums, left, right, median);
int k = len / 2;
if (len % 2 == 0) {
bfprt_sort(nums, left, right, k-1);
} else {
bfprt_sort(nums, left, right, k);
}
}
五、总结
BFPRT算法是一种常见的查找算法,其能够在大量数据的排序问题中表现出较高的效率和稳定性。通过对数据进行特殊的划分和查找,BFPRT算法能够寻找无序数组中第K小/大的数,得到数组的中位数,或对大量数据进行快速排序等应用。