終於把樹簡單學習完了,這裏只是簡單學習完了,以後樹的用處還有很多,以後在補補,這裏先往後走,二叉堆,這個我們在排序算法中堆排序裏已經用過了,不過這裏簡單描述一下二叉堆。
9.1 二叉堆簡介
9.1.1 簡介
二叉堆本質上是一種完全二叉樹,它分爲兩個類型。
- 最大堆:任何一個父節點的值,都大於或等於它左右孩子結點的值。
- 最小堆:任何一個父節點的值,都小於或等於它左右孩子結點的值。
圖片來自:https://blog.csdn.net/varyall/article/details/81099681
二叉堆的根結點叫做堆頂,最大堆的堆頂是這個堆中的最大值,最小堆的堆頂是這個堆中的最小值。
9.1.2 二叉堆代碼結構
二叉堆的存儲方式不是鏈式結構,而是用順序存儲的,因爲這個二叉堆就是一個完全二叉樹,用數組存儲也是合理利用空間。
這裏以最小堆爲例介紹,優先隊列是最大堆,這樣兩個都可以介紹了。
我們看的時候還是要畫成二叉樹的風格,這樣有利於我們理解,
這個就是最小堆,是不是很想二叉樹,不過跟二叉樹性質不一樣,最小堆的父節點是最小值,排序二叉樹的父節點是中間值。
代碼中的存儲結構:
就是這樣的順序結構,這裏沒有指向左右孩子的指針,那怎麼找到左右孩子呢?
這個我們就要利用數組的下標了,
假設父節點的下標爲parent,左孩子的下標是2 * parent + 1 ,右孩子的下標爲 2 * parent + 2。
**假設孩子結點的下標爲child,那麼父節點的下標爲 (child - 1)/ 2. **
這個很重要,在代碼中就是移動的關鍵。
代碼:
typedef Elemtype int;
typedef struct binHeap
{
int size; //data數組的長度
int len; //有對少有限數據
Elemtype *data; //data數據
}_binHeap;
9.1.3 二叉堆的創建
二叉堆的創建比較簡單,跟創建一個棧差不多,都是申請數據,填充長度信息
/**
* @brief 創建binHeap對象
* @param
* @retval
*/
struct binHeap *binHeap_creat(int size)
{
struct binHeap *heap = (struct binHeap *)malloc(sizeof(struct binHeap));
assert(heap);
Elemtype *data = (Elemtype *)malloc(sizeof(Elemtype) *size);
assert(data);
heap->len = 0;
heap->data = data;
heap->size = size;
return heap;
}
/**
* @brief 銷燬binHeap對象
* @param
* @retval
*/
int binHeap_destroy(struct binHeap *heap)
{
assert(heap);
if(heap->data)
free(heap->data);
if(heap)
free(heap);
return 0;
}
9.2 二叉堆插入
9.2.1 插入介紹
又來到我們插入環節,這個二叉堆的插入也是比較簡單的,二叉堆的插入就兩個步驟:
第一,插入到最後一個元素,二叉堆前面的元素都已經符合了最大堆或最小堆的性質,所以新添加的要加入到最後一個元素裏。
第二,上浮,新加的一個元素不可能就一定會滿足最大堆或最小堆的性質,所以需要調整,這個調整就是上浮。
9.2.2 插入
二叉堆的插入比較簡單,從上面都已經看到二叉堆的存儲結構是數據,所以只要找到數據最後一個元素插入即可。
代碼:
/**
* @brief 二叉堆插入
* @param
* @retval
*/
int binHeap_insert(struct binHeap *heap, Elemtype data)
{
//判斷數據元素是否已經超出
if(heap->size <= heap->len + 1) //可以實現擴容
return -1;
heap->data[heap->len] = data;
minHeap_upAdjust(heap);
return 0;
}
是不是看着很簡單,minHeap_upAdjust這個函數下節講,這纔是插入的重點。
9.2.3 上浮
插入比較簡單的話,那就是調整比較複雜了,像紅黑樹那樣,調整起來多難受,不過這個二叉堆調整起來也簡單,只要不斷的把最小結點往上提即可。
簡單的插入過程:
插入1,然後可以往上浮:
還不滿足,繼續上浮:
這樣就完成了一個上浮操作,結點多的時候,也是這個道理。
代碼:
/**
* @brief 最小堆調整
* @param
* @retval
*/
static int minHeap_upAdjust(struct binHeap *heap)
{
//調整的時候,就是不斷的和父節點比較,然後判斷是否替換父節點
int child = heap->len - 1;
int parent = (child - 1)/2;
Elemtype temp = heap->data[heap->len-1];
//開始循環比較
while(child)
{
//跟第一個父節點比較
if(temp < heap->data[parent]){
//上浮
heap->data[child] = heap->data[parent];
child = parent;
parent = (child - 1)/2;
}
}
heap->data[child] = temp;
return 0;
}
9.3 二叉堆刪除
9.3.1 刪除介紹
刪除跟插入是反方向的,不過大體邏輯一樣,也是先刪除在調整,廢話就不多說了,直接下節,刪除。
9.3.2 刪除
二叉堆的刪除跟插入不一樣,插入是插入到最後一個元素,但是刪除是刪除第一個元素,爲什麼刪除第一個呢?因爲第一個元素是這個堆中的最大值或者最小值,我們用了二叉堆這個數據結構,就是必然對最大值或最小值感興趣,所以刪除的是第一個元素。
代碼:
/**
* @brief 二叉堆刪除
* @param
* @retval
*/
int binHeap_delete(struct binHeap *heap)
{
//判斷數據元素是否已經超出
if(heap->size <= 0) //可以實現擴容
return -1;
int head = heap->data[0];
heap->data[0] = heap->data[heap->len-1];
heap->len--;
minHeap_downAdjust(heap);
return head;
}
9.3.3 下沉
下沉步驟:
刪除1,然後最後一個元素4替換到1的位置:
然後開始下沉:
然後下沉完成,我畫的比較簡單,不過原理確實這樣。
代碼:
/**
* @brief 最小堆下沉
* @param
* @retval
*/
static int minHeap_downAdjust(struct binHeap *heap)
{
//調整的時候,就是不斷的和孩子結點比較,然後獲取到那個孩子結點的值比父節點的小,替換父節點
int parent = 0;
int left_child = 2 * parent + 1;
int right_child = 2 * parent + 2;
int child = left_child;
Elemtype temp = heap->data[parent];
while(parent < heap->len)
{
//如果存在右孩子,並且右孩子小於左孩子,
if(right_child < heap->len && heap->data[right_child] < heap->data[left_child]) {
child = right_child; //父節點要跟右孩子比較
}
//判斷父節點如果小於等於最小孩子結點,就不用移動
if(temp <= heap->data[child])
break;
//這個需要下沉
heap->data[parent] = heap->data[child];
parent = child;
left_child = parent * 2 + 1;
right_child = parent * 2 + 2;
child = left_child;
}
heap->data[parent] = temp;
return 0;
}
9.4 構建二叉堆
構建二叉堆其實就是堆排序,想看詳細步驟可以看《二、排序算法下》中的堆排序,這裏也是就不描述了。(本來是要寫的,但是上面寫的代碼都是基於優先隊列的寫的,所以這裏就不寫了)
9.5 優先對列
隊列我們前面已經講過了,就是先進先出,入隊操作,元素加到隊尾,出隊操作,首元素出隊。
優先對壘有什麼特殊的呢?
優先隊列分爲兩種情況:
最大優先隊列,無論入隊順序如何,都是當前最大的元素優先出隊
最小優先隊列,無論入隊順序如何,都是當前最小的元素優先出隊
看到這兩種情況就想到我們的二叉堆,二叉堆會把最小值或最大值調整到第一個元素。
出隊的時候,獲取到第一個元素即可,然後再調整,就想到上面寫的刪除操作。
入隊操作:這個就更簡單,直接插入到隊尾,然後調整,這個跟上面的插入操作一樣。
代碼參考上面,這裏就不寫了。