一、定義
堆(heap),也叫優先隊列(priority queue),取出元素的順序是依照元素的優先權(關鍵字)大小,而不是元素進入隊列的先後順序,也就是說堆的排序是按照自定義的順序(優先權)。
比如說,打印機在打印不同文件時,並不是按照文件的先後順序來打印的,而是由文件是否急需來決定這些文件的打印順序,這樣的模型就是堆模型。
堆最主要的操作有插入(insert)和刪除(deleteMax/deleteMin)兩種操作,堆有多種實現方式,比如說數組或鏈表,也可以採用完全二叉樹存儲結構來實現,完全二叉樹非常適合實現堆,所以提到堆,默認就是樹結構,這也是完全二叉樹的一個應用,所以這裏只講二叉樹實現的C語言代碼。如下圖所示,堆可分爲最大堆和最小堆,最大堆是指子結點的值小於父結點,最小堆是值子結點的值大於父結點,注意,兄弟結點之間沒有優先權排序的關係。
以最大堆爲例,12在第一個位置,11在第二個位置,9在第三個位置……
二、C語言實現
這裏以最大堆的實現爲例。
//頭文件,文件名:MaxHeap.h
#ifndef MAXHEAP_H_INCLUDED
#define MAXHEAP_H_INCLUDED
struct HeapStruct{
//存放堆元素的數組,該數組下標爲0的位置不存放堆元素,僅存放哨兵元素
int* heapArr;
int size;//存放堆的當前元素的數目
int capacity;//存放該堆的容量
};
typedef struct HeapStruct* MaxHeap;
MaxHeap createMaxHeap(int);
void insert(int, MaxHeap);
int find(int, MaxHeap);
#endif // MAXHEAP_H_INCLUDED
//堆的具體實現文件,文件名:MaxHeap.c
#include <stdio.h>
#include <stdlib.h>
#include "MaxHeap.h"
MaxHeap createMaxHeap(int maxSize){
MaxHeap maxHeap = (MaxHeap)malloc(sizeof(struct HeapStruct));
maxHeap->heapArr = (int*)malloc(sizeof(int)*(maxSize+1));
maxHeap->size = 0;
maxHeap->capacity = maxSize;
maxHeap->heapArr[0] = 0;//heapArr數組不存放堆的元素,只存放堆中的最大元素
return maxHeap;
}
/*
查找到元素則返回元素在堆中的位置,否則返回0
*/
int find(int element, MaxHeap maxHeap){
if(!maxHeap){
printf("該堆不存在\n");
return 0;
}
int i = 1;
for(;i<=maxHeap->size;i++){
if(element == maxHeap->heapArr[i]){
return i;
}
}
return 0;
}
/*
朝一個堆裏插入元素,
原理:先在尾部插入元素,然後將該元素與父結點比較,如果父結點小於該元素,則兩個元素互換位置,
這叫採用上濾(percolate up)的方式搜索。
*/
void insert(int element, MaxHeap maxHeap){
if(!maxHeap){
printf("該堆不存在!\n");
return;
}
if(find(element, maxHeap)){
printf("堆中已存在元素%d,不再插入!\n", element);
return;
}
if(maxHeap->size == maxHeap->capacity){
printf("該堆的容量已滿,不能再插入元素了\n");
return;
}else{
/*
因爲這裏可能需要使用哨兵元素來判斷是否結束循環,
所以哨兵元素在插入操作時,必須爲待插入元素與該堆最大元素的較大者
判斷哨兵元素是否更新,更新後可以作爲判斷條件,
但是插入數據後一定要將哨兵元素的值恢復成該堆的最大元素
*/
if(maxHeap->heapArr[0]<element){
maxHeap->heapArr[0] = element;
}
int i = ++maxHeap->size;
for(;maxHeap->heapArr[i/2]<element;i/=2){
maxHeap->heapArr[i] = maxHeap->heapArr[i/2];
}
maxHeap->heapArr[i] = element;
//最後復原哨兵元素
maxHeap->heapArr[0] = maxHeap->heapArr[1];
}
}
/*
刪除並返回最大堆裏的最大的元素
原理:最大堆的最大元素爲根結點上的元素,也就是說,等價於給根結點位置重新找子樹中最大的元素,合併左右兩顆最大堆。
先刪除最後一個結點n(值替換根結點的值),從根開始找出當前結點下較大的子結點,
然後用這個子結點的值與結點n的值比較,
如果大於結點n的值,則這個子結點的值移動到父結點的位置,該子節點的位置作爲下一個父結點,繼續向下查找;
如果小於結點n的值,則停止向下搜索。
這叫採用下濾(percolate down)的方式搜索。
*/
int deleteMax(MaxHeap maxHeap){
int parent, child;//父結點和子結點的位置
if(maxHeap->size == 0){
printf("該最大堆已經不含有結點\n");
return 0;
}
//初始堆裏的最大值
int maxItem = maxHeap->heapArr[1];
//將初始堆的最後一個元素賦值給根結點,並且刪除最後一個結點
maxHeap->heapArr[1] = maxHeap->heapArr[maxHeap->size--];
int temp = maxHeap->heapArr[1];
//開始循環比較父結點與子結點的大小
//parent*2<=maxHeap->size判斷是否有子結點
for(parent=1; parent*2 <= maxHeap->size; parent=child){
child = parent*2;//左子結點的位置
if((child!=maxHeap->size)&&(maxHeap->heapArr[child]<maxHeap->heapArr[child+1])){
//child!=maxHeap->size成立的話,表明該parent結點有右子結點
//maxHeap->heapArr[child]<maxHeap->heapArr[child+1]判斷左右子結點的大小
//如果if條件成立,那麼parent的右結點比左結點大,此時child指向右結點的位置
child++;
}
if(maxHeap->heapArr[child] > temp){
maxHeap->heapArr[parent] = maxHeap->heapArr[child];
}else{
break;
}
}
maxHeap->heapArr[parent] = temp;
//修改哨兵
maxHeap->heapArr[0] = maxHeap->heapArr[1];
return maxItem;
}
/*
根據一個數組直接建立堆
而不是一個元素一個元素的插入
原理:先無序將這個元素放入到堆裏,
然後按照刪除元素的原理(下濾)從第一個含非空子結點(位置:最後一個葉結點的位置/2取整)的元素開始調整
*/
void createMaxHeapByArray(int arr[], int length, MaxHeap maxHeap){
//MaxHeap maxHeap = createMaxHeap(length);
/*
因爲這裏數組裏的元素並未放到maxHeap->heapArr裏面
所以需要把這些元素放進去
其實這裏不需要另一個數組,這裏爲了方便理解由數組建堆的原理,
所以另加了一個數組。
*/
maxHeap->size = length;
//將數組元素依次放入完全二叉樹裏
for(int j=0; j<length; j++){
maxHeap->heapArr[j+1] = arr[j];
}
//開始調整
int i;
for(i = maxHeap->size/2; i>0; i--){
int parent, child, element;
element = maxHeap->heapArr[i];
for(parent = i; parent*2<=maxHeap->size; parent=child){
child = parent*2;
if((child!=maxHeap->size)&&(maxHeap->heapArr[child]<maxHeap->heapArr[child+1])){
child++;
}
if(element >= maxHeap->heapArr[child]){
break;
}else{
maxHeap->heapArr[parent] = maxHeap->heapArr[child];
}
}
maxHeap->heapArr[parent] = element;
}
maxHeap->heapArr[0] = maxHeap->heapArr[1];
}
//測試文件,文件名:main.c
#include "MaxHeap.h"
#include <stdio.h>
#include <stdlib.h>
int main()
{
MaxHeap maxHeap = createMaxHeap(4);
insert(15, maxHeap);
insert(7, maxHeap);
insert(9, maxHeap);
insert(20, maxHeap);
//查找元素7
int position = find(7,maxHeap);
printf("7在第%d個位置\n",position);
//查找不存在的元素21
int pos = find(21,maxHeap);
printf("21在第%d個位置\n",pos);
printf("\n");
for(int i=1;i<=maxHeap->size;i++){
printf("第%d元素爲:%d\n", i, maxHeap->heapArr[i]);
}
printf("哨兵爲:%d\n", maxHeap->heapArr[0]);
printf("\n");
//重複插入9
insert(9, maxHeap);
printf("\n");
//測試刪除最大元素
int maxItem = deleteMax(maxHeap);
printf("刪除的最大元素爲:%d\n", maxItem);
printf("刪除之後的堆爲:\n");
for(int i=1;i<=maxHeap->size;i++){
printf("第%d元素爲-----%d\n", i, maxHeap->heapArr[i]);
}
printf("哨兵爲-----%d\n", maxHeap->heapArr[0]);
//測試直接生成最大堆
printf("\n");
printf("根據數組直接生成最大堆\n");
int arr[5] = {1,2,3,4,5};
MaxHeap newMaxHeap = createMaxHeap(5);
createMaxHeapByArray(arr, 5, newMaxHeap);
for(int m = 1; m<=newMaxHeap->size; m++){
printf("第%d元素爲:%d\n", m, newMaxHeap->heapArr[m]);
}
return 0;
}
//結果:
//7在第4個位置
//21在第0個位置
//第1元素爲:20
//第2元素爲:15
//第3元素爲:9
//第4元素爲:7
//哨兵爲:20
//堆中已存在元素9,不再插入!
//刪除的最大元素爲:20
//刪除之後的堆爲:
//第1元素爲-----15
//第2元素爲-----7
//第3元素爲-----9
//哨兵爲-----15
//根據數組直接生成最大堆
//第1元素爲:5
//第2元素爲:4
//第3元素爲:3
//第4元素爲:1
//第5元素爲:2