觀看本系列博文提醒:
- 你將學會堆的原理 和 算法實現;
- 一個企業級應用:堆實現優先隊列;
- 還有堆排序;
- 最後還有一道檢測是否掌握堆算法的作業。
這已經是本系列博文的第二篇了,還沒看過第一篇博文:C/C++ 入門核心算法大局觀:堆 的朋友可以點擊下面鏈接去了解一下。
https://blog.csdn.net/cpp_learner/article/details/105599877
由於本篇博文講的是堆和隊列算法結合的知識點,所以還不懂隊列的朋友也可以點擊下面鏈接去了解一下。
https://blog.csdn.net/cpp_learner/article/details/105299782
操作系統內核作業調度是優先隊列的一個應用實例,它根據優先級的高低而不是先到先服務的方式來進行調度;
如果最小鍵值元素擁有最高的優先級,那麼這種優先隊列叫作升序優先隊列(即總是先刪除最小的元素),類似的,如果最大鍵值元素擁有最高的優先級,那麼這種優先隊列叫作降序優先隊列(即總是先刪除最大的元素);由於這兩種類型是完全對稱的,所以只需要關注其中一種,如升序優先隊列.
我們下面所講的案例完全是依照本系列第一篇博文來將的,所以強烈建議大家先去看本系列的第一篇博文,然後再看這篇博文。
https://blog.csdn.net/cpp_learner/article/details/105599877
定義堆和節點
#define Max 128
// 節點
typedef struct _Task {
int priority; // 優先級
int value; // 鏈表數據
}Task;
#define IsEstimate(a, b) (a.priority < b.priority) // 判斷兩數大小
typedef Task DateType;
// 堆
typedef struct _priorityQueue {
DateType* date; // 存儲數據指針
int size; // 當前存儲個數
int capacity; // 當前存儲容量
}PQ;
這次我們堆中存儲的是一個結構體指針。
該結構體裏面有
int priority; // 優先級
int value; // 鏈表數據
兩個變量。一個是決定出隊的優先級,一個是存儲的數據。
#define IsEstimate(a, b) (a.priority < b.priority) // 判斷兩數大小
此條宏定義是用來判斷節點的優先大小的。
初始化
建完後我們得初始化啦!
首先和之前一樣,定義相關的函數:
bool initPQ(PQ &queue, DateType *arr, int size); // 初始化
static void buildHeap(PQ &queue); // 建堆
static void heapDown(PQ &queue, int index); // 堆節點下移
函數的具體實現:
void heapDown(PQ &queue, int index) {
int father, son;
DateType cur = queue.date[index];
for (father = index; (father * 2 + 1) < queue.size; father = son) {
son = father * 2 + 1; // 找到子節點下標
// 此條判斷找到子節點的最大節點
if (son + 1 < queue.size && IsEstimate(queue.date[son], queue.date[son + 1])) {
son++;
}
// 判斷子節點的值與父節點的值的大小
if (IsEstimate(queue.date[son], cur)) {
break;
} else {
queue.date[father] = queue.date[son];
queue.date[son] = cur;
}
}
}
void buildHeap(PQ &queue) {
for (int i = queue.size / 2 - 1; i >= 0; i--) {
heapDown(queue, i);
}
}
bool initPQ(PQ & queue, DateType* arr, int size) {
if (!arr) {
cout << "數組爲空!" << endl;
return false;
}
int capacity = Max > size ? Max : size; // 選定內配的內存容量
queue.date = new DateType[capacity];
if (!queue.date) {
cout << "內存分配失敗!" << endl;
return false;
}
queue.capacity = capacity;
queue.size = 0;
if (size > 0) {
memcpy(queue.date, arr, size * sizeof(DateType)); // 內存拷貝
queue.size = size;
buildHeap(queue); // 建堆
}
return true;
}
將數組的內存拷貝給堆後,我們就可以開始建堆的步驟了。
插入元素
也還是一樣,將新的元素插入堆爲中,然後再進行堆的重新排序。
定義相關函數:
bool insert(PQ &queue, DateType index); // 插入
static void heapUp(PQ &queue, int index); // 堆節點上移
具體函數實現:
void heapUp(PQ &queue, int index) {
/*int father, son;
for (son = index; (son - 1) / 2 >= 0; son = father) {
int cur = queue.date[son];
father = (son - 1) / 2;
if (queue.date[father] > cur) {
break;
} else {
queue.date[son] = queue.date[father];
queue.date[father] = cur;
}
}*/
while (index > 0) {
DateType cur = queue.date[index]; // 獲取當前下標的值
int parent = (index - 1) / 2; // 計算出父節點
if (parent >= 0) { // 檢查父節點是否合法
if (IsEstimate(cur, queue.date[parent])) { // 比較該節點與父節點的大小
break;
} else { // 當子節點大於父節點時
queue.date[index] = queue.date[parent]; // 交換他們的數據
queue.date[parent] = cur;
index = parent; // 父節點下標賦值給子節點下標繼續循環處理
}
}
}
}
// 插入
bool insert(PQ &queue, DateType index) {
if (queue.size == queue.capacity) {
cout << "內存已滿!" << endl;
return false;
}
int score = queue.size;
queue.date[queue.size] = index;
queue.size += 1;
heapUp(queue, score); //
}
出隊
有插入就有刪除。
替換後,再將堆重新排序一遍就完成出隊了。
定義相關函數:
bool deleteQueueMax(PQ &queue, DateType& index); // 刪除堆中最大的
配和實現函數:
static void heapDown(PQ &queue, int index); // 堆節點下移
具體函數實現:
// 刪除最大元素
bool deleteQueueMax(PQ &queue, DateType &index) {
if (queue.size < 1) {
cout << "堆爲空!" << endl;
return false;
}
index = queue.date[0];
queue.date[0] = queue.date[queue.size - 1];
queue.size -= 1;
heapDown(queue, 0);
return true;
}
最後還有一些零散的:
int size(PQ &queue); // 獲取對的元素個數
bool clearQueue(PQ &queue); // 清空釋放內存
// 獲取元素個數
int size(PQ &queue) {
return queue.size;
}
// 清空隊列,釋放內存
bool clearQueue(PQ &queue) {
if (queue.size < 1) {
cout << "堆爲空!" << endl;
return false;
}
queue.capacity = 0;
queue.size = 0;
delete queue.date;
return true;
}
全部測試代碼:
#include <iostream>
#include <Windows.h>
using namespace std;
#define Max 128
// 堆
typedef struct _Task {
int priority; // 優先級
int value; // 鏈表數據
}Task;
#define IsEstimate(a, b) (a.priority < b.priority) // 判斷兩數大小
typedef Task DateType; // 堆的存儲類型
// 節點
typedef struct _priorityQueue {
DateType* date; // 存儲數據指針
int size; // 當前存儲個數
int capacity; // 當前存儲容量
}PQ;
bool initPQ(PQ &queue, DateType *arr, int size); // 初始化
static void buildHeap(PQ &queue); // 建堆
static void heapDown(PQ &queue, int index); // 堆節點下移
bool insert(PQ &queue, DateType index); // 插入
static void heapUp(PQ &queue, int index); // 堆節點上移
bool deleteQueueMax(PQ &queue, DateType& index); // 刪除堆中最大的
int size(PQ &queue); // 獲取對的元素個數
bool clearQueue(PQ &queue); // 清空釋放內存
void heapDown(PQ &queue, int index) {
int father, son;
DateType cur = queue.date[index];
for (father = index; (father * 2 + 1) < queue.size; father = son) {
son = father * 2 + 1; // 找到子節點下標
// 此條判斷找到子節點的最大節點
if (son + 1 < queue.size && IsEstimate(queue.date[son], queue.date[son + 1])) {
son++;
}
// 判斷子節點的值與父節點的值的大小
if (IsEstimate(queue.date[son], cur)) {
break;
} else {
queue.date[father] = queue.date[son];
queue.date[son] = cur;
}
}
}
void buildHeap(PQ &queue) {
for (int i = queue.size / 2 - 1; i >= 0; i--) {
heapDown(queue, i);
}
}
bool initPQ(PQ & queue, DateType* arr, int size) {
if (!arr) {
cout << "數組爲空!" << endl;
return false;
}
int capacity = Max > size ? Max : size; // 選定內配的內存容量
queue.date = new DateType[capacity];
if (!queue.date) {
cout << "內存分配失敗!" << endl;
return false;
}
queue.capacity = capacity;
queue.size = 0;
if (size > 0) {
memcpy(queue.date, arr, size * sizeof(DateType)); // 內存拷貝
queue.size = size;
buildHeap(queue); // 建堆
}
return true;
}
void heapUp(PQ &queue, int index) {
/*int father, son;
for (son = index; (son - 1) / 2 >= 0; son = father) {
int cur = queue.date[son];
father = (son - 1) / 2;
if (queue.date[father] > cur) {
break;
} else {
queue.date[son] = queue.date[father];
queue.date[father] = cur;
}
}*/
while (index > 0) {
DateType cur = queue.date[index]; // 獲取當前下標的值
int parent = (index - 1) / 2; // 計算出父節點
if (parent >= 0) { // 檢查父節點是否合法
if (IsEstimate(cur, queue.date[parent])) { // 比較該節點與父節點的大小
break;
} else { // 當子節點大於父節點時
queue.date[index] = queue.date[parent]; // 交換他們的數據
queue.date[parent] = cur;
index = parent; // 父節點下標賦值給子節點下標繼續循環處理
}
}
}
}
// 插入
bool insert(PQ &queue, DateType index) {
if (queue.size == queue.capacity) {
cout << "內存已滿!" << endl;
return false;
}
int score = queue.size;
queue.date[queue.size] = index;
queue.size += 1;
heapUp(queue, score); //
}
// 刪除最大元素
bool deleteQueueMax(PQ &queue, DateType &index) {
if (queue.size < 1) {
cout << "堆爲空!" << endl;
return false;
}
index = queue.date[0];
queue.date[0] = queue.date[queue.size - 1];
queue.size -= 1;
heapDown(queue, 0);
return true;
}
// 獲取元素個數
int size(PQ &queue) {
return queue.size;
}
// 清空隊列,釋放內存
bool clearQueue(PQ &queue) {
if (queue.size < 1) {
cout << "堆爲空!" << endl;
return false;
}
queue.capacity = 0;
queue.size = 0;
delete queue.date;
return true;
}
int main(void) {
DateType arr[10];
PQ queue;
for (int i = 0; i < 10; i++) {
arr[i].priority = i + i;
arr[i].value = i * i;
}
// 初始化
initPQ(queue, arr, sizeof(arr) / sizeof(arr[0]));
for (int i = 0; i < queue.size; i++) {
cout << queue.date[i].priority << ":" << queue.date[i].value << ", ";
}
cout << endl;
DateType value;
value.priority = 23;
value.value = 23;
cout << endl << "入隊優先級23,值23 後:" << endl;
insert(queue, value); // 入隊
for (int i = 0; i < queue.size; i++) {
cout << queue.date[i].priority << ":" << queue.date[i].value << ", ";
}
cout << endl;
cout << endl << "全部出隊:" << endl;
while (deleteQueueMax(queue, value)) { // 出隊
cout << value.priority << ":" << value.value << " ";
}
cout << endl;
clearQueue(queue); // 釋放內存
system("pause");
return 0;
}
運行截圖:
冒號前面是優先級,冒號後面是節點的元素。
總結:
這種算法的代碼知悉效率是很高的,是很多地方都學不到的。
只需要將堆理解好,這篇博文也不是事,重在理解。
比系列第二篇博文到此位置,後續將介紹堆排序。
敬請期待!
至此!