什么是堆?
是一种特殊的“队列”,取出元素的顺序依照元素的优先权(关键字)大小,而不是元素进入队列的先后顺序。(可以用STL库中的优先队列代替,下面也是其实现原理,虽然可以直接用优先队列,但原理也很重要)
堆的特性:
(1)结构特性:用数组表示的完全二叉树;
(2)有序性:任一结点的关键字是其子树所有结点的最大值或最小值(对应最大堆、最小堆)。
故对堆进行操作时一定不能破坏这两特性。
堆的基本操作:
#define MaxData 。。。//建议设一个堆的最大元素值作为哨兵,在对堆进行操作时你会发现很方便。当然只是建议。
typedef struct node *Heap;
struct node{
ElementType *Elements;//储存堆元素的指针
int size;//当前堆元素个数
int Capacity;// 堆容量
};
(1)堆的建立:
Heap CreateMaxHeap(int maxsize)
{
Heap H=new node;
H->Elements=new ElementType[maxsize+1];
H->size=0;
H->Capacity=maxsize;
H->Elements[0]=MaxData;//设一个堆的最大元素值放在0位置上
return H;
}
下面的操作均已最大堆为例,其实最小堆就是小的节点优先替换父节点,看了下面的自己模仿实现。
(2)判空判满:
bool IsFull( MaxHeap H )
{
return (H->Size == H->Capacity);
}
bool IsEmpty( MaxHeap H )
{
return (H->Size == 0);
}
在下面的函数中,因为**有了哨兵H->Elements[0],不用加i>1的循环条件,左子节点就是父节点的序号的2倍!!!**不过最重要的是自己画图写代码体会!!!
(3)最大堆的插入:
前面已经说了,对堆进行操作时一定不能破坏其特性。所以我们在插入时先将要插入的元素作为最后一个叶子结点,先满足结构特性,在进行调整满足有序性。
bool Insert( Heap H, ElementType X )
{ /* 将元素X插入最大堆H,其中H->Data[0]已经定义为哨兵 */
int i;
if ( IsFull(H) ) {
printf("最大堆已满");
return false;
}
i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */
for ( ; H->Data[i/2] < X; i/=2 )
H->Data[i] = H->Data[i/2]; /* 上滤X */
H->Data[i] = X; /* 将X插入 */
return true;
}
(4)删除最大堆顶元素(根节点):
前面说到堆是一种特殊的“队列”,取出元素的顺序依照元素的优先权(关键字)大小,所以删除操作其实是删除根节点。我们的做法是让最后一个叶子结点代替根节点,先满足结构特性,在进行调整满足有序性。
#define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */
ElementType PercDown(Heap H)//返回根节点
//把根节点为H->Elements[p]的子堆调整为最大堆。
{
if ( IsEmpty(H) ) {
printf("最大堆已为空");
return ERROR;
}
int child,parent;
ElementType MAX=H->Elements[1];//根节点
ElementType tmp=H->Elements[H->size--];//--是因为删除了根节点。
for(parent=p;parent*2<=H->size;parent=child){
child=parent*2;//得到左子节点序号
if( child!=H->size && H->Elements[child] < H->Elements[child+1] )
//取左右子节点中大的那个(毕竟要让最大的在上面)
child+=1;
if(tmp>=H->Elements[child]) break;
else //如果子节点大于父节点就要让子节点代替父节点
H->Elements[parent]=H->Elements[child];
}
H->Elements[parent]=tmp;
return MAX;
}
建立最大堆:
显然根据树中的经验,对于一个含有N个元素的序列,我们有两种方式建立最大堆,第一种是一个个用Insert函数插入,第二种是先按照序列中的顺序建立一个完全二叉树满足结构特性,再调整各节点位置来满足有序性。
第一种方法很飘,因为我们不知道这个序列的顺序无法控制时间复杂度,如果这个序列是按最小堆排序的话(最坏情况)我们的时间复杂度将是O(NlogN);
第二种方法我们把两个特性分开实现,可以在线性时间复杂度实现建立最大堆,所以我们用第二种方法。调整的操作和删除类似。
int N,data[N];//这样定义全局变量不对,只是用于代表序列
Heap CreateMaxHeap(//参数根据需要写)//要对上面的建堆函数做一些小修改,其实本就应灵活变通
{
Heap H=new node;
H->Elements=new ElementType[N+1];
for(int i=1;i<=N;i++){
H->Elements[i]=data[i-1];//data是从0到n-1。
}
H->size=0;
H->Capacity=N;
H->Elements[0]=MaxData;//设一个堆的最大元素值放在0位置上
return H;
}
void PercDown(Heap H,int p)//下滤
//把根节点为H->Elements[p]的子堆调整为最大堆。
{
int child,parent;
ElementType tmp=H->Elements[p];//用tmp把子堆的根节点存起来
for(parent=p;parent*2<=H->size;parent=child){
child=parent*2;//得到左子节点序号
if( child!=H->size && H->Elements[child] < H->Elements[child+1] )
//取左右子节点中大的那个(毕竟要让最大的在上面)
child+=1;
if(tmp>=H->Elements[child]) break;
else //如果子节点大于父节点就要让子节点代替父节点
H->Elements[parent]=H->Elements[child];
}
H->Elements[parent]=tmp;
}
void BuildMaxHeap(Heap H)
{//从最后一个父节点开始下滤,方便的一批
for(int i=H->size/2;i>=1;i--)
PercDown(H,i);
}
代码中有很多小细节,还需要慢慢体会其中的一些用意。