1. 程式人生 > >數據結構-堆 接口定義與實現分析(詳細註釋與圖解)

數據結構-堆 接口定義與實現分析(詳細註釋與圖解)

info 獲取 init ret targe 動態 函數 target 用戶自定義函數

如果想了解堆的概念,可以點擊此處查看前面關於堆的定義的隨筆。

堆的操作接口包括初始化堆、銷毀堆、向堆中插入元素、從堆頂移除元素、堆的結點個數。

我們用heap來命名一個堆。下面是對以上接口的定義:

heap_init


void heap_init(Heap *heap,int (*compare)(const void key1,const void key2),void (*destroy)(void *data));

返回值:無

描述初始化堆heap

在對堆執行其他操作之前,必須要對其進行初始化。

函數compare用來比較堆中的結點大小,如果堆為最大值堆,當key1>key2時,函數返回1;當key1=key2時,函數返回0;當key1<key2時,函數返回-1。在最小值堆中返回值正好相反。

函數指針destroy,通過調用heap_deatroy來釋放動態分配的內存空間。如果堆中的數據不需要釋放,那麽destroy應該指向NULL。

復雜度:O(1)

heap_destroy


void heap_destroy(Heap *heap);

返回值:無

描述銷毀堆heap

在調用heap_destroy之後不再允許進行其他操作。

heap_destroy會刪除堆中的所有結點,在刪除的同時調用heap_init中的destroy所指向的銷毀函數(此函數指針不為NULL)。

復雜度:O(n),n代表堆中的結點個數。

heap_insert


int heap_insert(Heap *heap,const void *data);

返回值:如果插入元素成功則返回0,否則返回-1。

描述向堆heap中插入一個結點

新結點包含一個指向data的指針,只要結點仍然存在於堆中,此指針就一直有效。與data有關的內存空間將由函數的調用者來管理。

復雜度:O(lg n),n代表堆中的結點個數。

heap_extract


int heap_extract(Heap *heap,void **data);

返回值:如果結點釋放成功則返回0,否則返回-1。

描述從堆heap中釋放堆頂部的結點

返回時,data指向被釋放結點中存儲的數據。與data相關的內存空間將由函數的調用者來管理。

復雜度:O(lg n),n代表堆中的結點個數。

heap_size


int heap_size(const Heap *heap);

返回值:堆中結點的個數。

描述:這是一個獲取堆heap中結點個數的宏。

復雜度:O(1)。

下面來分析一下如何實現堆:

我們用來叉樹來實現堆,將其結點按照樹的層次結構存放在一個數組中。我們令Heap作為堆的數據結構,此結構體包含4個成員:

1、size指明堆中的結點個數;2、compare與3、destroy是用於封閉傳入heap_init的函數的指針;4、tree是堆中存儲結點的數組。

堆的頭文件示例如下:

/*heap.h*  堆的頭文件/
#ifndef HEAP_H
#define HEAP_H

/*定義堆的數據結構體*/
typedef struct Heap_
{
    int size;
    
    int (*compare)(const void *key1,const void *key2);
    void (*destroy)(void *data);
    
    void **tree;
}Heap;

/*公共接口部分*/
void heap_init(Heap *heap,int (*compare)(const void *key1,const void key2),void (*destroy)(void *data));
void heap_destroy(Heap *heap);
int heap_insert(Heap *heap,const void *data);
int heap_extract(Heap *heap,void **data);

#define heap_size(heap)((heap)->size)

#endif // HEAP_H

堆的實現示例如下:

/*heap.c*/
#include <stdlib.h>
#include <string.h>

#include "heap.h"
/*定義heap執行中需要使用的私有宏*/
#define heap_parent(npos) ((int)(((npos)-1)/2))  /*npos的父結點*/
#define heap_left(npos) (((npos)*2)+1)           /*npos的左兄弟結點*/
#define heap_right(npos) (((npos)*2)+2)          /*npos的右兄弟結點*/

/*heap_init 堆的初始化*/
void heap_init(Heap *heap,int (*compare)(void *key1,void *key2),void (*destroy)(void *data))
{
    /*只需要將size設為0,destroy成員指向destroy,將tree指針設置為NULL*/
    heap->size = 0;
    heap->compare = compare;
    heap->destroy = destroy;
    heap->tree = NULL;

    return ;
}
/*heap_destroy  銷毀堆*/
void heap_destroy(Heap *heap)
{
    int i;
    /*移除堆中所有的結點*/
    if(heap->destroy != NULL)
    {
        for(i=0; i<heap_size(heap);i++)
        {
            /*調用用戶自定義函數釋放動態分配的數據*/
            heap->destroy(heap->tree[i]);
        }
    }
    /*釋放為堆分配的空間*/
    free(heap->tree);

    memset(heap,0,sizeof(Heap));
    return;
}

/*heap_insert  向堆中插入結點*/
int heap_insert(Heap *heap,const void *data)
{
    void *temp;
    int  ipos;
         ppos;

    /*為結點分配空間*/
    if((temp = (void **)realloc(heap->tree,(heap->size(heap)+1)*sizeof(void *))) == NULL)
    {
        return -1;
    }
    else
    {
        heap->tree = temp;
    }
    /*將結點插入到堆的最末端*/
    heap->tree[heap_size(heap)] = (void *)data;
    /*將新結點向上推動,恢復堆的排序特點*/
    ipos = heap_size(heap);    /*堆結點數的數值*/
    ppos = heap_parent(ipos);  /*ipos位置結點的父結點*/

    /*如果堆不為空,並且末位結點大於其父結點,則將兩個結點進行交換*/
    while(ipos>0 && heap->compare(heap->tree[ppos],heap->tree[ipos])<0)
    {
        /*交換末端結點與其父結點的位置*/
        temp = heap->tree[ppos];
        heap->tree[ppos] = heap->tree[ipos];
        heap->tree[ipos] = temp;

        /*將定位結點向上移動一層,以繼續執行堆排序*/
        ipos = ppos;
        ppos = heap_parent(ipos);
    }

    /*堆插入與排序完成,調整堆的結點數量值*/
    heap->size++;

    return 0;
}

/*heap_extract 釋放堆頂部的結點*/
int heap_extract(Heap *heap,void **data)
{
    void *save,
         *temp;
    int  ipos,lpos,rpos,mpos;

    /*不允許從空的堆中釋放結點*/
    if(heap->size(heap) == 0)
        return -1;

    /*釋放堆頂部的結點*/
    /*首先將data指向將要釋放結點的數據*/
    *data = heap->tree[0]

    /*將save指向未位結點*/
    save = heap->tree[heap_size(heap)-1];

    if(heap_size(heap)-1 > 0)
    {   /*為堆分配一個稍小一點的空間*/
        if((temp = (void **)realloc(heap->tree,(heap_size(heap)-1)*sizeof(void *)))==NULL)
        {
            return -1;
        }
        else
        {
            heap->tree = temp;
        }
        /*調整堆的大小*/
        heap->size--;
    }
    else
    {   /*只有一個結點,釋放並重新管理堆,並返回*/
        free(heap->tree);
        heap->tree = NULL;
        heap->size = 0;
        return 0;
    }
    /*將末位結點拷貝到根結點中*/
    heap->tree[0] = save;

    /*重新調整樹的結構*/
    ipos = 0;                /*頂元素*/
    lpos = heap_left(ipos);  /*左子結點*/
    rpos = heap_right(ipos); /*右子結點*/

    /*父結點與兩個子結點比較、交換,直到不再需要交換為止,或者結點到達一個葉子位置*/
    while(1)
    {
        /*選擇子結點與當前結點進行交換*/
        lpos = heap_left(ipos);
        rpos = heap_right(ipos);
        /*父結點與左子結點位置不正確,左子結點大於其父結點*/
        if(lpos < heap_size(heap) && heap->compare(heap->tree[lpos],heap->tree[ipos])>0)
        {
            mpos = lpos;  /*將左子結點的位置賦給mpos(最大位置)*/
        }
        else
        {
            mpos = ipos;
        }

        if(rpos < heap_size(heap) && heap->compare(heap->tree[rpos],heap->tree[mpos])>0)
        {
            mpos = rpos;
        }

        /*當mpos和ipos相等時,堆特性已經被修復,結束循環*/
        if(mpos == ipos)
        {
            break;
        }
        else
        {
            /*交換當前結點與被選中的結點的內容*/
            temp = heap->tree[mpos];
            heap->tree[mpos] = heap->tree[ipos];
            heap->tree[ipos] = temp;

            /*下移一層,以繼續執行堆排序*/
            ipos = mpos;
        }
    }
    return 0;
}

下圖是heap_insert,向最大值堆中插入結點的過程圖解示例

技術分享圖片

下圖是將一個元素從最大值堆中釋放的過程圖解示例:

技術分享圖片

數據結構-堆 接口定義與實現分析(詳細註釋與圖解)