優先佇列和堆 實戰:HDU1242
阿新 • • 發佈:2019-01-31
什麼叫優先佇列呢,能完成以下任務的就叫做優先佇列:
·插入一個數值
·取出最小的數值(獲取數值,並且刪除)
實現優先佇列,應該使用二叉樹完成,是一種叫二叉堆的資料結構(binary heap)
二叉堆分為兩種,最小堆和最大堆。最小堆是父節點的鍵值總是小於等於子節點的鍵值。最大堆是父節點的鍵值總是大於等於子節點的鍵值。
可以將二叉堆看成陣列的形式。
程式碼:
// 模擬最小堆 // 最小堆是二叉堆的一種,其特點是父節點的鍵值總是小於或者等於子節點。 // 實現細節: // push:向堆中插入資料時,首先在堆的末尾插入資料,然後不斷向上提升,直到沒有大小顛倒時。 // pop:從堆中刪除最小值時首先把最後一個值複製到根節點上,並且刪除最後一個數值。然後不斷向下交換 // 直到沒有大小顛倒為止。在向下交換過程中,如果有兩個子兒子都小於自己,就選擇較小的 #include <iostream> using namespace std; const int MAX_N = 1005; int heap[MAX_N], sz = 0; void push(int x); void display(); int pop(); int main() { // 測試 int x; int cmd; do { cout << "請輸入命令:1.push\t2.pop\t3.display\t0.quit\n"; cin >> cmd; switch(cmd) { case 1: cout << "Input X:"; cin >> x; push(x); break; case 2: x = pop(); cout << x << "已取出!\n"; break; case 3: display(); break; } }while(cmd); return 0; } void push(int x) { // i是要插入節點的下標 int i = sz++; while(i > 0) { // p為父親節點的下標 int p = (i - 1) / 2; // 如果父親節點小於等於插入的值,則說明大小沒有跌倒,可以退出 if(heap[p] <= x) break; // 互換當前父親節點與要插入的值 heap[i] = heap[p]; i = p; } heap[i] = x; cout << "資料插入成功!\n"; } int pop() { // 取出根節點 int ret = heap[0]; // 將最後一個節點的值提到根節點上 int x = heap[--sz]; int i = 0; while(i * 2 + 1 < sz) { // a,b為左右兩個子節點的下標 int a = 2 * i + 1, b = 2 * i + 2; // 去兩個子節點中較小的值 if(b < sz && heap[b] < heap[a]) a = b; // 如果已經沒有大小顛倒的話則退出迴圈 if(heap[a] >= x) break; // 將父親節點與子節點互換 heap[i] = heap[a]; i = a; } heap[i] = x; return ret; } void display() { for(int i = 0; i < sz; i++) cout << heap[i] << "\t"; cout << endl; }
但是在c++的STL中已經包含了優先佇列的高效實現——priority_queue,不過與上面的例子不一樣的是每次取出數值都是最大值。
看一個簡單的例項:
#include <iostream> #include <queue> using namespace std; int main() { // 宣告 priority_queue<int> pque; // 插入元素 pque.push(3); pque.push(5); pque.push(1); // 不斷迴圈直到為空為止 while(!pque.empty()) { // 獲得最大值並且刪除 cout << pque.top() << endl; pque.pop(); } return 0; }
用杭電的一題來實戰一下吧
題目的意思是和走迷宮找最短出口差不多,就是有一個天使(在地圖上顯示為a),你是天使的好基友(在地圖上顯示為r),為了拯救基友,你打算去劫獄,.代表路, #代表牆壁。每走一步就要花一個時間單位。監獄裡當然會有很多獄警(在地圖上顯示為x)啦,但是基情使你充滿力量,輕輕鬆鬆能殺死一個獄警,也只要一個時間單位好了。現在你要怎麼做才能在最短的時間內救出好基友。
=_=
這題很明顯就是使用BFS來做的。因為裡面多了一個獄警,所以用佇列的話就比較麻煩了,所以在這裡使用優先佇列。
因為優先佇列預設是取出最大值的,所以首先要過載一下運算子,讓優先取出最小值。
ac程式碼如下:
#include <iostream> #include <queue> #include <string> using namespace std; const string fail = "Poor ANGEL has to stay in the prison all his life."; const int INF = 1000005; const int MAX_N = 205; int N, M; int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1}; char pic[MAX_N][MAX_N]; struct Node { int x, y; int num; // 將優先佇列每次取出最大值改為每次去最小值 bool operator < (const Node &a) const { return num > a.num; } }first, next; int bfs(); int main() { while(cin >> N >> M) { int i, j; for(i = 0; i < N; i++) { cin >> pic[i]; for(j = 0; j < M; j++) { if(pic[i][j] == 'a') { first.x = i; first.y = j; first.num = 0; pic[i][j] = '#'; } } } int ans = bfs(); if(ans == INF) cout << fail << endl; else cout << ans << endl; } return 0; } int bfs() { priority_queue<Node> que; que.push(first); while(que.size()) { first = que.top(); que.pop(); for(int i = 0; i < 4; i++ ) { next.x = first.x + dx[i]; next.y = first.y + dy[i]; if(next.x >= 0 && next.x < N && next.y >=0 && next.y < M && pic[next.x][next.y] != '#') { next.num = first.num; if(pic[next.x][next.y] == 'r') return next.num + 1; else if(pic[next.x][next.y] == '.') next.num += 1; else if(pic[next.x][next.y] == 'x') next.num += 2; pic[next.x][next.y] = '#'; que.push(next); } } } return INF; }