堆的简介
1.堆是一种完全二叉树(不是平衡二叉树,也不是二分搜索)。
2.堆要求父亲节点大于左右子节点。
如图1就是一个最大堆,而图二,图三就不是堆,因为图2不满足第二个条件。图三两个条件均不满足。
堆的存储结构
由于堆是一颗完全二叉树,所以我们可以通过数组的方式去存储它。
假设根节点在数组的0 号位置,依次将节点编号。我们可以发现以下规律
对于任意的节点K 如果其存在左右子树则必然存在的关系为:
对于该节点的左子树可表示为:left = 2k +1
对于该节点的右子树可表示为:left = 2k +2
对于该节点的父节点可表示为: parent = (k-1)/2
所以我们可以设置以下方法求解左右子树,以及父亲节点
//获取父节点
int parent(int index){
if(index==0){
cout<<" Index-0 doesn't have parent"<<endl;
return -1;
}
return (index-1)/2;
}
//左子树
int left(int index){
return index*2+1;
}
//右子树
int right(int index){
return index*2+2;
}
堆的插入
由于底层实现容器使用的是vector,那么vector在最后的插入以及删除的效率是最高的,所以堆的插入一般在最后插入一个元素。但是我们要保证堆的性质,所以我们要去维护这个插入。这个时候我们用到一个上浮的操作。
1.从最后一个元素开始,寻找其父节点,如果父节点小于当前元素则上浮。否则结束compare(k,parent(k))
2.更新需要上浮的位置k=parent(k)
如图所以插入元素15,我们需要和父节点9比较,如果比9大则交换,然后继续往上冒泡。
上浮的代码如下
//插入元素后上浮到合适的位置
void siftUp(int k){
while (k>0&&parent(k)>=0&&compare(container[k],container[parent(k)]))
{
/* code */
swap(container[parent(k)],container[k]);
k=parent(k);
}
}
堆的删除
堆在删除的时候一般先把堆顶元素和堆底元素交换,然后把堆底的元素删除,这个时候再把堆顶的元素下沉到堆的相应位置。
交换位置
如图10,堆顶的9元素会分别和其左右孩子节点进行比较,选出较大的孩子节点和其进行交换。很明显右孩子17大于左孩子15。即和右孩子进行交换。
下沉的代码如下
//删除元素后重新构建树的结构
void siftDown(int k){
//直到左子树不为空
while(left(k)<container.size()){
int j = left(k);
//如果该节点还有右孩子而且右孩子大于左孩子那么将右孩子与该节点互换,选出左右孩子中最大的孩子
if(j+1<container.size()&&compare(container[j+1],container[j])){
j=right(k);
}
//如果说根节点大于左右孩子则停止
if(compare(container[k],container[j]))
break;
swap(container[j],container[k]);
k=j;
}
}
堆排序
对于堆排序其实我们可以先创建一个堆,然后把元素逐一的push进去,然后再逐一的pop出来,我们即可完成排序:
priority_queue<int> a;
a.push(18);
a.push(14);
a.push(17);
a.push(8);
a.push(9);
a.push(12);
a.push(11);
a.push(1);
a.push(7);
a.push(6);
a.push(15);
while (!a.empty())
{
/* code */
cout<<a.top()<<" ";
a.pop();
}
堆的建立是log(n)的,元素个数是n所以整个复杂度是nlogn。
完整代码
整个代码模仿C++中的priority_queue,默认情况下是大顶堆,当然也可以根据自己的情况传入自己的函数指针进行个性化的设计。
#include <iostream>
#include <vector>
using namespace std;
template <class T>
bool myCompare(T a,T b){
return a>b;
}
template <class T>
class priority_queue{
public:
//priority_queue():_size(0){}
//默认使用大顶堆
priority_queue(bool (*compare2)(T,T)=myCompare<T>):compare(compare2),_size(0){ }
T top(){
if(container.empty()){
return -1;
}
return container[0];
}
void push(T e){
container.push_back(e);
siftUp(container.size()-1);
_size++;
}
void pop(){
if(container.empty()){
return;
}
swap(container[0],container[container.size()-1]);//第一个和最后一个交换
container.pop_back();//删除最后一个
//重新维护
siftDown(0);
_size--;
}
int size(){
return container.size();
}
bool empty(){
return container.empty();
}
private:
vector<int> container; //容器
int _size;
bool (*compare)(T,T); //比较器
private:
void swap(T& a,T& b){
T temp=a;
a = b;
b = temp;
}
//获取父节点
int parent(int index){
if(index==0){
cout<<" Index-0 doesn't have parent"<<endl;
return -1;
}
return (index-1)/2;
}
//左子树
int left(int index){
return index*2+1;
}
//右子树
int right(int index){
return index*2+2;
}
//插入元素后把最大的元素上浮到第一个
void siftUp(int k){
while (k>0&&parent(k)>=0&&compare(container[k],container[parent(k)]))
{
/* code */
swap(container[parent(k)],container[k]);
k=parent(k);
}
}
//删除元素后重新构建树的结构
void siftDown(int k){
//直到左子树不为空
while(left(k)<container.size()){
int j = left(k);
//如果该节点还有右孩子而且右孩子大于左孩子那么将右孩子与该节点互换,选出左右孩子中最大的孩子
if(j+1<container.size()&&compare(container[j+1],container[j])){
j=right(k);
}
//如果说根节点大于左右孩子则停止
if(compare(container[k],container[j]))
break;
swap(container[j],container[k]);
k=j;
}
}
};
template<class T>
bool compare2(T a,T b){
return a<b;
}
int main(){
priority_queue<int> a(compare2<int>);
a.push(18);
a.push(14);
a.push(17);
a.push(8);
a.push(9);
a.push(12);
a.push(11);
a.push(1);
a.push(7);
a.push(6);
a.push(15);
while (!a.empty())
{
/* code */
cout<<a.top()<<" ";
a.pop();
}
}