7.2 二叉樹與堆
阿新 • • 發佈:2021-11-19
二叉樹與堆
二叉樹是一種特殊的、常見的樹
簡介
二叉樹的特點在於每個結點最多隻有兩個兒子
如果要使用更嚴格的遞迴定義,則是:
二叉樹要麼為空,要麼由根結點、左子樹、右子樹組成
而左子樹、右子樹分別是一棵二叉樹
二叉樹是使用範圍極廣的樹,一棵多叉樹也可以轉換為二叉樹
二叉樹型別
滿二叉樹:如果二叉樹中每個內部結點都有兩個兒子,這樣的二叉樹叫做滿二叉樹
完全二叉樹:如果一棵二叉樹除了最右邊的位置上有一個或幾個葉節點缺少外,其他都是豐滿的,那麼它就是完全二叉樹
我們可以把滿二叉樹理解為一種特殊的,極其完美的完全二叉樹
我們只需要用一個一維陣列就能儲存完全二叉樹
我們將完全二叉樹進行從上到下,從左到右
如果已知子結點的編號是x,那麼它父結點的編號就是x/2
堆
符合所有父結點都比子結點要小的樹,就是最小堆
符合所有父結點都比子結點要大的樹,就是最大堆
那麼想要向堆中加入一個數據,需要怎麼做呢?
比如說我們想要把23加入到一個最小堆中
1.我們把23放入堆頂,檢查是否合適
2.如果不合適,我們就將這個數和它的兩個兒子比較,並且選擇較小者交換位置
3.繼續向下交換,檢查。重複1-2直到符合條件
當新增加一個數被放置到堆頂時,如果此時不符合最小堆的特性,則需要將這個數向下調整,直到找到合適的位置為止
程式碼例項_向下調整
void siftdown(int i) { //傳入一個需要向下調整的結點編號i //這裡傳入1,即從堆的頂點開始向下調整 int t,flag = 0 ; //flag用於標記是否需要繼續向下調整 //當i結點有兒子(其實至少是有左兒子的情況下)並且有需要調整的時候,迴圈就執行 while( i*2 <= n && flag == 0 ){ //先判斷它和左兒子的關係,用t記錄值比較小的結點編號 if(h[i] > h[i*2]) t = i*2 ; //子節點是較小的 //和/2得到父節點相對,*2自然得到子節點(左) else t = 1 ; //1是較小值 //如果有右兒子,再對右兒子(*2+1)進行討論 if(i*2+1 <= n) { //如果右兒子的值更小,更新較小的結點編號 if(h[t] > h[i*2 + 1]) t = i*2+1 ; } //如果發現最小的結點編號不是自己,那麼就說明子結點中,有比父結點更小的 if(t!=i) { swap(t,i);//交換它們,這裡記得自己寫一個swap函式 i = t ; } else{ falg = 1 ; } } return ; }
如果只是想新增一個值,而不要刪除掉最小值,那麼該怎麼做呢?
直接將新元素插入到末尾,再根據情況判斷元素是否需要上移,直到滿足堆的特性為止
堆排序
與快速排序一樣,,堆排序的時間複雜度是O(NlogN)
進行堆排序,需要我們建立對應的堆,每次刪除頂部元素並將頂部元素輸出或者放到一個數組中,直到堆空
最後輸出的或者存放在新陣列中的數,就是已經排序好的
下面以從小到大排序為例
程式碼例項_從小到大堆排序
//先整個刪除最小元素的 int deletemin() { int t ; t = h[1];//用一個臨時變數記錄堆頂點的值 h[1] = h[n];//將堆的最後一個點賦值到堆頂 n--;//使堆的元素減少1 siftdown(1) ; //向下調整 return t ; //返回在之前記錄的堆的頂點的最小值 }
完整的程式碼組成如下:
#include <stdio.h>
int h[101];//用來存放堆的陣列
int n ; //用來儲存堆中元素的個數,實質上就是堆的大小
//先整個交換函式
void swap(int x ,int y)
{
int t ;
t = h[x];
h[x] = h[y];
h[y] = t ;
return
}
//向下調整的函式,上文已經寫過了
void siftdown(int i)
{
//傳入一個需要向下調整的結點編號i,這裡傳入1,即從堆的頂點開始向下調整
int t,flag = 0 ;
//flag用於標記是否需要繼續向下調整
//當i結點有兒子(其實至少是有左兒子的情況下)並且有需要調整的時候,迴圈就執行
while( i*2 <= n && flag == 0 ){
//先判斷它和左兒子的關係,用C記錄值比較小的結點編號
if(h[i] > h[i*2])
t = i*2 ;
else
t = 1 ;
//如果有右兒子,再對右兒子進行討論
if(i*2+1 <= n)
{
//如果右兒子的值更小,更新較小的結點編號
if(h[t] > h[i*2 + 1])
t = i*2+1 ;
}
//如果發現最小的結點編號不是自己,那麼就說明子結點中,有比父結點更小的
if(t!=i)
{
swap(t,i);//交換它們,這裡記得自己寫一個swap函式
i = t ;
}
else{
falg = 1 ;
}
}
return ;
}
//建立堆的函式
void creat()
{
int i ; //從最後一個非葉結點到第一個結點依次進行向下調整
for(i=n/2 ; i>=1;i--)
{
siftdown(i);
}
return ;
}
//刪除最大的元素
int deletmax()
{
int t ;
t = h[1];
h[1] = h[n];
b-- ;
siftdown(1);//向下調整
return t ;
}
int main(){
int i , num ;
//讀入要排序的數字個數
scanf("%d",&num);
//讀入數字
for(i=1;i<num;i++)
scanf("%d",&h[i]);
n = num ;
//建堆
creat();s
//刪除頂部元素,連續刪除n次,實際上就是從小到大把數輸出
for(i=1;i<=num;i++)
printf("%d",deletemax());
getchar();getchar();
return 0 ;
}
小結
像這種,支援插入元素和尋找最值元素的資料結構被稱為優先佇列
堆就是一種優先佇列的實現