复习——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;

}
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读