TopK问题求解(堆排、快排、快排优化(bfprt算法求中位数))
(一)问题阐述:
在一个无序队列中,找到最大(或最小)的 K 个数。
(二)解决方案
在本文中,我们以找寻最大 K 个数为目的,讲解(代码中,最大(小)都有)。
(1)堆排序解法——平均O(n*logn)
首先,简单阐述一下堆排序的思想:
对于 n 个数的无序队列,将其视为一棵完全二叉树。然后,从最后一个父节点(father = n / 2 - 1)开始遍历,到堆顶节点(father = 0)为止,将原二叉树调整为最大(小)堆结构,然后从队尾开始遍历,与堆顶元素交换,每次交换,待排序队列长度(初始为 n) len-1,每次交换,重新调整待排序队列为最大(小)堆结构,直到待排序数组长度为 0 。
然后,topK的问题也就很简单可以解决了:
第一步:寻找最大的 K 个数,将队首的前 K 个数调整为最小堆(找最小的 K 个数,建最大堆)。
第二步:从下标为 K 的第 K+1 个元素开始,遍历原队列,并和堆顶元素比较,如果小于,则交换,并重新调整为最小堆,直到队列遍历完成,此时前 K 个数,则为最大的 K 。
说明:这样得出的结果,前 K 个数并不一定是有序队列,一般 K 的值远小于 n, 要排序的话,随便用啥排序都很快
C++程序代码如下:
#include "pch.h"
#include <iostream>
using namespace std;
//print
void printArray(int arr[], int n)
{
for (int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
void adjustMaxHeap(int arr[], int n, int father)
{
// 左孩子
int child = 2 * father + 1;
// 左孩子不越界
while (child < n)
{
// 先比较两个孩子的大小, 以数组长度约束确定是否有右孩子
child = (child < n - 1 && arr[child] < arr[child + 1]) ? child + 1 : child;
// 左右孩子大的那个与父节点比较
if (arr[child] > arr[father])
{
swap(arr[child], arr[father]);
father = child;
child = 2 * father + 1;
}
else
{
//父节点不变,立刻结束循环
break;
}
}
}
void adjustMinHeap(int arr[], int n, int father)
{
// 左孩子
int child = 2 * father + 1;
// 左孩子不越界
while (child < n)
{
// 先比较两个孩子的大小, 以数组长度约束确定是否有右孩子
child = (child < n - 1 && arr[child] > arr[child + 1]) ? child + 1 : child;
// 左右孩子小的那个与父节点比较
if (arr[child] < arr[father])
{
swap(arr[child], arr[father]);
father = child;
child = 2 * father + 1;
}
else
{
//父节点不变,立刻结束循环
break;
}
}
}
void topKByHeap(int arr[], int n, int k, int type)
{
if (type) {
// 调整前k个数为最大堆
for (int i = k / 2 - 1; i >= 0; i--)
{
adjustMaxHeap(arr, k, i);
printArray(arr, n);
}
// 从下标为k的第k+1个元素开始遍历
for (int i = k; i < n; i++)
{
//遍历元素与堆顶元素比较
if (arr[i] < arr[0])
{
swap(arr[0], arr[i]);
adjustMaxHeap(arr, k, 0);
}
}
}
else
{
// 调整前k个数为最小堆
for (int i = k / 2 - 1; i >= 0; i--)
{
adjustMinHeap(arr, k, i);
printArray(arr, n);
}
// 从下标为k的第k+1个元素开始遍历
for (int i = k; i < n; i++)
{
//遍历元素与堆顶元素比较
if (arr[i] > arr[0])
{
swap(arr[0], arr[i]);
adjustMinHeap(arr, k, 0);
}
}
}
}
int main()
{
//rand init array
int arr[10];
for (int i = 0; i < 10; i++) arr[i] = i + 1;
for (int i = 1; i < 10; i++) swap(arr[i], arr[rand() % i]);
int n = sizeof(arr) / sizeof(arr[0]);
int type, k;
do{
cout << "Max(0) or Min(1) :";
cin >> type;
cout << "Top K, K is (1-10):";
cin >> k;
k = k > n ? n : k;
cout << "Orignal Array :" << endl;
printArray(arr, n);
topKByHeap(arr, n, k, type);
cout << "Top K:" << endl;
printArray(arr, k);
} while (k);
}
(2)快速排序解法——平均O(N)
第一步:首先,将初始的边界 [left, right](在C++中,使用 pair<int, int> bound (left, right))放置在栈底(声明栈(stack) boundStk)
第二步:栈不为空,栈顶元素出栈用定义好的 pair<int, int> 类型的 bound 接收,将要划分部分的边界 [bound.first, bound.second] 。
在初始情况下,bound.first为 left, bound.second 为right。
第三步:将待划分部分分为两部分:
小于基准值的部分 [bound.first : index - 1]
大于基准值的部分 [index : bound.second]
第四步:判断 K 和 count = index - left(第一部分的总数)的关系。
K == count ,跳出循环,队列前 K 个数就是所要结果
K < count ,对第一部分重复第二、三步,再划分,找到符合条件的“”index 的索引位置
K > count , 对第三部分重复第二、三步, 再划分,找到符合条件的index 的索引位置
说明:调整 index 使得第一部分的数据个数等于 K
注:代码好像有点小问题,但是结果肯定是对的,有时间再更新
#include "pch.h"
#include <iostream>
using namespace std;
//print
void printArray(int arr[], int n)
{
for (int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
int quickHolandFlagMin(int arr[], int left, int right)
{
// 取左边界为基准值
int flag = arr[left];
int j = right + 1;
int index = left;
while (index < j)
{
if (arr[index] < flag)
{
index++;
}
else
{
swap(arr[index], arr[--j]);
}
}
return index;
}
int quickHolandFlagMax(int arr[], int left, int right)
{
// 取左边界为基准值
int flag = arr[left];
int j = right + 1;
int index = left;
while (index < j)
{
if (arr[index] > flag)
{
index++;
}
else
{
swap(arr[index], arr[--j]);
}
}
return index;
}
void topKByQuick(int arr[], int left, int right, int k, int type)
{
stack<pair<int, int>> boundStk;
// 初始边界置于栈底
boundStk.push(make_pair(left, right));
while (!boundStk.empty())
{
pair<int, int> bound = boundStk.top();
boundStk.pop();
int index;
// 判断求top K min or max
if (type)
{
index = quickHolandFlagMin(arr, bound.first, bound.second);
}
else
{
index = quickHolandFlagMax(arr, bound.first, bound.second);
}
/*当第一部分数和 K 相等时,跳出循环
**大于时,在做划分
**小于时,从第二部分添加
*/
int count = index - left;
if (count == k)
{
break;
}
else if (count > k)
{
boundStk.push(make_pair(bound.first, index - 1));
}
else
{
boundStk.push(make_pair(index, bound.second));
}
}
}
int main()
{
//rand init array
int arr[10];
for (int i = 0; i < 10; i++) arr[i] = i + 1;
for (int i = 1; i < 10; i++) swap(arr[i], arr[rand() % i]);
int n = sizeof(arr) / sizeof(arr[0]);
int type, k;
do{
cout << "Max(0) or Min(1) :";
cin >> type;
cout << "Top K, K is (1-10):";
cin >> k;
k = k > n ? n : k;
cout << "Orignal Array :" << endl;
printArray(arr, n);
topKByQuick(arr, 0, n-1, k, 1);
cout << "Top K:" << endl;
printArray(arr, n);
printArray(arr, k);
} while (k);
}
(3)快速优化解法
快排的优化在于基准值的选取
注:有时间在更新
转载链接(luxurylu 原创文章):
https://blog.csdn.net/m0_46216098/article/details/106567812