堆的創建、優先隊列、topk、堆排序C語言實現


 

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;
}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM