優先佇列(堆)及相關操作
阿新 • • 發佈:2019-02-13
二叉堆(堆)
- 堆是一顆完全二叉樹:除了底層每個節點都有兩個孩子,底層節點從左到右依次填入(不能有間隔)。
- 一顆高為的完全二叉樹有個節點;的節點的完全二叉樹的高度為。
- 堆可以用陣列實現:如果陣列下標從1開始,每個位置的左孩子下標為,右孩子下標為,父親下標為。
- 堆中每個節點的孩子都大於它自身,稱為小堆。堆中每個節點的孩子都小於它自身,稱為大堆。
堆的資料結構(陣列實現)
- 基本資料結構:一個存放元素的陣列,堆的最大容量,當前堆的大小
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: 資料結構與演算法分析