資料結構實現 6.2:優先佇列_基於最大二叉堆實現(C++版)
資料結構實現 6.2:優先佇列_基於最大二叉堆實現(C++版)
1. 概念及基本框架
佇列 是一種現實中很常見的資料結構,但有時候佇列排序不僅僅依靠入隊時間來決定,而是不同的元素有不同的優先順序。顯然,通過 6.1 中的 最大二叉堆
優先佇列 是 佇列 的一種,所以有佇列的基本特性:
1.佇列 有 隊頭 和 隊尾 兩端。
2.入隊 操作只能從 隊尾 進行,出隊 操作只能從 隊頭 進行。
3.先 入隊 的先 出隊 ,即 先進先出(First In First Out),FIFO 。
還有一個隱含特性,佇列可以自行 擴容(縮容),而不需要使用者關心,很顯然,最大二叉堆已經滿足了這個要求。
首先使用一個由 純虛擬函式 構成的 抽象類 作為一個佇列介面,介面內定義一些佇列的基本操作。具體程式碼如下:
template <class T>
class Queue{
public:
virtual int size() = 0;
virtual bool isEmpty() = 0;
virtual void print() = 0;
//入隊操作
virtual void enqueue(T num) = 0;
//出隊操作
virtual void dequeue() = 0;
//獲得隊首元素
virtual T front() = 0;
};
下面只需要通過繼承 抽象類,並且重寫 純虛擬函式 ,就可以完成 優先佇列 的實現。優先佇列類的框架如下:
template <class T>
class PriorityQueue{
...
private:
MaxHeap<T> heap;
};
同樣,這裡為了避免重複設計就可以相容更多資料型別,引入了 泛型 ,即 模板 的概念。(模板的關鍵字是 class 或 typename)
這裡的 heap 表示一個 最大二叉堆 ,同樣,為了保護資料,變數設定為 private 。
注:這裡沒有顯式的給出建構函式,因為子類中除了最大二叉堆物件之外沒有特別需要初始化的東西。編譯器會預設先呼叫 最大二叉堆 類(即父類)的建構函式,再去呼叫 優先佇列 類(即子類)的建構函式。
實現了前面的程式之後,接下來就是一個佇列的增、刪、查以及一些其他基本操作,接下來利用程式碼去實現。
2. 基本操作程式實現
2.1 入隊操作
template <class T>
class PriorityQueue{
public:
...
//入隊操作
void enqueue(T num){
heap.add(num);
}
...
};
2.2 出隊操作
template <class T>
class PriorityQueue{
public:
...
//出隊操作
void dequeue(){
heap.extractMax();
}
...
};
2.3 查詢操作
template <class T>
class PriorityQueue{
public:
...
//獲得隊首元素
T front(){
return heap.findMax();
}
...
};
2.4 其他操作
優先佇列還有一些其他的操作,包括 佇列大小 等的查詢等操作。
template <class T>
class PriorityQueue{
public:
int size(){
return heap.size();
}
bool isEmpty(){
return heap.isEmpty();
}
void print(){
if (heap.isEmpty()){
return;
}
cout << "PriorityQueue: ";
cout << "Size = " << heap.size() << endl;
cout << "front [";
cout << heap.findMax() << "...";
cout << "] rear" << endl;
}
...
};
3. 演算法複雜度分析
因為這裡直接呼叫的最大二叉堆的介面函式,所以基本操作的時間複雜度和最大二叉堆操作的時間複雜度相同。
3.1 入隊操作
函式 | 最壞複雜度 | 平均複雜度 |
---|---|---|
enqueue | O(logn) | O(logn) |
3.2 出隊操作
函式 | 最壞複雜度 | 平均複雜度 |
---|---|---|
dequeue | O(logn) | O(logn) |
3.3 查詢操作
函式 | 最壞複雜度 | 平均複雜度 |
---|---|---|
front | O(1) | O(1) |
總體情況:
操作 | 時間複雜度 |
---|---|
增 | O(logn) |
刪 | O(logn) |
查 | O(1) |
4. 完整程式碼
程式完整程式碼(這裡使用了標頭檔案的形式來實現類)如下:
優先佇列介面函式一覽:
函式宣告 | 函式型別 | 函式功能 |
---|---|---|
int size() | public | 返回二叉堆的大小 |
bool isEmpty() | public | 返回優先佇列是否為空(空為true) |
void print() | public | 列印輸出優先佇列隊頭 |
void enqueue(T) | public | 入隊元素到優先佇列 |
void dequeue() | public | 從優先佇列中出隊一個元素 |
T front() | public | 獲得優先佇列隊頭元素 |
抽象類 介面程式碼:
#ifndef __QUEUE_H__
#define __QUEUE_H__
template <class T>
class Queue{
public:
virtual int size() = 0;
virtual bool isEmpty() = 0;
virtual void print() = 0;
//入隊操作
virtual void enqueue(T num) = 0;
//出隊操作
virtual void dequeue() = 0;
//獲得隊首元素
virtual T front() = 0;
};
#endif
最大二叉堆 程式碼:
#ifndef __MAXHEAP_H__
#define __MAXHEAP_H__
#include "Array.h"
template <class T>
class MaxHeap{
public:
int size(){
return arr.size();
}
bool isEmpty(){
return arr.isEmpty();
}
void add(T num){
arr.addLast(num);
siftUp(arr.size() - 1);
}
T findMax(){
if (arr.size() == 0){
cout << "二叉堆為空!" << endl;
return NULL;
}
return arr.get(0);
}
T extractMax(){
T res = findMax();
swap(0, arr.size() - 1);
arr.removeLast();
siftDown(0);
return res;
}
private:
int parent(int index){
if (index <= 0 || index >= arr.size()){
return NULL;
}
return (index - 1) / 2;
}
int leftChild(int index){
return index * 2 + 1;
}
int rightChild(int index){
return (index + 1) * 2;
}
void swap(int i, int j){
if (i < 0 || i >= arr.size() || j < 0 || j >= arr.size()){
return;
}
T t = arr.get(i);
arr.set(i, arr.get(j));
arr.set(j, t);
}
void siftUp(int index){
while (index && arr.get(index) > arr.get(parent(index))){
swap(index, parent(index));
index = parent(index);
}
}
void siftDown(int index){
while (leftChild(index) < arr.size()){
int left = leftChild(index);
if (left + 1 < arr.size() && arr.get(left + 1) > arr.get(left)){
left++;
}
if (arr.get(index) >= arr.get(left)){
break;
}
swap(index, left);
index = left;
}
}
private:
Array<T> arr;
};
#endif
優先佇列類 程式碼:
#ifndef __PRIORITYQUEUE_H__
#define __PRIORITYQUEUE_H__
#include "Queue.h"
#include "MaxHeap.h"
template <class T>
class PriorityQueue{
public:
int size(){
return heap.size();
}
bool isEmpty(){
return heap.isEmpty();
}
void print(){
if (heap.isEmpty()){
return;
}
cout << "PriorityQueue: ";
cout << "Size = " << heap.size() << endl;
cout << "front [";
cout << heap.findMax() << "...";
cout << "] rear" << endl;
}
//入隊操作
void enqueue(T num){
heap.add(num);
}
//出隊操作
void dequeue(){
heap.extractMax();
}
//獲得隊首元素
T front(){
return heap.findMax();
}
private:
MaxHeap<T> heap;
};
#endif