观看本系列博文提醒:
- 你将学会堆的原理 和 算法实现;
- 一个企业级应用:堆实现优先队列;
- 还有堆排序;
- 最后还有一道检测是否掌握堆算法的作业。
这已经是本系列博文的第二篇了,还没看过第一篇博文: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;
}
运行截图:
冒号前面是优先级,冒号后面是节点的元素。
总结:
这种算法的代码知悉效率是很高的,是很多地方都学不到的。
只需要将堆理解好,这篇博文也不是事,重在理解。
比系列第二篇博文到此位置,后续将介绍堆排序。
敬请期待!
至此!