歡迎關注我,學習資料免費分享給你哦!還有其他超多學習資源,都是我自己學習過的,經過過濾之後的資源,免去你還在因爲擁有大量資源不知如何入手的糾結,讓你體系化學習。
二叉堆
二叉堆是一棵完全二叉樹,什麼是完全二叉樹呢?簡單來說,就是按照層的順序,對樹的節點標號,然後按照層次遍歷的順序來遍歷,得到的結果是按照順序來標號的,不能出現斷點,這就是一個完全二叉樹。這麼說比較抽象,舉個例子來說。
這個二叉樹的層次遍歷結果是0,1,2,3,4,5,6,7,8.所以是一個完全二叉樹。
這裏這棵二叉樹進行層次遍歷,得到就是0,1,2,3,4,5,6,7,10.編號不是連續的,就不是一棵完全二叉樹。
堆就是一棵完全二叉樹,不過因爲完全二叉樹的特性,在實現它的時候往往不是使用鏈表,而是使用數組來實現,我們可以把上面的二叉樹的節點標號與數組的下標對應起來。二叉樹的根節點標號從1開始,存儲的數組也從下標1開始存儲二叉樹,一一對應,這種情況下,父節點的數組下標爲i,則左子樹的數組下標爲2*i,右子樹的下標爲2*i+1(如果沒有超出數組的界限的情況下)
上圖的二叉樹將被存儲爲下面的數組
在程序裏就是如下的訪問子節點和父節點。
int heap[10];
//訪問第三個節點的父節點
heap[3/2];
//訪問第三個節點的左子節點
heap[3*2]
//訪問第三個節點的右子節點
heap[3*2+1]
如果是最大堆,那麼父節點都要比兩個子節點的數值大,相應的最小堆就是父節點比兩個子節點都要小。而哨兵的作用就是充當邊界檢測的角色。如果是最大堆,那麼這個值比堆中的數據都要大,如果是最小堆,那麼這個值比堆中所有數據都要小。
堆的程序實現
堆的結構定義
typedef int Element;
typedef struct HeapNode
{
int heapsize; //堆的最大尺寸,也就是數組的最大值
int size;//堆的當前大小
Element *data;//數組指針,動態分配數組的大小
};
typedef struct HeapNode* Heap;//堆的指針定義
#define MIN_DATA -20000 //這裏實現最小堆,哨兵是最小值,堆中數據的範圍是-10000,10000,這個值隨需要更改
創建一個heapsize大小的堆
Heap creatHeap(int heapsize)
{
Heap heap;
heap=(Heap)malloc(sizeof(struct HeapNode));
if(heap==NULL)
{
printf("內存不足");
return NULL;
}
heap->data=(Element*)malloc(sizeof(Element)*(heapsize+1));
if(heap->data==NULL)
{
printf("內存不足");
return NULL;
}
heap->heapsize=heapsize;
heap->size=0;
heap->data[0]=MIN_DATA;
return heap;
}
判斷堆空或者堆滿
這個比較簡單,因爲堆是一個數組,如果size變量爲0,表示堆空。如果size變量等於堆數組的最大值,那麼就是堆滿。
int isFull(Heap heap)
{
return heap->size==heap->heapsize;
}
int isEmpty(Heap heap)
{
return heap->size==0;
}
堆的插入操作
堆的插入操作就是首先將size變量加1,當前節點i=size+1,然後訪問它的父節點i/2,比較插入的元素和父節點的元素哪個元素小(這裏以最小堆爲例),如果新插入的元素比較小,那麼就將父節點i/2的值賦值到當前節點i。此時將i更新(i=i/2),然後去比較此時i的節點的父節點(i/2,相當於(size+1)/4)與插入的元素,直到比較到哨兵,因爲哨兵是最小值,所以插入的元素會放到數組下標的0的位置。如果比較到其中某個位置時,插入的節點比父節點的大,那麼就把插入的節點放到當前位置。以圖說話。
假設有如上的最小堆,堆目前的size是5,如果插入一個元素15,則size加1,相當於把31後面的數組位置空出來,等待插入,然後比較i=6的父節點i/2,此時是16,因爲15小於16,所以16就需要下移到數組下標爲6的位置了。
只是將16複製到了數組下標爲6的地方,數組下標爲3的值沒有改變,還是16.然後此時i移動到位置3,比較i=3的父節點i/2,此時是數組的下標1的數據,13<15,說明符合最小堆的定義,父節點小於子節點,所以15應該插入到數組下標爲3的地方。插入完成。
在比如在此時直接插入20元素,size加1等於7,它的父節點的值爲15,比20小,所以20直接放到數組下標爲7的位置。
插入操作的程序如下:
void insert(Heap heap,Element x)
{
int i=0;
if(isFull(heap))
{
printf("堆滿了");
return;
}
for(i=++heap->size;x<heap->data[i/2];i=i/2) //這裏因爲有哨兵,所以當i等於0時,是一定會結束循環的,如果不使用哨兵,需要判斷一下i爲0,否則導致死循環
{
heap->data[i]=heap->data[i/2];
}
heap->data[i]=x;
}
堆的刪除操作
堆的刪除操作就是將數組的下標1的數據返回,然後重新構造堆,將size減1,並將最後一個數據找到合理的位置插入。就是從根節點開始(也就是下標1)找到兩個子節點的最小值,與最後一個數據比較,如果最後一個數據比兩個子節點的最小值還要小的話,那麼就把這個數據複製到這兩個子節點的父節點;如果最後一個數據比兩個子節點的最小值還要大的話,那麼就將兩個子節點的最小值節點複製到它的父節點,然後以這個子節點作爲父節點,重複上述過程,直到找到合適位置。
假設執行刪除操作,13刪除,最後一個元素16被取出,size減1,比較13的子節點21和15,15位最小的,那麼比較15和16,發現15小,所以將15複製到13的位置。
此時以數組下標3作爲父節點,比較它的兩個子節點,但是發現已經沒有了,所以把16複製到數組下標3的位置就完成了刪除操作。
假設第一次刪除13時,它的兩個子節點都比16大,那麼直接將16複製到數組下標爲1的地方,就完成了刪除。這就是堆的刪除操作的執行過程。
程序代碼如下:
Element deleteHeap(Heap heap)
{
int child,parent;
Element x,num;
if(isEmpty(heap))
{
printf("堆空");
return MIN_DATA;
}
x=heap->data[1];//保存要刪除的元素,作爲返回值
num=heap->data[heap->size--];//堆的尺寸小1,將這個數值保存以免丟失
for(parent=1;parent*2<=heap->size;parent=child)
{
child=2*parent;
//尋找兩個子節點中最小的一個,&&前面的判斷是防止child已經是堆的最後一個數據,加1後超出堆的範圍
if((child+1)<=heap->size&&heap->data[child]>heap->data[child+1])
{
child=child+1;
}
//比較兩個子節點的最小值和要原堆中最後一個元素,如果最後一個元素小,說明找到了插入的位置,結束循環
if(num<=heap->data[child])
{
break;
}
else
{
heap->data[parent]=heap->data[child];
}
}
heap->data[parent]=num;
return x;
}
堆的構建
將一個數組變成最小堆,思路就是從根本一個子樹一個子樹的逐漸變換,假設數組大小爲size,那麼它的父節點就是i=size/2,以i爲起點,執行與刪除操作相同的下濾操作,直到i變成0,就完成了堆的構建,其實就是堆排序的思路。
void perdown(Heap heap,int d)
{
int child,parent;
Element x;
x=heap->data[d];
for(parent=d;parent*2<=heap->size;parent=child)
{
child=2*parent;
if((child+1)<=heap->size&&heap->data[child]>heap->data[child+1])
{
child=child+1;
}
if(x<=heap->data[child])
{
break;
}
else
{
heap->data[parent]=heap->data[child];
}
}
heap->data[parent]=x;
}
void buildHeap(Heap heap)
{
int i=0;
for(i=heap->size/2;i>0;i--)
{
perdown(heap,i);
}
}