仔细看了算法导论第六章,想实现里面的优先队列算法,结果调了好半天的bug才调对,写篇博客记录一下,写的不好之处,跪求各位dalao指点。
算法导论中的优先队列使用数组实现的最大堆,我选择的语言是C++,并且用类封装了这个优先队列,话不多说,我就边贴代码边说明吧_(:3 」∠)_
#pragma once
#include<stdlib.h>
#include<iostream>
using namespace std;
#define MIN 0
#define PARENT(index) (index / 2)
#define LEFT(index) (index * 2)
#define RIGHT(index) (index * 2 + 1)
#define SWAP(a,b,q) \
NODE temp = q[a];\
q[a] = q[b]; \
q[b] = temp;
typedef struct node{
int dat; //数据
int priority;//优先级
}NODE;
class pQueue{
private:
int heapsize; //初始队列长度
int max; //队列的初始最大长度(如超过可增长)
NODE *pQ; //指向结构体数组的指针
void heapify(NODE* q, int index);//调整堆
public:
pQueue(int maxsize);
~pQueue();
void bulidPrioQueue(NODE* arr, int initsize);
int extractMaxPriority(NODE* q);
void insert(NODE* q, int dat, int prio);
void increase_prio(NODE* q, int index, int num);
void decrease_prio(NODE* q, int index, int num);
int search(NODE *q,int value);
void showQueue(NODE *q);
};
首先定义了一个结构体NODE来存储优先队列中的元素(内容见代码注释),由于还没有加模板,我这里假设数据是int型。
除了构造和析构函数,对于优先队列的操作,我这里定义了这些:
bulidPrioQueue:建立优先队列(其实是建立大根堆)
extractMaxPriority:提取优先队列中的根元素,并调整堆
insert:往优先队列中插入一个元素
increase_prio:提升队列中某个元素的优先级
decrease_prio:降低队列中某个元素的优先级
search:根据队列中元素的值找到队列元素的下标
showQueue:其实就是一个按照优先级从大到小的堆排序输出
构造函数中我做了一些初始化:
pQueue::pQueue(int maxsize){
heapsize = 0;
pQ = NULL;
max = maxsize;
}
析构函数中我将指向数组的指针释放,这就意味着申请结构体数组必须用动态申请(malloc,calloc等),不过这不是必须的,似乎在工程中(比如写一个服务器)才需要这么写。(不知道说的对不对/(┐「ε:)/)
pQueue::~pQueue(){
if (pQ != NULL){
free(pQ);
}
}
下面是一个关键函数:调整堆。
这个函数由于复用的比较多,给我带来了不少bug,一些零碎的就不提了,尤其要注意的是,由于是优先队列,在优先级相同的情况下,应该尽量保证先入队的先出队,所以前面的两个if语句的顺序不能调换.
void pQueue::heapify(NODE* q, int index){
int largest = index;
int l = LEFT(index);
int r = RIGHT(index);
if (l <= heapsize && q[l].priority > q[index].priority){
largest = l;
}
if (r <= heapsize && q[r].priority > q[largest].priority){
largest = r;
}
//优先级相同的情况下,左儿子的优先级高于右儿子
if (largest != index){
SWAP(largest,index,q);
heapify(q, largest);//递归调整堆,建议改为循环调整
}
}
初始化优先队列操作,这个函数从倒数第二排从右往左第一个有儿子的节点开始调整堆,从堆底向堆顶构造大根堆,保证了高优先级在上。
void pQueue::bulidPrioQueue(NODE* arr, int initsize){
pQ = arr;
heapsize = initsize;
for (int i = initsize / 2; i >= 1; i--){
heapify(pQ, i);
}
}
提取一个堆顶元素。这个函数类似单步的堆排序操作。
int pQueue::extractMaxPriority(NODE* q){
if (heapsize > 1){
SWAP(heapsize, 1, q);
heapsize--;
heapify(q, 1);
return q[heapsize + 1].dat;
}
return q[heapsize--].dat;
}
往优先队列中插入一个元素。算法导论中插入操作的思想是先将带插入元素的优先级置为最低(这里是0),放在数组末尾(也就是堆的右下角)然后对这个元素执行优先级提升操作,让它提高到应有的位置。
void pQueue::insert(NODE* q, int dat,int prio){
if (heapsize < max){
q[++heapsize].dat = dat;
q[heapsize].priority = MIN;
}
else{
realloc(q,(++heapsize + 1)*sizeof(NODE));
q[heapsize].dat = dat;
q[heapsize].priority = MIN;
max++;
}
increase_prio(q, heapsize, prio);
}
优先级提升操作。这个操作很关键,它一定程度上保证了同优先级元素,后入队的后出队。(但也不是一定能保证,比如数组中的元素优先级为:3,2,3。这时插入一个优先级为3的元素,那么由于提升优先级的操作,数组将变成这样:3,3(新插入的),3,2。这时新插入的元素在左儿子,用类似堆排序的方法打印队列的时候,新插入的高优先级元素可能会“插队”。这似乎没什么好的办法解决,dalao们有什么好办法请指教!
void pQueue::increase_prio(NODE* q, int index,int prio){
int p = PARENT(index);
if (p == 0){
//说明是第一个元素,没有父母
cout << "Alread bigest!" << endl;
return;
}
if (prio <= q[index].priority) {
//优先级没有提升
cout << "Priotrity need to be bigger!" << endl;
return;
}
q[index].priority = prio;
while (p >= 1 && prio > q[p].priority){
//只有优先级大于(不是大于等于)父节点才往上提,这保证了同级之间的先来后到
SWAP(p, index, q);
index = p;
p = PARENT(index);
}
return;
}
有升级就自然有降级,降级操作比较简单,关键还是是调整堆的操作。
void pQueue::decrease_prio(NODE* q, int index,int num){
if (index > heapsize){
cout << "Out of heapsize!" << endl;
return;
}
q[index].priority = num;
heapify(q, index);
}
这个按值寻找元素的操作就是简单的遍历数组。
int pQueue::search(NODE *q, int value){
for (int i = 1; i <= heapsize; i++){
if (q[i].dat == value){
return i;
}
}
return 0;
}
另开辟一片内存,将原数组复制过去(不能改变原队列),进行类似堆排序操作,打印优先队列。
void pQueue::showQueue(NODE *q){
//使用堆排序显示优先队列
int hz = heapsize;
NODE * shower = (NODE*)calloc(heapsize+1, sizeof(NODE));
if (shower != NULL){
for (int i = 1; i <= heapsize; i++){
//复制原队列元素到新开的内存进行堆排序
shower[i].dat = pQ[i].dat;
shower[i].priority = pQ[i].priority;
}
while (heapsize >= 1){
cout << extractMaxPriority(shower) << ",";
}
heapsize = hz;
free(shower);
}
else{
cout << "Memory fault!" << endl;
}
}
测试代码
#pragma once
#include"priorityQueue.h"
#define MAXSIZE 10 + 1 //第0号元素不使用,故优先队列最多MAXSIZE - 1个元素
#define VIP1 1
#define VIP2 2
#define VIP3 3
int main(){
pQueue prioQ(MAXSIZE);
NODE* arr = NULL;
int heapsize = 0;
int top = 0;
arr = (NODE *)calloc(MAXSIZE, sizeof(NODE));//自动初始化为0,所以用优先级为0作为队列结束标志
if (arr != NULL){
//测试数据
arr[1].dat = 24;
arr[1].priority = VIP2;
arr[2].dat = 14;
arr[2].priority = VIP1;
arr[3].dat = 317;
arr[3].priority = VIP3;
arr[4].dat = 17;
arr[4].priority = VIP3;
for (int i = 1; i < MAXSIZE; i++){
if (arr[i].priority != 0){
heapsize++;
}
else{
break;
}
}
prioQ.bulidPrioQueue(arr, heapsize);
prioQ.showQueue(arr);
cout << endl;
prioQ.insert(arr, 100, VIP1);
prioQ.insert(arr, 99, VIP2);
prioQ.insert(arr, 101, VIP3);
prioQ.showQueue(arr);
cout << endl;
prioQ.increase_prio(arr, prioQ.search(arr, 99), VIP3);
prioQ.showQueue(arr);
cout << endl;
prioQ.decrease_prio(arr, prioQ.search(arr, 99), VIP1);
prioQ.showQueue(arr);
cout << endl;
cout << top << endl;
}
else{
cout << "Memory fault" << endl;
}
system("pause");
exit(0);
}