首先,什么是优先队列?
普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征.
简单来说,每次出队,优先级最高的先出,如果优先级相同,按普通队列方式(先进先出)出队.
结构体定义
提示:
- 应有存放优先级的变量(本文中,数字越大,优先级越高)
- 应有数据域
- 应有指针域
typedef struct FQNode{
int priority;//应有存放优先级的变量
int data;//应有数据域
FQNode *next;//应有指针域
}FQNode;
typedef struct {
FQNode *front;
FQNode *rear;
}FQueue;
初始化
提示:
- 指向队头和队尾的指针是否置空?
bool initFQueue(FQueue* &q) {
q = new FQueue;
q->front = q->rear = NULL;//指向队头和队尾的指针是否置空?
return true;
}
判断是否队空
无提示
bool ifEmpty(FQueue *q) {
if (!q->front && !q->rear) {
return true;
}
return false;
}
入队
提示:
- 插入的元素有可能是队列的第一个元素!
void insertFQueue(FQueue *q,int data,int priority) {
if (!q) {
return;
}
FQNode *e = new FQNode;
e->data = data;
e->priority = priority;
e->next = NULL;
if (ifEmpty(q)) {//插入的元素有可能是队列的第一个元素!
q->front = q->rear = e;
return;
}
q->rear->next = e;
q->rear = e;
}
出队(仅用两个二级指针)
提示:
- 遍历队列找到优先级最高的结点
- 要能获得优先结点的前一个结点以便出队和释放内存
- 出队元素可能是队头元素或队尾元素
void deleteFQueue(FQueue *q) {
if (ifEmpty(q)) {
cout << "空队列!" << endl;
return;
}
FQNode **max,**e;
max = &(q->front);//指向队头
e = &(q->front->next);//指向第一个结点
while (*e) {
if ((*e)->priority>(*max)->priority) {
max = e;//指向优先结点的前一个节点
}
//记住下面这行代码
e = &((*e)->next);//继续搜索
}
if (*max == q->rear) {//出队元素是队尾元素
q->rear = (FQNode*)max;
}
//记住下面这行代码
*max = (*max)->next;//出队(已包含出队元素是第一个元素的情况)
delete *e;
}
这里有点难,我会做详细讲解.
首先,为什么要用二级指针?二级指针能同时关联两个结点,在找到优先结点后能方便的回溯到上一个结点.
在网上查二级指针,你得到的大概是这样的图(如下图左)
然而实际上的二级指针是这样的(如上图右).
其实不管是二级指针还是三级指针还是一百级指针,都可以用上图右的方式表示,这也是我们遇到指针"套娃"时最好的理解方式.
在上面的代码实现里,我让大家记住了两行代码:
FQNode **max,**e;//定义
e = &((*e)->next);//继续搜索
*max = (*max)->next;//出队
可以看出,我在写这两行代码的注释时,一个写的是"继续搜索",一个写的是"出队",意思是两者功能完全不同.可是,&和*是两个相反的运算,且max和e都属于同一类型的二级指针,按理来说两者操作是等价的啊?
我在刚开始的时候也认为它们是等价的,后面经过不断调试,才发现了问题所在.我们先把它统一一下,再做讲解:
FQNode **p;//定义
p = &((*p)->next);//继续搜索
*p= (*p)->next;//出队
从链表的删除来看
在上面讲为什么要用二级指针的时候,我画过一幅图:
其实p就指向(图中)第一个结点的next域,也就是说,*p就是(图中)第一个结点的next域.现在如果我们有指向第一个结点的指针,名字叫"one",以及指向第二个结点的指针,名字叫"two",我们可以写出如下的删除结点代码:
one->next = one->next->next;
delete two;
这里one->next和上图的*p是一样的,我们做代换得以下代码:
*p = (*p)->next;
//delete...
出队我们解释清楚了,接下来解释搜索.
FQNode **p;//定义
p = &((*p)->next);//继续搜索
*p= (*p)->next;//出队
此处省略了数据域,只保留指针域.实际上是很简单的,如果你理解了我之前画的实际上的二级指针
从内存上看
我猜,看完上面后只有一部分读者懂了,所以我从另一个角度再讲一次.
FQNode **p;//定义
p = &((*p)->next);//继续搜索
*p= (*p)->next;//出队
搜索前:
搜索后:
出队前:
出队后:
总结:在这里,*p
实际上就是某一个结点的next域的别名(这与C++引用有异曲同工之妙),更改*p的值也就更改了next的值(即它的指向),也就导致了表的重新链接.而对p的值的更改只会导致指向的变量不同,不会影响到表.
打印队列元素
无提示,使用自己喜欢的风格
void printFQueue(FQueue *q) {
FQNode *e=q->front;
while (e) {
cout << e->data << "\t" << e->priority << endl;
e = e->next;
}
}
全部代码及测试代码
#include <iostream>
using namespace std;
typedef struct FQNode{
int priority;
int data;
FQNode *next;
}FQNode;
typedef struct {
FQNode *front;
FQNode *rear;
}FQueue;
bool initFQueue(FQueue* &q) {
q = new FQueue;
q->front = q->rear = NULL;
return true;
}
bool ifEmpty(FQueue *q) {
if (!q->front && !q->rear) {
return true;
}
return false;
}
void insertFQueue(FQueue *q,int data,int priority) {
if (!q) {
return;
}
FQNode *e = new FQNode;
e->data = data;
e->priority = priority;
e->next = NULL;
if (ifEmpty(q)) {
q->front = q->rear = e;
return;
}
q->rear->next = e;
q->rear = e;
}
void deleteFQueue(FQueue *q) {
if (ifEmpty(q)) {
cout << "空队列!" << endl;
return;
}
FQNode **max,**e;
max = &(q->front);//指向队头
e = &(q->front->next);//指向第一个结点
while (*e) {
if ((*e)->priority>(*max)->priority) {
max = e;//指向优先节点的前一个结点
}
e = &((*e)->next);//继续搜索
}
if (*max == q->rear) {//出队元素是队尾元素
q->rear = (FQNode*)max;
}
*max = (*max)->next;//出队(已包含出队元素是第一个元素的情况)
delete *e;
}
void printFQueue(FQueue *q) {
FQNode *e=q->front;
while (e) {
cout << e->data << "\t" << e->priority << endl;
e = e->next;
}
}
int main(void) {
int n, data, priority;
FQueue *q;
initFQueue(q);
cout << "请输入插入元素的个数:";
cin >> n;
while (n--) {
cout << "请输入插入元素值:";
cin >> data;
cout << "优先级:";
cin >> priority;
insertFQueue(q, data, priority);
}
printFQueue(q);
cout << endl;
deleteFQueue(q);
printFQueue(q);
system("pause");
return 0;
}
最后
事实上,优先队列通常采用堆来实现.在之后讲堆的时候我们会重提优先队列.