欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

复习——Top K问题

程序员文章站 2024-03-07 16:54:27
...

复习——Top K问题
首先,像这种先把数组排序后再找出k个数的暴力方法就不要提了,太low了。
法1:很容易想到,也很简单,直接使用快排中的partition函数即可;
法2:当面对海量数据时,就不能使用快排了,但依然可以使用堆;

法1:快排

class Solution {
public:

	vector<int> getLeastNumbers(vector<int>& arr, int k) {
		int n = arr.size();
		if (n == k) return arr;
		if (n < k || k <= 0 || n == 0) return{};
		int low = 0, high = n - 1;
		int pivotkey = partition(arr, low, high);
		while (pivotkey != k)
		{
			if (pivotkey > k)
				high = pivotkey - 1;
			else
				low = pivotkey + 1;
			pivotkey = partition(arr, low, high);
		}
		return vector<int>(arr.begin(), arr.begin() + k);
	}



	int partition(vector<int>&a, int low, int high)//选第一个元素为枢轴
	{
		while (low < high)
		{
			while (low < high && a[high] >= a[low]) high--;//此时枢轴的下标为low
			swap(a[low], a[high]); //交换后枢轴的下标为high

			while (low < high && a[low] <= a[high]) low++; //此时枢轴的下标为high
			swap(a[low], a[high]);//交换后枢轴的下标为low
		}
		return low;//返回枢轴的下标

	}

};

法2:堆排序,时间复杂度O(nlogk)
这里需要注意的是我们对完全二叉树的结点从0开始编号,所以如果父结点的编号为n,那么它的左孩子结点编号为2n+1,右孩子结点编号为2n+2

//这里需要注意的是我们对完全二叉树结点的编号从0开始,所以如果父结点的编号为n,那么它的左孩子结点编号为2n+1,右孩子结点编号为2n+2
class Solution {
public:
	vector<int> getLeastNumbers(vector<int>& arr, int k) {
		if (k == 0) return vector<int>();
		vector<int> my_max_heap(arr.begin(), arr.begin() + k);//创建一个含有k个元素的大顶堆
		buildMaxHeap(my_max_heap);

		//start
		for (int i = k; i < arr.size(); ++i)
		{
			// 出现比堆顶元素小的值, 置换堆顶元素, 并调整堆
			if (arr[i] < my_max_heap[0])
			{
				replacePeek(my_max_heap, arr[i]);
			}
		}


		return my_max_heap;
	}
private:
	void buildMaxHeap(vector<int>& v)
	{
		//从下往上,从右往左,将每个非叶子结点作为根结点,将其和其子树调整成大顶堆
		for (int i = (v.size() - 2) / 2; i >= 0; --i) {
			HeapAdjust(v, i);
		}
	}

	void HeapAdjust(vector<int>& v, int root_index) {
		int temp = v[root_index];
		int cur_root_index = root_index;

		// 先初始化为左孩子结点索引
		int child_index = 2 * cur_root_index + 1;
		while (child_index < v.size()) {
			// 判断是否存在右孩子, 并选出较大的结点,存入child_index
			if (child_index + 1 < v.size() && v[child_index + 1] > v[child_index]) {
				child_index += 1;
			}
			// 判断父结点和子结点的大小关系
			if (temp >= v[child_index])
				break;
			// 较大结点上浮
			v[cur_root_index] = v[child_index];
			//为下一次循环做准备
			cur_root_index = child_index;
			child_index = 2 * cur_root_index + 1;
		}
		// cur_root_index就是根结点最终下沉到的位置的索引
		v[cur_root_index] = temp;
	}

	void replacePeek(vector<int>& v, int val)
	{
		v[0] = val;
		HeapAdjust(v, 0);
	}
};

这里顺便复习一下,如果对完全二叉树的结点从1开始编号,那么堆排序的代码如下:

#include<iostream>
using namespace std;
void HeapSort(int* a, int num);
void HeapAdjust(int* a, int i, int num);
void swap(int* a, int i, int j)
{
	int temp = a[i];
	a[i] = a[j];
	a[j] = temp;
}
#define Num 9
int main() {
	int a[Num + 1] = { 0,50,10,90,30,70,40,80,60,20 };
	int num = Num;
	HeapSort(a, num);

	for (int i = 1; i <= num; i++)
		cout << a[i] << endl;

	system("pause");
	return 0;
}
void HeapSort(int* a, int num) {
	for (int i = num / 2; i > 0; i--)//第一个循环:将数组a初始化为一个大顶堆
		HeapAdjust(a, i, num);

	for (int i = num; i > 1; i--)//第二个循环:将大顶堆的根结点元素与大顶堆的最后一个结点元素交换,并且再重新调整新的大顶堆(此时注意新的大顶堆的总结点个数要减1)
	{
		swap(a, 1, i);
		HeapAdjust(a, 1, i - 1);
	}
}


void HeapAdjust(int* a, int root_index, int num) {
	int temp = a[root_index];
	int cur_root_index = root_index;

	// 先初始化为左孩子结点索引
	int child_index = 2 * cur_root_index;
	while (child_index <= num) {
		// 判断是否存在右孩子, 并选出较大的结点,存入child_index
		if (child_index + 1 <= num && a[child_index + 1] > a[child_index]) {
			child_index += 1;
		}
		// 判断父结点和子结点的大小关系
		if (temp >= a[child_index])
			break;
		// 较大结点上浮
		a[cur_root_index] = a[child_index];
		//为下一次循环做准备
		cur_root_index = child_index;
		child_index = 2 * cur_root_index;
	}
	// cur_root_index就是根结点最终下沉到的位置的索引
	a[cur_root_index] = temp;

}

上一篇: Top K问题

下一篇: python 清洗数据