數據結構:堆Heap

一、定義

  堆(heap),也叫優先隊列(priority queue),取出元素的順序是依照元素的優先權(關鍵字)大小,而不是元素進入隊列的先後順序,也就是說堆的排序是按照自定義的順序(優先權)。
  比如說,打印機在打印不同文件時,並不是按照文件的先後順序來打印的,而是由文件是否急需來決定這些文件的打印順序,這樣的模型就是堆模型。
  堆最主要的操作有插入(insert)和刪除(deleteMax/deleteMin)兩種操作,堆有多種實現方式,比如說數組或鏈表,也可以採用完全二叉樹存儲結構來實現,完全二叉樹非常適合實現堆,所以提到堆,默認就是樹結構,這也是完全二叉樹的一個應用,所以這裏只講二叉樹實現的C語言代碼。如下圖所示,堆可分爲最大堆和最小堆,最大堆是指子結點的值小於父結點,最小堆是值子結點的值大於父結點,注意,兄弟結點之間沒有優先權排序的關係


這裏寫圖片描述

  以最大堆爲例,12在第一個位置,11在第二個位置,9在第三個位置……


二、C語言實現

  這裏以最大堆的實現爲例。

//頭文件,文件名:MaxHeap.h
#ifndef MAXHEAP_H_INCLUDED
#define MAXHEAP_H_INCLUDED
struct HeapStruct{
    //存放堆元素的數組,該數組下標爲0的位置不存放堆元素,僅存放哨兵元素
    int* heapArr;
    int size;//存放堆的當前元素的數目
    int capacity;//存放該堆的容量
};
typedef struct HeapStruct* MaxHeap;

MaxHeap createMaxHeap(int);
void insert(int, MaxHeap);
int find(int, MaxHeap);

#endif // MAXHEAP_H_INCLUDED
//堆的具體實現文件,文件名:MaxHeap.c
#include <stdio.h>
#include <stdlib.h>
#include "MaxHeap.h"

MaxHeap createMaxHeap(int maxSize){
    MaxHeap maxHeap = (MaxHeap)malloc(sizeof(struct HeapStruct));
    maxHeap->heapArr = (int*)malloc(sizeof(int)*(maxSize+1));
    maxHeap->size = 0;
    maxHeap->capacity = maxSize;
    maxHeap->heapArr[0] = 0;//heapArr數組不存放堆的元素,只存放堆中的最大元素

    return maxHeap;
}

/*
查找到元素則返回元素在堆中的位置,否則返回0
*/
int find(int element, MaxHeap maxHeap){
    if(!maxHeap){
        printf("該堆不存在\n");
        return 0;
    }
    int i = 1;
    for(;i<=maxHeap->size;i++){
        if(element == maxHeap->heapArr[i]){
            return i;
        }
    }

    return 0;
}

/*
    朝一個堆裏插入元素,
    原理:先在尾部插入元素,然後將該元素與父結點比較,如果父結點小於該元素,則兩個元素互換位置,
    這叫採用上濾(percolate up)的方式搜索。
*/
void insert(int element, MaxHeap maxHeap){
    if(!maxHeap){
        printf("該堆不存在!\n");
        return;
    }
    if(find(element, maxHeap)){
        printf("堆中已存在元素%d,不再插入!\n", element);
        return;
    }
    if(maxHeap->size == maxHeap->capacity){
        printf("該堆的容量已滿,不能再插入元素了\n");
        return;
    }else{
        /*
        因爲這裏可能需要使用哨兵元素來判斷是否結束循環,
        所以哨兵元素在插入操作時,必須爲待插入元素與該堆最大元素的較大者
        判斷哨兵元素是否更新,更新後可以作爲判斷條件,
        但是插入數據後一定要將哨兵元素的值恢復成該堆的最大元素
        */
        if(maxHeap->heapArr[0]<element){
            maxHeap->heapArr[0] = element;
        }
        int i = ++maxHeap->size;
        for(;maxHeap->heapArr[i/2]<element;i/=2){
            maxHeap->heapArr[i] = maxHeap->heapArr[i/2];
        }
        maxHeap->heapArr[i] = element;
        //最後復原哨兵元素
        maxHeap->heapArr[0] = maxHeap->heapArr[1];
    }
}

/*
    刪除並返回最大堆裏的最大的元素
    原理:最大堆的最大元素爲根結點上的元素,也就是說,等價於給根結點位置重新找子樹中最大的元素,合併左右兩顆最大堆。
先刪除最後一個結點n(值替換根結點的值),從根開始找出當前結點下較大的子結點,
然後用這個子結點的值與結點n的值比較,
如果大於結點n的值,則這個子結點的值移動到父結點的位置,該子節點的位置作爲下一個父結點,繼續向下查找;
如果小於結點n的值,則停止向下搜索。
這叫採用下濾(percolate down)的方式搜索。
*/
int deleteMax(MaxHeap maxHeap){
    int parent, child;//父結點和子結點的位置
    if(maxHeap->size == 0){
        printf("該最大堆已經不含有結點\n");
        return 0;
    }

    //初始堆裏的最大值
    int maxItem = maxHeap->heapArr[1];
    //將初始堆的最後一個元素賦值給根結點,並且刪除最後一個結點
    maxHeap->heapArr[1] = maxHeap->heapArr[maxHeap->size--];
    int temp = maxHeap->heapArr[1];
    //開始循環比較父結點與子結點的大小
    //parent*2<=maxHeap->size判斷是否有子結點
    for(parent=1; parent*2 <= maxHeap->size; parent=child){
        child = parent*2;//左子結點的位置
        if((child!=maxHeap->size)&&(maxHeap->heapArr[child]<maxHeap->heapArr[child+1])){
            //child!=maxHeap->size成立的話,表明該parent結點有右子結點
            //maxHeap->heapArr[child]<maxHeap->heapArr[child+1]判斷左右子結點的大小
            //如果if條件成立,那麼parent的右結點比左結點大,此時child指向右結點的位置
            child++;
        }

        if(maxHeap->heapArr[child] > temp){
            maxHeap->heapArr[parent] = maxHeap->heapArr[child];
        }else{
            break;
        }
    }

    maxHeap->heapArr[parent] = temp;

    //修改哨兵
    maxHeap->heapArr[0] = maxHeap->heapArr[1];
    return maxItem;
}

/*
    根據一個數組直接建立堆
    而不是一個元素一個元素的插入
    原理:先無序將這個元素放入到堆裏,
    然後按照刪除元素的原理(下濾)從第一個含非空子結點(位置:最後一個葉結點的位置/2取整)的元素開始調整
*/

void createMaxHeapByArray(int arr[], int length, MaxHeap maxHeap){
    //MaxHeap maxHeap = createMaxHeap(length);
    /*
        因爲這裏數組裏的元素並未放到maxHeap->heapArr裏面
        所以需要把這些元素放進去
        其實這裏不需要另一個數組,這裏爲了方便理解由數組建堆的原理,
        所以另加了一個數組。
    */
    maxHeap->size = length;
    //將數組元素依次放入完全二叉樹裏
    for(int j=0; j<length; j++){
        maxHeap->heapArr[j+1] = arr[j];
    }

    //開始調整
    int i;
    for(i = maxHeap->size/2; i>0; i--){
        int parent, child, element;
        element = maxHeap->heapArr[i];
        for(parent = i; parent*2<=maxHeap->size; parent=child){
            child = parent*2;
            if((child!=maxHeap->size)&&(maxHeap->heapArr[child]<maxHeap->heapArr[child+1])){
                child++;
            }
            if(element >= maxHeap->heapArr[child]){
                break;
            }else{
                maxHeap->heapArr[parent] = maxHeap->heapArr[child];
            }
        }

        maxHeap->heapArr[parent] = element;
    }
    maxHeap->heapArr[0] = maxHeap->heapArr[1];

}
//測試文件,文件名:main.c
#include "MaxHeap.h"
#include <stdio.h>
#include <stdlib.h>

int main()
{
    MaxHeap maxHeap = createMaxHeap(4);

    insert(15, maxHeap);
    insert(7, maxHeap);
    insert(9, maxHeap);
    insert(20, maxHeap);

    //查找元素7
    int position = find(7,maxHeap);
    printf("7在第%d個位置\n",position);
    //查找不存在的元素21
    int pos = find(21,maxHeap);
    printf("21在第%d個位置\n",pos);
    printf("\n");

    for(int i=1;i<=maxHeap->size;i++){
        printf("第%d元素爲:%d\n", i, maxHeap->heapArr[i]);
    }
    printf("哨兵爲:%d\n", maxHeap->heapArr[0]);
    printf("\n");

    //重複插入9
    insert(9, maxHeap);
    printf("\n");

    //測試刪除最大元素
    int maxItem = deleteMax(maxHeap);
    printf("刪除的最大元素爲:%d\n", maxItem);
    printf("刪除之後的堆爲:\n");
    for(int i=1;i<=maxHeap->size;i++){
        printf("第%d元素爲-----%d\n", i, maxHeap->heapArr[i]);
    }
    printf("哨兵爲-----%d\n", maxHeap->heapArr[0]);

    //測試直接生成最大堆
    printf("\n");
    printf("根據數組直接生成最大堆\n");
    int arr[5] = {1,2,3,4,5};
    MaxHeap newMaxHeap = createMaxHeap(5);
    createMaxHeapByArray(arr, 5, newMaxHeap);

    for(int m = 1; m<=newMaxHeap->size; m++){
        printf("第%d元素爲:%d\n", m, newMaxHeap->heapArr[m]);
    }

    return 0;
}

//結果:
//7在第4個位置
//21在第0個位置

//第1元素爲:20
//第2元素爲:15
//第3元素爲:9
//第4元素爲:7
//哨兵爲:20

//堆中已存在元素9,不再插入!

//刪除的最大元素爲:20
//刪除之後的堆爲:
//第1元素爲-----15
//第2元素爲-----7
//第3元素爲-----9
//哨兵爲-----15

//根據數組直接生成最大堆
//第1元素爲:5
//第2元素爲:4
//第3元素爲:3
//第4元素爲:1
//第5元素爲:2
發佈了28 篇原創文章 · 獲贊 70 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章