有一种特殊形式的完全二叉树——堆,它有两种基本形式:最大堆和最小堆。
最大堆:如果一颗完全二叉树的任意一个非终结结点的元素都不小于其左儿子结点和右儿子结点的元素,则称此完全二叉树为最大堆。
最小堆:如果一颗完全二叉树的任意一个非终结结点的元素都不大于其左儿子结点和右儿子结点的元素,则称此完全二叉树为最小堆。
最大堆的根结点中的元素在整个堆中是最大的;而最小堆的根结点中的元素在整个堆中是最小的。
当把最大堆看做抽象数据型时,有以下几个基本操作:
- MaxHeap(heap):创建一个空堆,最多可以容纳MaxSize个元素。
- Insert(heap,item):判断堆是否为满。
- HeapEmpty(heap):插入一个元素。
- DeleteMax(heap):删除最大元素。
由于最大堆是一颗完全二叉树,所以最大堆可以用一维数组来表示。
- 最大堆的类型定义
#define Maxsize 200
typedef struct
{
int key;
/*other fields*/
}Elementtype;
typedef struct
{
Elementtype elements[Maxsize];
int n;
}HEAP;
- 创建一个空堆
初始时,堆的元素个数计数器n为零,
void MaxHeap(HEAP *heap)
{
heap->n=0;
}
- 判断堆是否为空
int HeapEmpty(HEAP heap)
{
return(!heap.n);
}
- 堆中插入一个元素
当要插入一个元素时,为了保持完全二叉树的结构性质,新增结点的编号应为i=n+1。同时,为了保持最大堆的性质,还要把结点i的元素与其父结点的元素进行比较。如果结点i的元素大于其父结点的元素,则将结点i的元素与其父结点的元素进行交换,并令结点i的父结点成为新结点i,继续向上比较,直到结点i的元素不大于其父结点的元素或者i到达根结点为止。
由于堆是一颗完全二叉树,因此具有n个元素的堆,其高度为[log(n+1)]。这就意味着while循环要重复O(logn)次,因此插入函数的时间复杂性为O(logn)。
void Insert(HEAP *heap,Elementtype element)
{
int i;
i=heap->n+1;
while( (i!=1)&&(element.key>heap->elements[i/2].key))
{
heap->elements[i]=heap->elements[i/2];
i/=2;
}
heap->elements[i]=element;
heap->n++;
}
- 堆中删除一个元素
在最大堆中删除最大元素时,一定是从堆的根结点中删除元素。为了在删除结点后保持完全二叉树的结构性质,把最后一个结点(编号最大的叶结点n)的元素暂存在根节点i=1中,此时完全二叉树中元素的个数变为n-1,但可能不符合最大堆的定义。为了保持最大堆的性质,还需要比较结点i的元素预期较大儿子结点的元素。如果结点i的元素小于其较大儿子结点的元素,则将结点i与其较大儿子结点的元素对换,并令结点i的较大儿子结点成为新的结点i,继续向下比较,直到结点i的元素不小于其较大儿子结点的元素或者i到达叶结点为止。
删除操作的时间复杂性为O(logn)。
Elementtype DeleteMax(HEAP *heap)
{
int parent=1,child=2;
Elementtype element,tmp;
if(!HeapEmpty(*heap))
{
element=heap->elements[1];
tmp=heap->elements[heap->n--];
while(child <= heap->n)
{
if((child<heap->n)&&(heap->elements[child].key<heap->elements[child+1].key))
child++;
if(tmp.key >= heap->elements[child].key)
break;
heap->elements[parent]=heap->elements[child];
parent=child;
child*=2;
}
heap->elements[parent]=tmp;
return element;
}
}
- 主函数
int main()
{
int n,i;
HEAP h;
MaxHeap(&h);
printf("请输入要插入的元素个数:(不超过200)");
scanf("%d",&n);
while(n>Maxsize)
{
printf("元素个数超出堆的容量!请重新输入:");
scanf("%d",&n);
}
Elementtype a[n+1],x;
printf("请依次输入每个元素的值:");
for (i = 1; i <= n; i++)
{
scanf("%d",&a[i].key);
Insert(&h, a[i]);
}
while (!HeapEmpty(h))
{
x = DeleteMax(&h);
printf("%d", x.key);
if (!HeapEmpty(h))
printf(",");
}
printf("\n");
return 0;
}
- 测试结果
经常使用堆来实现优先级队列。优先级队列只对最高(或最低)优先级的元素进行删除。但是在任何时候,都可以把任意优先级的元素插入到优先级队列。如果应用程序要求删除最高优先级的元素,就使用最大堆。如果应用程序要求删除最低优先级的元素,则使用最小堆。