堆数据结构是一种数组对象,它可以被视为一科完全二叉树结构。
它的特点是父节点的值大于(小于)两个子节点的值(分别称为最大堆和最小堆)。它常用于管理算法执行过程中的信息,应用场景包括堆排序,优先队列等。
1、根结点若有子树,则子树一定也是堆。
2、根结点一定大于(或小于)子结点。
因为要求堆必须是完全二叉树,所以可以用线性的数据结构,比如数组,来实现堆。
利用数组实现,则对于长为N的堆中的元素从0到N-1排列,有:
i的父结点:Parent(i)=(i+1)/2-1
i的左叶子:Left(i)=(i+1)*2-1
i的右叶子:Right(i)=(i+1)*2
堆(Heap),其实也没什么大不了,简单地说就是一种有序队列而已,普通的队列是先入先出,而二叉堆是:最小先出。
这不是很简单么?如果这个队列是用数组实现的话那用打擂台的方式从头到尾找一遍,把最小的拿出来不就行了?行啊,可是出队的操作是很频繁的,而每次都得打一遍擂台,那就低效了,打擂台的时间复杂度为Ο(n),那如何不用从头到尾fetch一遍就出队呢?二叉堆能比较好地解决这个问题,不过之前先介绍一些概念。
完全树(Complete Tree):从下图中看出,在第n层深度被填满之前,不会开始填第n+1层深度,还有一定是从左往右填满。

这样有什么好处呢?好处就是能方便地把指针省略掉,用一个简单的数组来表示一棵树,如图:

那么下面介绍二叉堆:二叉堆是一种完全二叉树,其任意子树的左右节点(如果有的话)的键值一定比根节点大,上图其实就是一个二叉堆。
你一定发觉了,最小的一个元素就是数组第一个元素,那么二叉堆这种有序队列如何入队呢?看图:

假设要在这个二叉堆里入队一个单元,键值为2,那只需在数组末尾加入这个元素,然后尽可能把这个元素往上挪,直到挪不动,经过了这种复杂度为Ο(logn)的操作,二叉堆还是二叉堆。
那如何出队呢?也不难,看图:
将最小的节点删除后,把最后的一个节点放到第一个节点的位置,然后在调整。
最小堆的类模板实现:类接口部分
/////////////////////////
#include <iostream>
using namespace std;
#define DefaultSize 10
template<class T>
class MinHeap //最小堆的类模板实现
{
public:
MinHeap(int sz=DefaultSize);
MinHeap(T arr[], int n);
~MinHeap(){delete []heap;}
bool isEmpty()const{return currentSize==0;}
bool isFull()const{return currentSize==maxHeapSize;}
void makeempty(){currentSize=0;}
bool insert(const T& x); //在数组尾部插入,并调整堆
bool removeMin(T& x); //删除堆顶上的最小元素,最后一个元素补到堆顶,然后调整
private:
T * heap; //采用数组作为其存储方式。
int currentSize;
int maxHeapSize;
void siftDown(int start, int m);//从start到m下滑调整为最小堆
void siftUp(int start); //从start到0上滑调整为最小堆
};具体接口函数实现部分:
template<class T>
MinHeap<T>::MinHeap(int sz=DefaultSize)
{
maxHeapSize=(sz<DefaultSize)?DefaultSize:sz;
heap=new T[maxHeapSize];
if(heap==NULL){cerr<<"err\n";exit(-1);}
currentSize=0;
}
template<class T>
MinHeap<T>::MinHeap(T arr[], int n)
{
maxHeapSize=(n<DefaultSize)?DefaultSize:n;
heap=new T[maxHeapSize];
if(heap==NULL){cerr<<"err\n";exit(-1);}
currentSize=n;
int i=0;
while(i<n){ //copy
heap[i]=arr[i];
++i;
}
int currentPos=(currentSize-2)/2; //找到最初调整位置,最后节点的父节点位置,也就是最后的分支节点
while(currentPos>=0){ //自底向上逐步扩大形成堆
siftDown(currentPos,currentSize-1);
--currentPos;
}
}
template<class T>
void MinHeap<T>::siftDown(int start, int m)//堆的下滑调整算法,从节点start到m为止,从上到下比较,如果子女小于父节点,
{ //关键码上浮 ,据需向下层比教,将局部子树调整为最小堆
int i=start;int j=2*i+1; //i当前子树的根节点,j左子女
T temp=heap[i]; //保存根节点的值
while(j<=m){ //从上向下调整,检查是否到最后位置
if(j<m && heap[j+1]<heap[j]) //j指向两个子女中最小的
++j;
if(temp<=heap[j]) break; //小则不做调整
else{
heap[i]=heap[j];i=j;j=2*i+1;//小者上移,i,j下降
}
}
heap[i]=temp;
}
template<class T>
void MinHeap<T>::siftUp(int start)//新节点插入到最小堆的后面,故需从下到上,与父节点比较,调整
{ //从start开始到0为止,从下向上
int j=start, i=(j-1)/2; //j表示子节点,i表示j的父节点
int temp=heap[j];
while(j>0){
if(heap[i]<=temp)break;
else{
heap[j]=heap[i];j=i;i=(j-1)/2;
}
heap[j]=temp;
}
}
template<class T>
bool MinHeap<T>::insert(const T& x)
{
if(currentSize==maxHeapSize){
cerr<<"full\n";return false;
}
heap[currentSize]=x;
siftUp(currentSize);
++currentSize;
return true;
}
template<class T>
bool MinHeap<T>::removeMin(T& x)
{
if(!currentSize){
cerr<<"empty\n";return false;
}
x=heap[0];
heap[0]=heap[currentSize-1];
--currentSize;
siftDown(0,currentSize-1);
return true;
}
为了简单,验证时把类中private屏蔽掉,以便外面访问;
int main(int argc, char* argv[])
{
int arr[6]={5,4,3,2,1,0};
int i=0;
while(i<sizeof(arr)/sizeof(arr[0])){
cout<<arr[i]<<" ";
++i;
}
cout<<endl;
MinHeap<int> h(arr,sizeof(arr)/sizeof(arr[0]));
i=0;
while(i<6){
cout<<h.heap[i]<<" ";
++i;
}
cout<<endl;
int x=10;
h.insert(x);
i=0;
while(i<7){
cout<<h.heap[i]<<" ";
++i;
}
cout<<endl;
h.removeMin(x);
i=0;
while(i<6){
cout<<h.heap[i]<<" ";
++i;
}
cout<<endl;
system("pause");
return 0;
}5 4 3 2 1 0
0 1 3 2 4 5
0 1 3 2 4 5 10
1 2 3 10 4 5
请按任意键继续. . .
版权声明:本文为【借你一秒】原创文章,转载请标明出处。
原文地址:http://blog.csdn.net/u013467442/article/details/47153781