1. 程式人生 > >優先佇列(堆)及相關操作

優先佇列(堆)及相關操作

二叉堆(堆)

  • 堆是一顆完全二叉樹:除了底層每個節點都有兩個孩子,底層節點從左到右依次填入(不能有間隔)。
  • 一顆高為h的完全二叉樹有2h2h+11個節點;N的節點的完全二叉樹的高度為logN
  • 堆可以用陣列實現:如果陣列下標從1開始,每個位置i的左孩子下標為2i,右孩子下標為2i+1,父親下標為i/2
  • 堆中每個節點的孩子都大於它自身,稱為小堆。堆中每個節點的孩子都小於它自身,稱為大堆。

堆的資料結構(陣列實現)

  • 基本資料結構:一個存放元素的陣列,堆的最大容量,當前堆的大小
typedef struct
HeapStruct { // 定義堆的結構(基於陣列) int Capacity; // 堆的最大容量 int Size; // 堆中元素個數 ElemType* arr; // 堆中包含資料元素的陣列的頭指標 }Heap, *BinHeap;

堆的插入操作(以小堆為例)

  • 向堆中插入元素X,首先在下一個空閒位置建立一個“空穴”(滿足完全二叉樹)。
  • 如果X放入該“空穴”不破壞堆序性(X大於“空穴”的父節點),則插入完成。否則把“空穴”的父節點上的元素移到該空穴中。
  • 繼續該操作,直到“空穴”的父節點小於X(以小堆為例),把X放入空穴。插入完成。
  • 這種策略被稱為上濾
void insert(BinHeap H, ElemType x)
{
    if (isFull(H))      // 判斷佇列是否為滿
    {
        cout << "The heap is full" << endl;
        exit(1);
    }
    int i = ++H->Size;  // 將佇列的大小增1,賦值給i表示要插入的空穴
    while (x < H->arr[i / 2])   // 比較插入位置的父節點的值和要插入的元素的大小,元素值較小,則移動父節點到空穴,空穴上移。。。
{ H->arr[i] = H->arr[i / 2]; i /= 2; } H->arr[i] = x; // 停止移動的空穴賦值為x }

堆的刪除操作

  • 刪除堆頂元素(最小值),也叫做優先隊列出隊。首先返回該元素值,將堆頂(根節點)置為“空穴”。
  • 然後選擇“空穴”的較小的孩子移動到空穴,“空穴”下移。這裡選取堆中最後位置的元素X作為插入的元素。
  • 接下來類似於插入操作,當X都大於“空穴”的孩子,小孩子移動“空穴”,“空穴”下移,直到X小於“空穴”的孩子。
  • 或者,“空穴”移動到堆最末尾。“空穴”停止移動,將X填入空穴。
  • 這種策略被稱為下濾。
ElemType DeleteMin(BinHeap H)   // 刪除優先佇列(堆)中的最小值
{
    if (isEmpty(H))
    {
        cout << "The heap is empty" << endl;
        return H->arr[0];
    }
    ElemType minElem = H->arr[1];       // 要返回的最小值:堆中陣列的首元素/樹形結構的根節點
    ElemType lastElem = H->arr[H->Size--];  // 記錄最後一個元素的值,且更新H->Size
    // 由於刪除了根節點,樹形結構需要進行調整
    int i = 1;
    int priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 求較小的孩子
    while (H->arr[priorChild] < lastElem && priorChild <= H->Size)  // 將根節點置為空穴
    {
        H->arr[i] = H->arr[priorChild];     // 選取較小的孩子填充空穴
        i = priorChild;                     // 空穴下移,"下濾"
        priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 更新"優先"孩子
    }
    H->arr[i] = lastElem;       // 將原來堆裡面最後一個元素的值填入空穴
    return minElem;
}

構建堆

  • 從陣列中直接構建堆,既不需要依次插入元素來構建堆。
  • 採用“下濾”的策略,依次從有孩子的節點(堆大小除以2)開始至根節點結束,對這些節點依次執行“下濾”操作。
BinHeap BuildHeap(ElemType* A, int n)       // 直接由陣列構建堆
{
    BinHeap H = initializer(n * 2);
    for (int i = 0;i < n;i++)
    {
        H->arr[i + 1] = A[i];
    }
    H->Size = n;            // 將元素填入堆中

    for (int i = n / 2;i > 0;i--)   // 從有孩子的節點開始執行下濾操作
    {
        int SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 求“小孩子”下標
        while (H->arr[SmallChild] < H->arr[i] && SmallChild<H->Size)        // 當節點i的小孩子小於其自身且其孩子的下標不超過堆大小時,執行迴圈
        {
            ElemType temp = H->arr[i];
            H->arr[i] = H->arr[SmallChild];
            H->arr[SmallChild] = temp;              // 交換節點i與其小孩子的位置
            i = SmallChild;                         // 節點i下移
            SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 更新“小孩子”
        }
    }
    return H;
}

附二叉堆實現及相關操作C/C++

#include<iostream>

using namespace std;
typedef int ElemType;

typedef struct HeapStruct {     // 定義堆的結構(基於陣列)
    int Capacity;               // 堆的最大容量
    int Size;                   // 堆中元素個數
    ElemType* arr;              // 堆中包含資料元素的陣列的頭指標
}Heap, *BinHeap;

BinHeap initializer(int Capacity)       // 初始化二叉堆,引數為最大容量
{
    BinHeap H = new Heap;               // 建立一個堆結構
    H->Capacity = Capacity;             
    H->arr = new ElemType[Capacity];    // 為堆中陣列分配空間
    H->arr[0] = -9999;      // 將陣列的0元素賦值為無窮小,表示新增一條啞資訊,保證該位置元素小於任何堆中元素
    H->arr[1] = 50;
    H->Size = 0;
    return H;
}

bool isEmpty(BinHeap H)
{
    return H->Size == 0;
}

bool isFull(BinHeap H)
{
    return H->Size == H->Capacity;
}


void insert(BinHeap H, ElemType x)
{
    if (isFull(H))      // 判斷佇列是否為滿
    {
        cout << "The heap is full" << endl;
        exit(1);
    }
    int i = ++H->Size;  // 將佇列的大小增1,賦值給i表示要插入的空穴
    while (x < H->arr[i / 2])   // 比較插入位置的父節點的值和要插入的元素的大小,元素值較小,則移動父節點到空穴,空穴上移。。。
    {
        H->arr[i] = H->arr[i / 2];
        i /= 2;
    }
    H->arr[i] = x;      // 停止移動的空穴賦值為x
}

ElemType DeleteMin(BinHeap H)   // 刪除優先佇列(堆)中的最小值
{
    if (isEmpty(H))
    {
        cout << "The heap is empty" << endl;
        return H->arr[0];
    }
    ElemType minElem = H->arr[1];       // 要返回的最小值:堆中陣列的首元素/樹形結構的根節點
    ElemType lastElem = H->arr[H->Size--];  // 記錄最後一個元素的值,且更新H->Size
    // 由於刪除了根節點,樹形結構需要進行調整
    int i = 1;
    int priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 求較小的孩子
    while (H->arr[priorChild] < lastElem && priorChild <= H->Size)  // 將根節點置為空穴
    {
        H->arr[i] = H->arr[priorChild];     // 選取較小的孩子填充空穴
        i = priorChild;                     // 空穴下移,"下濾"
        priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 更新"優先"孩子
    }
    H->arr[i] = lastElem;       // 將原來堆裡面最後一個元素的值填入空穴
    return minElem;
}

BinHeap BuildHeap(ElemType* A, int n)       // 直接由陣列構建堆
{
    BinHeap H = initializer(n * 2);
    for (int i = 0;i < n;i++)
    {
        H->arr[i + 1] = A[i];
    }
    H->Size = n;            // 將元素填入堆中

    for (int i = n / 2;i > 0;i--)   // 從有孩子的節點開始執行下濾操作
    {
        int SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 求“小孩子”下標
        while (H->arr[SmallChild] < H->arr[i] && SmallChild<H->Size)        // 當節點i的小孩子小於其自身且其孩子的下標不超過堆大小時,執行迴圈
        {
            ElemType temp = H->arr[i];
            H->arr[i] = H->arr[SmallChild];
            H->arr[SmallChild] = temp;              // 交換節點i與其小孩子的位置
            i = SmallChild;                         // 節點i下移
            SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 更新“小孩子”
        }
    }
    return H;
}

int main()
{
    const int rawdata[] = { 19, 13, 9, 8, 23, 39, 4, 2, 75, 100, 43, 58 };
    int tablesize = 12;

    BinHeap myHeap = initializer(tablesize*2);

    for (int i = 0;i < sizeof(rawdata) / sizeof(int);i++)
    {
        insert(myHeap, rawdata[i]);     // 向堆中插入給定資料
    }

    cout << "Prior Deque from the Heap : \n";   // 依次優先出佇列
    while (!isEmpty(myHeap))
    {
        cout << DeleteMin(myHeap) << " ";
    }
    cout << endl;
    int newdata[] = { 9, 8, 23, 39, 2, 75, 100, 43, 58 };

    BinHeap newHeap = BuildHeap(newdata, 9);
    cout << "Build a new Heap directly from an array...\n"; 
    cout << "Prior Deque from the new Heap : \n";   // 依次優先出佇列
    while (!isEmpty(newHeap))
    {
        cout << DeleteMin(newHeap) << " ";
    }
    cout << endl;
    delete myHeap;
    delete newHeap;
    system("pause");
    return 0;
}
  • 操作執行結果
Prior Deque from the Heap :
2 4 8 9 13 19 23 39 43 58 75 100
Build a new Heap directly from an array...
Prior Deque from the new Heap :
2 8 9 23 39 43 58 75 100
請按任意鍵繼續. . .

參考資料

Mark Allen Weiss: 資料結構與演算法分析