C/C++ 入門核心算法:堆的企業級應用 之 堆實現優先隊列

觀看本系列博文提醒:

  1. 你將學會堆的原理算法實現
  2. 一個企業級應用:堆實現優先隊列
  3. 還有堆排序
  4. 最後還有一道檢測是否掌握堆算法作業

這已經是本系列博文的第二篇了,還沒看過第一篇博文: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;
}

運行截圖:
在這裏插入圖片描述
冒號前面是優先級,冒號後面是節點的元素。


總結:
這種算法的代碼知悉效率是很高的,是很多地方都學不到的。
只需要將堆理解好,這篇博文也不是事,重在理解。


比系列第二篇博文到此位置,後續將介紹堆排序
敬請期待!

至此!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章