1、堆的定義
堆就是用數組實現的二叉樹,所有它沒有使用父指針或者子指針。
堆就是利用完全二叉樹的結構來維護的一維數組。
創建一個堆除了一個簡單的一維數組以外,不需要任何額外的空間。
如果我們不允許使用指針,那么我們怎么知道哪一個節點是父節點,哪一個節點是它的子節點呢?節點在數組中的位置index 和它的父節點已經子節點的索引之間有一個映射關系。
如果 i
是某個節點的索引,那么下面的公式就給出了它的父節點和子節點在數組中的位置:
parent(i) = floor((i - 1)/2) left(i) = 2i + 1 right(i) = 2i + 2
注意 right(i)
就是簡單的 left(i) + 1
。左右節點總是處於相鄰的位置。
2、堆的分類
按照堆的特點可以把堆分為大頂堆和小頂堆
大頂堆:每個結點的值都大於或等於其左右孩子結點的值,左右孩子節點無大小關系
小頂堆:每個結點的值都小於或等於其左右孩子結點的值,左右孩子節點無大小關系
我們對堆中的結點按層進行編號,將這種邏輯結構映射到數組中就是下面這個樣子
我們用簡單的公式來描述一下堆的定義就是:(讀者可以對照上圖的數組來理解下面兩個公式)
大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] 小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
堆可以用來做什么:
- 構建優先隊列
- topk
- 支持堆排序
3、堆和普通樹的區別
堆並不能取代二叉搜索樹,它們之間有相似之處也有一些不同。我們來看一下兩者的主要差別:
節點的順序:在二叉搜索樹中,左子節點必須比父節點小,右子節點必須必比父節點大。但是在堆中並非如此。在最大堆中兩個子節點都必須比父節點小,而在最小堆中,它們都必須比父節點大。
內存占用:普通樹占用的內存空間比它們存儲的數據要多。你必須為節點對象以及左/右子節點指針分配額為是我內存。堆僅僅使用一個數據來村塾數組,且不使用指針。
平衡:二叉搜索樹必須是“平衡”的情況下,其大部分操作的復雜度才能達到O(log n)。你可以按任意順序位置插入/刪除數據,或者使用 AVL 樹或者紅黑樹,但是在堆中實際上不需要整棵樹都是有序的。我們只需要滿足對屬性即可,所以在堆中平衡不是問題。因為堆中數據的組織方式可以保證O(log n) 的性能。
搜索:在二叉樹中搜索會很快,但是在堆中搜索會很慢。在堆中搜索不是第一優先級,因為使用堆的目的是將最大(或者最小)的節點放在最前面,從而快速的進行相關插入、刪除操作。
4、堆的操作
創建堆:創建小頂堆
1.將數組順序添加到堆中。(此時堆還不算小頂堆)
2.調整堆為小頂堆
注意:
1.for(j=(heap->Size-1)/2;j>=0;j--):比如我下面堆中有十個元素,Size大小也為10,第一次調整堆時,索引是從4開始的,那么它的左右節點分別是:9、10索引位置上的元素,很明顯,10索引是不存在。
j的數字依次是4,3,2,1,0。
2.每次外層遍歷都需要判斷節點的左右節點是否存在(左節點存在,右節點自然存在,左右節點相鄰,右節點索引=左節點索引+1),然后找出左右節點中的最小值,節點與此最小值交換,最后索引位置也交換,繼續往下
#include<stdio.h> #define MAX 100 typedef struct Heap HeapNode; struct Heap{ int Arr[MAX] ; int Size ; }; void print(HeapNode *heap){ int i = 0; for(;i<heap->Size;i++){ printf("%d ",heap->Arr[i]); } printf("\n"); } //創建堆 HeapNode * create(){ HeapNode *heap; heap = (HeapNode*)malloc(sizeof(HeapNode)); heap->Size = 0; return heap; } //向堆中添加數據並調整為最小堆 void Add_and_Adjust(HeapNode * heap,int arr[],int len){ //添加數組元素到堆,順序添加就可以了 int i =0; for(0;i<len;i++){ heap->Arr[i] = arr[i]; } //賦值堆的成員個數:用於處理左右節點是否存在的判斷 heap->Size = i; //調整堆為最小堆:自下往上 int j = 0; for(j=(heap->Size-1)/2;j>=0;j--){ adjust(heap,j); print(heap); } } //自上往下 void adjust(HeapNode * heap,int root){ while(1){ int left = 2 * root + 1; int right = 2 * root + 2; //判斷此節點的左右節點是否存在:如果左邊的left都不存在,則右節點也不會存在,則該節點沒有左右節點 //通過索引位置判斷 if(left>=heap->Size){ return ; } //我們需要找到左右節點中的最小值 //假設左節點為最小值 int min = left; //如果右節點存在,且右節點的值小於左節點,則右節點為最小值 if(right<heap->Size&&(heap->Arr[right] < heap->Arr[left])) min = right; //如果根節點比左右節點中小的那個還小,則不用交換 if(heap->Arr[root]<= heap->Arr[min]) return ; //如果根節點比左右節點中小的大,則交換 int temp = heap->Arr[root]; heap->Arr[root] = heap->Arr[min]; heap->Arr[min] = temp; //交換后,繼續往下遍歷,根索引位置等於左右節點中值小些的索引 root = min; } } int main(void) { int arr[10] = {76,23,43,6,87,56,34,45,32,12}; HeapNode *heap = create(); Add_and_Adjust(heap,arr,10); return 0; }
構建優先隊列:利用堆的堆頂為最小值或者最大值的特性
需要掌握堆的插入操作:需要注意的是每次插到數組末尾,從末尾與父節點比較交換,交換后,繼續往上比較,直至沒有滿足比較條件或者到達索引為1的節點。
1.每次插入一個元素放在堆的數組末尾
2.從末尾開始,與此元素的父節點比較,交換。
#include<stdio.h> #define MAX 100 typedef struct Heap HeapNode; struct Heap{ int Arr[MAX] ; int Size ; }; void print(HeapNode *heap){ int i = 0; for(;i<heap->Size;i++){ printf("%d ",heap->Arr[i]); } printf("\n"); } //創建堆 HeapNode * create(){ HeapNode *heap; heap = (HeapNode*)malloc(sizeof(HeapNode)); heap->Size = 0; return heap; } //向堆中添加數據並調整為最小堆 void Add_and_Adjust(HeapNode * heap,int arr[],int len){ //添加數組元素到堆,順序添加就可以了 int i =0; for(0;i<len;i++){ heap->Arr[i] = arr[i]; } //賦值堆的成員個數:用於處理左右節點是否存在的判斷 heap->Size = i; //調整堆為最小堆 int j = 0; for(j=(heap->Size-1)/2;j>=0;j--){ adjust(heap,j); print(heap); } } //從頂至下 void adjust(HeapNode * heap,int root){ while(1){ int left = 2 * root + 1; int right = 2 * root + 2; //判斷此節點的左右節點是否存在:如果左邊的left都不存在,則右節點也不會存在,則該節點沒有左右節點 //通過索引位置判斷 if(left>=heap->Size){ return ; } //我們需要找到左右節點中的最小值 //假設左節點為最小值 int min = left; //如果右節點存在,且右節點的值小於左節點,則右節點為最小值 if(right<heap->Size&& (heap->Arr[right] < heap->Arr[left])) min = right; //如果根節點比左右節點中小的那個還小,則不用交換 if(heap->Arr[root]<= heap->Arr[min]) return ; //如果根節點比左右節點中小的大,則交換 int temp = heap->Arr[root]; heap->Arr[root] = heap->Arr[min]; heap->Arr[min] = temp; //交換后,繼續往下遍歷,根索引位置等於左右節點中值小些的索引 root = min; } } void insert(HeapNode * heap,int node){ if(heap->Size>=MAX){ return ; } heap->Arr[heap->Size] = node; heap->Size ++; int now = heap->Size-1; print(heap); while(now>=1){ //找到父節點 int root = (now -1)/2; //如果父節點小於此節點,結束 if(heap->Arr[root]<=heap->Arr[now]){ break ; } printf("%d:%d\n",heap->Arr[root],heap->Arr[now]); //交換 int temp = heap->Arr[root]; heap->Arr[root] = heap->Arr[now]; heap->Arr[now] = temp; //繼續往上交換 now = root; } } int main(void) { int arr[10] = {76,23,43,6,87,56,34,45,32,12}; HeapNode *heap = create(); Add_and_Adjust(heap,arr,10); insert(heap,2); print(heap); return 0; }
TOPk
很常見的面試題,利用堆很容易做出來。
找出最大的k個數,我們可以用最小堆來做,
新插入的元素與堆頂的元素比較,如果比它大,則將它賦值給堆頂元素,並向下調整堆(繼續維持最小堆的狀態),如果比堆頂元素小,則不做處理。
#include<stdio.h> #define MAX 100 typedef struct Heap HeapNode; struct Heap{ int Arr[MAX] ; int Size ; }; void print(HeapNode *heap){ int i = 0; for(;i<heap->Size;i++){ printf("%d ",heap->Arr[i]); } printf("\n"); } //創建堆 HeapNode * create(){ HeapNode *heap; heap = (HeapNode*)malloc(sizeof(HeapNode)); heap->Size = 0; return heap; } //向堆中添加數據並調整為最小堆 void Add_and_Adjust(HeapNode * heap,int arr[],int len){ //添加數組元素到堆,順序添加就可以了 int i =0; for(0;i<len;i++){ heap->Arr[i] = arr[i]; } //賦值堆的成員個數:用於處理左右節點是否存在的判斷 heap->Size = i; //調整堆為最小堆 int j = 0; for(j=(heap->Size-1)/2;j>=0;j--){ adjust(heap,j); } } //從頂至下 void adjust(HeapNode * heap,int root){ while(1){ int left = 2 * root + 1; int right = 2 * root + 2; //判斷此節點的左右節點是否存在:如果左邊的left都不存在,則右節點也不會存在,則該節點沒有左右節點 //通過索引位置判斷 if(left>=heap->Size){ return ; } //我們需要找到左右節點中的最小值 //假設左節點為最小值 int min = left; //如果右節點存在,且右節點的值小於左節點,則右節點為最小值 if(right<heap->Size&& (heap->Arr[right] < heap->Arr[left])) min = right; //如果根節點比左右節點中小的那個還小,則不用交換 if(heap->Arr[root]<= heap->Arr[min]) return ; //如果根節點比左右節點中小的大,則交換 int temp = heap->Arr[root]; heap->Arr[root] = heap->Arr[min]; heap->Arr[min] = temp; //交換后,繼續往下遍歷,根索引位置等於左右節點中值小些的索引 root = min; } } void add(HeapNode * heap,int node){ if(node <= heap->Arr[0] ){ return ; } heap->Arr[0]= node; adjust(heap,0); } int main(void) { int arr[10] = {76,23,43,6,87,56,34,45,32,12}; HeapNode *heap = create(); Add_and_Adjust(heap,arr,10); add(heap,59); add(heap,98); add(heap,349); add(heap,321); add(heap,125); print(heap); return 0; }