目录
1.基本概念
(1)堆的定义
n个元素的序列{arr[1],arr[2],...,arr[n]},当且仅当满足下列关系时,称之为堆:
- arr[i]<=arr[2*i] && arr[i]<=arr[2*i+1] || arr[i]>=arr[2*i] && arr[i]>=arr[2*i+1], i∈[1,n/2]
相应的:
- 当arr[i]<=arr[2*i] && arr[i]<=arr[2*i+1], i∈[1,n/2] 时,称之为小顶堆
- 当arr[i]>=arr[2*i] && arr[i]>=arr[2*i+1], i∈[1,n/2] 时,称之为大顶堆
(2)堆的性质
由定义可知,堆其实就是一棵完全二叉树(顺序表示),故堆满足完全二叉树的所有性质。另外,小顶堆的堆顶元素是序列中的最小值,大顶堆的堆顶元素是序列中的最大值。
(3)堆排序
若在输出堆顶元素之后,使得剩余n-1个元素的序列又重建称一个堆,则可以得到序列中的次小(大)值。如此反复执行,便能得到一个有序序列,这个过程称为堆排序。
2.堆的实现
(1)数据结构
#include <iostream>
#include <vector>
#include <algorithm> // swap
class Heap{
private:
std::vector<int> heap;
public:
Heap(){}; // 初始化空堆
Heap(std::vector<int> &arr); // 由一个无序序列建成堆
void push(int val); // 插入一个结点
void pop(); // 弹出堆顶
int top(); // 返回堆顶
int size(); // 返回结点个数
bool empty(); // 判空
};
(2)建堆
算法一:直接将无序序列建成堆
将无序序列拷贝到heap,然后从最后一个分支结点直至堆顶(heap[n/2],heap[n/2-1],...,heap[1]),对每个分支结点,得到它与所有孩子中的最小(大)值,然后将该分支结点与最小(大)值对应的结点交换;
T(n)=O(n)
S(n)=O(n)
算法二:初始化空堆,然后依次插入无序序列中的元素
T(n)=O(nlogn)(每插入一个元素需要O(logn),下面会讲)
S(n)=O(n)
Heap::Heap(std::vector<int> &arr):heap(arr){
for(int parent=heap.size()/2,child=parent*2; parent>0; parent--,child=2*parent){
if(child+1<heap.size() && heap[child+1]<heap[child]){// 小顶堆,找最小孩子
// if(child+1==heap.size() && heap[child+1]>heap[child]){// 大顶堆,找最大孩子
child++;
}
if(heap[child] < heap[parent]){// 小顶堆,与最小孩子交换
// if(heap[child] < heap[parent]){// 大顶堆,与最大孩子交换
std::swap(heap[child], heap[parent]);
}
}
}
(3)插入一个结点
先将新结点插入heap末尾,然后向上调整堆:
将heap末尾元素视为当前结点,判断它是否有父结点,且当前结点是否小于(大于)父结点。若都满足则交换二者,然后视其父结点为当前结点继续向上判断,直至堆顶或父节点大于(小于)当前结点。
T(n)=O(logn)
S(n)=O(1)
void Heap::push(int val){
heap.push_back(val);
for(int child=heap.size()-1,parent=child/2; parent>0; child=parent,parent/=2){
if(heap[child] < heap[parent]){// 小顶堆,小于父结点要交换
// if(heap[child] > heap[parent]){// 大顶堆,大于父结点要交换
std::swap(heap[child], heap[parent]);
}else{
break;
}
}
}
(4)弹出堆顶
将堆顶与序列末尾元素交换,删除序列末尾元素,然后向下调整堆:
将堆顶视为当前结点,判断它是否有孩子结点,若有则将它与最小(最大)孩子交换,然后视被交换的孩子结点为当前结点继续向下进行该操作,直至叶子结点。
T(n)=O(logn)
S(n)=O(1)
void Heap::pop(){
swap(heap.front(),heap.back());
heap.pop_back();
for(int parent=1,child=2; child<heap.size(); parent=child,child*=2){
if(child+1<heap.size() && heap[child+1]<heap[child]){// 小顶堆,找最小孩子
// if(child+1<heap.size() && heap[child+1]>heap[child]){// 大顶堆,找最大孩子
child++;
}
if(heap[child] < heap[parent]){// 小顶堆,与最小孩子交换
// if(heap[child] > heap[parent]){// 大顶堆,与最大孩子交换
std::swap(heap[child], heap[parent]);
}else{
break;
}
}
}
(5)返回堆顶
int Heap::top(){
return heap.front();
}
(6)其它操作
int Heap::size(){
return heap.size();
}
bool Heap::empty(){
return heap.empty();
}
3.STL——priority_queue
(1)建堆
// 1.小根堆(默认优先级,std::less<T>)
priority_queue<T> heap(); // T(n)=O(1)
priority_queue<T> heap(iter1, iter2); // T(n)=O(n)
// 2.大根堆(std::greater<T>)
priority_queue<T, std::vector<T>, std::greater<T>> heap; // T(n)=O(1)
priority_queue<T, std::vector<T>, std::greater<T>> heap(iter1, iter2); // T(n)=O(n)
// 3.自定义优先级(若a的优先级大于(小于)b,则为小(大)根堆)
priority_queue<T, std::vector<T>, decltype([](auto& a,auto& b){
return bool_expression;
})> heap; // T(n)=O(1)
priority_queue<T, std::vector<T>, decltype([](auto& a,auto& b){
return bool_expression;
})> heap(iter1, iter2); // T(n)=O(n)
(2)操作
// 1.插入一个结点 T(n)=O(logn)
void push(const T& node);
void push(T&& node);
void emplace(Args&&... args);
// 2.弹出堆顶 T(n)=O(logn)
void pop();
// 3.返回堆顶 T(n)=O(1)
const T& top();
// 4.判空 T(n)=O(1)
bool empty() const;
// 5.返回结点数 T(n)=O(1)
size_t size() const;