前言:
复习堆排序,内容包括:堆排序的基本内容、TOPK问题。
一、堆排序的原理:
1.堆的定义:
首先我们解释一下完全二叉树的概念:
在逻辑结构上它满足这样两个性质:①出最后一层都是满的②且最后一层从右向左依次填满;由于这样的性质,我们可以不使用传统的链式结构,我们可以选择使用数组来存放其结构(因为它对数组的利用率为100%)。如下示意图:
我们以层序遍历二叉树的顺序将结点的value存放到数组之中。
此外根据这个结构我们可以得到一个重要的性质:
对于一个结点而言:
当它在数组中的位置为i;
其左孩子在数组中为2*i+1,其右孩子为2*i+2.
2.大根堆的概念:
在整个堆中最大的元素位于根节点,其中每个结点的值都大于其子结点的值(这个与BST有区别,不要混淆)。
3.维护堆的性质:
由于我们要实现大根堆我们必须在时刻维护其性质:
我们直接给出代码:
void heapify(int arr[], int n, int i) {//其中arr是用来存放堆的数组,其大小为n,i为需要维护的结点
if (i >= n) { return; }
int largest = i;//在父,左孩子,右孩子三个节点中最大的结点在数组的位置,开始默认为父节点。
int leftson = 2 * i + 1, rightson = 2 * i + 2;
if (arr[largest] < arr[leftson] && i < n) {//先与左孩子相比较
largest = leftson;
}
if (arr[largest] < arr[rightson] && i < n) {//再令max{父亲结点,左孩子}与右节点比较
largest = rightson;
}
if (i != largest) {//此时largest是最大结点的位置
swap(arr[i], arr[largest]);//交换位置
heapify(arr, n, largest);//最后再递归调用即可
}
}
4.实现堆排序:
分为两个步骤:
第一步建堆:首先我们要找到最后一个结点,并且标记。随后从该节点的父亲结点出发向前遍历终点为头结点:且在每一个遍历中进行一次heapify。
第二步排序:由于每一刻位置arr[0]中存放的数据是最大数据,我们可以令无序序列的最后于一个元素与之交换位置。再对arr[0](交换过以后的位置)进行维护堆的性质即可。
void heap_sort(int arr[], int n) {
int lastnode = n - 1;
int lastnodefather = (lastnode - 1) / 2;
for (int k = lastnodefather; k >= 0; k--) {
heapify(arr, n, k);
}//建堆
for (int i = n - 1; i >= 0; i--) {
swap(arr[0], arr[i]);
heapify(arr, n, i);
}//排序
}
5.代码实现:
#include<iostream>
using namespace std;
void heapify(int arr[], int n, int i) {//其中arr是用来存放堆的数组,其大小为n,i为需要维护的结点
if (i >= n) { return; }
int largest = i;//在父,左孩子,右孩子三个节点中最大的结点在数组的位置,开始默认为父节点。
int leftson = 2 * i + 1, rightson = 2 * i + 2;
if (arr[largest] < arr[leftson] && i < n) {//先与左孩子相比较
largest = leftson;
}
if (arr[largest] < arr[rightson] && i < n) {//再令max{父亲结点,左孩子}与右节点比较
largest = rightson;
}
if (i != largest) {//此时largest是最大结点的位置
swap(arr[i], arr[largest]);//交换位置
heapify(arr, n, largest);//最后再递归调用即可
}
}
void heap_sort(int arr[], int n) {
int lastnode = n - 1;
int lastnodefather = (lastnode - 1) / 2;
for (int k = lastnodefather; k >= 0; k--) {
heapify(arr, n, k);
}//建堆
for (int i = n - 1; i >= 0; i--) {
swap(arr[0], arr[i]);
heapify(arr, n, i);
}//排序
}
void printArray(int arr[], int n) {
for (int i = 0; i < n; ++i) {
cout << arr[i] << " ";
}
cout << "\n";
}
int main() {
int arr[] = { 12, 11, 13, 5, 6, 7 };
int n = sizeof(arr) / sizeof(arr[0]);
cout << "Unsorted array: \n";
printArray(arr, n);
heap_sort(arr, n);
cout << "Sorted array: \n";
printArray(arr, n);
return 0;
}
二、TOPK问题:
1.代码实现:
简单来讲就是从给定的序列中找出最大的前K个数据,显然我们使用堆排序可以轻松解决这个问题下面直接给出解决问题的代码
#include <iostream>
#include <vector>
using namespace std;
void heapify(vector<int>& heap, int n, int i) {
int smallest = i;
int leftson = 2 * i + 1;
int rightson = 2 * i + 2;
if (leftson < n && heap[leftson] < heap[smallest]) {
smallest = leftson;
}
if (rightson < n && heap[rightson] < heap[smallest]) {
smallest = rightson;
}
if (i != smallest) {
swap(heap[i], heap[smallest]);
heapify(heap, n, smallest);
}
}
void buildHeap(vector<int>& heap, int k) {
for (int i = k / 2 - 1; i >= 0; i--) {
heapify(heap, k, i);
}
}
vector<int> findTopK(int arr[], int n, int k) {
vector<int> heap(arr, arr + k); // Initialize the heap with the first k elements
buildHeap(heap, k); // Build a min-heap of size k
for (int i = k; i < n; i++) {
if (arr[i] > heap[0]) {
heap[0] = arr[i];
heapify(heap, k, 0); // Restore the heap property
}
}
return heap;
}
int main() {
int arr[] = {3, 2, 1, 5, 6, 4};
int n = sizeof(arr) / sizeof(arr[0]);
int k = 3;
vector<int> topK = findTopK(arr, n, k);
cout << "Top " << k << " elements: ";
for (int i = 0; i < k; i++) {
cout << topK[i] << " ";
}
cout << endl;
return 0;
}
思考:对于该问题我们已经获得了O(NlogN)的时间复杂度了,是否我们可以更近一步减少时间开销呢?(BFPRT算法)