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;
}

运行截图:
在这里插入图片描述
冒号前面是优先级,冒号后面是节点的元素。


总结:
这种算法的代码知悉效率是很高的,是很多地方都学不到的。
只需要将堆理解好,这篇博文也不是事,重在理解。


比系列第二篇博文到此位置,后续将介绍堆排序
敬请期待!

至此!

发布了59 篇原创文章 · 获赞 93 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/cpp_learner/article/details/105602667