TopK问题求解(堆排、快排、快排优化(bfprt算法求中位数))

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]

第四步:判断 Kcount = 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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值