首先引进一个概念:优先队列(Priority Queue):特殊的“队列”,取出元素的顺序是依照元素的优先权(关键字)大小,而不是元素进入队列的先后顺序。
可以考虑利用一般的数组、链表,有序数组、链表或者是二叉搜索树、AVL树等进行实现但是都会出现各种各样的问题,以下是对复杂度的分析
如果用搜索二叉树,则很可能总是从一边删除,过若干次树就会变得很不均匀,最后我们想到了利用优先队列的完全二叉树表示
堆具有两个特性
结构性:用数组表示的完全二叉树
有序性:任一结点的关键字是其子树所有结点的最大值(或最小值)
又分为
最大堆(MaxHeap),也称“大顶堆”:最大值
最小堆(MinHeap),也称“小顶堆”:最小值
下面举一下堆的例子
堆的抽象数据类型描述
类型名称:最大堆(MaxHeap)
数据对象集:完全二叉树,每个结点的元素值不小于其子结点的元素值
操作集:最大堆H∈MaxHeap,元素item∈ElementType,主要操作有:
- MaxHeap Create(int MaxSize):创建一个空的最大堆
- Boolean IsFull(MaxHeap H):判断最大堆H是否已满
- Insert(MaxHeap H, ElementType item):将元素item插入最大堆H
- Boolean IsEmpty(MaxHeap H):判断最大堆H是否为空
- ElementType DeleteMax(MaxHeap H):返回H中最大元素(高优先级)
下面讲逐一进行实现
最大堆的插入
在最大堆的插入过程中,首先将元素插入最后一个位置,之后与其父节点进行对比,如果其父节点比其小,交换两个结点的值其时间复杂性是O(logN),注意这里H->Elements[0]的用法,是哨兵元素,控制循环的进行
//最大堆的插入
void Insert(MaxHeap &H,ElementType item)
{//将元素item插入最大堆H,其中H->Elements[0]已经定义为哨兵
int i;
if(IsFull(H)){
printf("最大堆已满\n");
return;
}
i=++H->Size;//先Size自加1再赋给i,i指向插入后堆中的最后一个元素
for(;H->Elements[i/2]<item;i/=2)
H->Elements[i]=H->Elements[i/2];//向下过滤结点
//哨兵的作用就是避免插入的值比Elements[0]还大
H->Elements[i]=item;//将item插入,运行速度比交换快
//时间复杂性是O(logN)
};
函数如上
最大堆的删除
删除时是先记录根节点的大小,之后把最后的一个元素作为根节点,然后与左右两个子结点比较,选择比较大的子结点与根节点进行交换,最后返回根节点,具体的实现函数如下
//从堆中删除一个元素
ElementType DeleteMax(MaxHeap H){//从最大堆H中取出键值最大的元素,并删除一个结点
int Parent,Child;
ElementType MaxItem,temp;
if(IsEmpty(H)){
printf("最大堆已为空\n");
return ERROR;
}
MaxItem=H->Elements[1];//取出根节点(最大值)
//用最大堆中的最后一个元素从根节点开始向上过滤下层结点
temp=H->Elements[H->Size--];
for(Parent=1;Parent*2<=H->Size;Parent=Child){
Child=Parent*2;
if((Child!=H->Size)&&(H->Elements[Child]<H->Elements[Child+1]))
Child++;//Child指向左右子结点中较大的
if(temp>=H->Elements[Child])break;
else//移动temp到下一层
H->Elements[Parent]=H->Elements[Child];
}
H->Elements[Parent]=temp;
return MaxItem;
}
最大堆的建立
建立最大堆:将已经存在的N个元素按最大堆的要求存放在一个一维数组中
方法1:通过插入操作,将N个元素一个个相继插入到一个初始为空的堆中去,其时间代价最大为O(NlogN)
方法2:在线性时间复杂度下建立最大堆
(1)将N个玉努斯按输入顺序存入,先满足完全二叉树的结构特性
(2)调整各结点位置,以满足最大堆的有序特性
其线性时间复杂度由以下图片说明来证明
下面是函数的具体实现方法
/*----------- 建造最大堆 -----------*/
void PercDown( MaxHeap H, int p )
{ /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */
int Parent, Child;
ElementType X;
X = H->Elements[p]; /* 取出根结点存放的值 */
for( Parent=p; Parent*2<=H->Size; Parent=Child ) {
Child = Parent * 2;
if( (Child!=H->Size) && (H->Elements[Child]<H->Elements[Child+1]) )
Child++; /* Child指向左右子结点的较大者 */
if( X >= H->Elements[Child] ) break; /* 找到了合适位置 */
else /* 下滤X */
H->Elements[Parent] = H->Elements[Child];
}
H->Elements[Parent] = X;
}
void BuildHeap( MaxHeap H )
{ /* 调整H->Data[]中的元素,使满足最大堆的有序性 */
/* 这里假设所有H->Size个元素已经存在H->Data[]中 */
int i;
/* 从最后一个结点的父节点开始,到根结点1 */
for( i = H->Size/2; i>0; i-- )
PercDown( H, i );
}
来看一下代码的执行结果
最后附上完整代码
#include <stdio.h>
#include <stdlib.h>
#define MaxData 1000
#define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */
typedef int ElementType;
typedef struct HeapStruct *MaxHeap;
struct HeapStruct{
ElementType *Elements;//储存堆元素的数组
int Size;//堆的当前元素个数
int Capacity;//堆的最大容量
};
//创建容量为MaxSize的空的最大堆
MaxHeap Create(int MaxSize);
//判断最大堆是否已满
int IsFull(MaxHeap H);
//最大堆的插入
void Insert(MaxHeap &H,ElementType item);
//判断最大堆是否为空
int IsEmpty(MaxHeap H);
//从堆中删除一个元素
ElementType DeleteMax(MaxHeap H);
//建造最大堆
void PercDown( MaxHeap H, int p );
void BuildHeap( MaxHeap H );
//打印堆中元素
void Print(MaxHeap H);
int main(){
MaxHeap H= Create(15);
Insert(H,55);
Insert(H,79);
Insert(H,66);
Insert(H,83);
Insert(H,72);
Insert(H,30);
Insert(H,49);
Insert(H,91);
Insert(H,87);
Insert(H,43);
Insert(H,9);
Insert(H,38);
Print(H);
DeleteMax(H);
DeleteMax(H);
Print(H);
printf("\n");
MaxHeap HH= Create(15);
int a[]={55,79,66,83,72,30,49,91,87,43,9,38};
int len=sizeof(a)/sizeof(int);
HH->Size=len;
for(int i=0;i<len;i++)
HH->Elements[i+1]=a[i];
Print(HH);
BuildHeap(HH);
Print(HH);
system("pause");
}
//创建容量为MaxSize的空的最大堆
MaxHeap Create(int MaxSize){
MaxHeap H=(MaxHeap)malloc(sizeof(struct HeapStruct));
H->Elements=(ElementType *)malloc((MaxSize+1)*sizeof(ElementType));//从小标为1的地方开始存放
H->Size=0;
H->Capacity=MaxSize;
H->Elements[0]=MaxData;//定义“哨兵”为大于堆中所有可能元素的值,便于以后更快操作
return H;
//时间复杂性是O(logN)
};
//判断最大堆是否已满
int IsFull(MaxHeap H){
return(H->Size==H->Capacity);
};
//最大堆的插入
void Insert(MaxHeap &H,ElementType item)
{//将元素item插入最大堆H,其中H->Elements[0]已经定义为哨兵
int i;
if(IsFull(H)){
printf("最大堆已满\n");
return;
}
i=++H->Size;//先Size自加1再赋给i,i指向插入后堆中的最后一个元素
for(;H->Elements[i/2]<item;i/=2)
H->Elements[i]=H->Elements[i/2];//向下过滤结点
//哨兵的作用就是避免插入的值比Elements[0]还大
H->Elements[i]=item;//将item插入,运行速度比交换快
//时间复杂性是O(logN)
};
//判断最大堆是否为空
int IsEmpty(MaxHeap H){
return(H->Size==0);
};
#define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */
//从堆中删除一个元素
ElementType DeleteMax(MaxHeap H){//从最大堆H中取出键值最大的元素,并删除一个结点
int Parent,Child;
ElementType MaxItem,temp;
if(IsEmpty(H)){
printf("最大堆已为空\n");
return ERROR;
}
MaxItem=H->Elements[1];//取出根节点(最大值)
//用最大堆中的最后一个元素从根节点开始向上过滤下层结点
temp=H->Elements[H->Size--];
for(Parent=1;Parent*2<=H->Size;Parent=Child){
Child=Parent*2;
if((Child!=H->Size)&&(H->Elements[Child]<H->Elements[Child+1]))
Child++;//Child指向左右子结点中较大的
if(temp>=H->Elements[Child])break;
else//移动temp到下一层
H->Elements[Parent]=H->Elements[Child];
}
H->Elements[Parent]=temp;
return MaxItem;
}
/*----------- 建造最大堆 -----------*/
void PercDown( MaxHeap H, int p )
{ /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */
int Parent, Child;
ElementType X;
X = H->Elements[p]; /* 取出根结点存放的值 */
for( Parent=p; Parent*2<=H->Size; Parent=Child ) {
Child = Parent * 2;
if( (Child!=H->Size) && (H->Elements[Child]<H->Elements[Child+1]) )
Child++; /* Child指向左右子结点的较大者 */
if( X >= H->Elements[Child] ) break; /* 找到了合适位置 */
else /* 下滤X */
H->Elements[Parent] = H->Elements[Child];
}
H->Elements[Parent] = X;
}
void BuildHeap( MaxHeap H )
{ /* 调整H->Data[]中的元素,使满足最大堆的有序性 */
/* 这里假设所有H->Size个元素已经存在H->Data[]中 */
int i;
/* 从最后一个结点的父节点开始,到根结点1 */
for( i = H->Size/2; i>0; i-- )
PercDown( H, i );
}
//打印堆中元素
void Print(MaxHeap H){
printf("H中的元素为:\n");
for(int i=1;i<=H->Size;i++)
printf("%d ",H->Elements[i]);
printf("\n");
}
done,继续做下去√