复习——Top K问题
程序员文章站
2024-03-07 16:54:27
...
首先,像这种先把数组排序后再找出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 清洗数据