中午在食堂打饭,真是一个令人头疼的事情,去食堂的路上也总是步伐匆匆,迟一点去,就会深刻领悟什么叫做人山人海了。相比较学生来说,打饭阿姨毕竟是少数,在每个窗口都有人的时候,我们就得排队等待,队列最前面的一个学生打完饭离开,后面排队的人才可以继续向前走,直到轮到自己。虽然费时,但是秩序和规则却是我们每个人都应该遵守的,也只能抱怨自己来的迟了。这里描述的就是现实生活中普遍存在的排队的情形。
一、队列的定义
用电脑的时候,有时候机器会处于疑似死机的状态,鼠标点什么似乎都没有用,双击任何快捷方式都不动,就当你快失去耐心,打算reset的时候,突然它就像酒醒了一样,把你刚才点击的所有操作全部按照顺序执行了一遍,这其实是因为操作系统中的多个程序需要通过一个通道输出,而按照先后次序排队等待造成的。
前面的例子体现出来的数据结构就是队列(queue)。队列是一种只允许在一端进行删除操作,在另一端进行插入操作的线性表。允许插入的一端称作队尾 (rear),允许删除的的一端称为队头 (front,或者称为队首) 。队列的数据元素又叫做队列元素。在队尾插入一个队列元素称为入队,从队头删除一个队列元素称为出队 。可知队列是一种典型的先进先出 (FIFO——first in first out) 结构。
二、队列的实现
1.普通队列
同样可以使用数组来模拟队列。需要用一个“指针”来指示队头front,用另外一个“指针”来指示队尾rear。队列的操作包括入队、出队、取队首元素和判断队列是否为空等:
#include<iostream> using namespace std; const int MAX = 1000; int queue[MAX]; int front = 0; //队首“指针”,也就是队首对应数组的下标 int rear = 0; //队尾“指针”,其实是下一个入队元素存放位置(真实队尾应该是rear-1) void push(int n){ //入队(没有考虑数组已被存满无法再入队的情况) queue[rear++] = n; } int pop(){ //出队(没有考虑队列为空的情况) return queue[front++]; } int getfront(){ //取队首元素(没有考虑队列为空的情况) return queue[front]; } bool empty(){ //判断队列是否为空 return front==rear; } void print(){ //依次输出从队首到队尾的元素(测试需要) for(int i=front;i<rear;i++) cout<<queue[i]<<" "; cout<<endl; } int main() { for(int i=1;i<=5;i++) push(i); print(); cout<<getfront()<<endl; cout<<pop()<<endl; print(); push(6);push(7); print(); while(!empty()){ //全部出队 cout<<pop()<<endl; } return 0; }
2.循环队列
使用上面的方法,在入队出队的过程中,队首、队尾“指针”front和rear都在不断增大,那么很容易出现数组下标溢出的情况。并且我们还发现数组的前部(下标0~front-1)的区间被闲置了。解决问题的方法是当front和rear达到数组长度MAX的时候都重置成0,这个时候就成了循环队列:
void push(int n){ //入队 queue[rear] = n; rear = (rear+1)%MAX; } int pop(){ //出队 int t = queue[front]; front = (front+1)%MAX; return t; }
在入队和出队的过程,rear和front互相追赶,如果rear追到front说明队列满了,如果front追到rear说明队列为空。但是现在会出现一个新问题,那就是:空队特征是front == rear
, 队满时也会有front == rear
,可知判断条件将出现二义性。那么如何解决这个问题呢?可以设置一个标记变量flag,例如在入队时设置flag=1,出队时设置flag=0,那么结合标记变量的值就能区分开空队列和满队列的情况了:
#include<iostream> using namespace std; const int MAX = 6; //设置了一个较小的循环队列容量,正好测试后面的数据 int queue[MAX]; int front = 0; //队首“指针”,也就是队首对应数组的下标 int rear = 0; //队尾“指针”,其实是下一个入队元素存放位置 int flag = 0; //标记最近的操作是入队还是出队 bool empty(){ //判断空队列 return flag==0 && front==rear; } bool full(){ //判断满队列 return flag==1 && front==rear; } void push(int n){ //入队 if(full()) return; //队列满,不能再入队 flag = 1; queue[rear] = n; rear = (rear+1)%MAX; } int pop(){ //出队 if(empty()) return -1; //队列空,不能出队(返回一个特殊值-1) flag = 0; int t = queue[front]; front = (front+1)%MAX; return t; } int getfront(){ //取队首元素 if(empty()) return -1; //队列空,不能取队首元素(返回一个特殊值-1) return queue[front]; } void print(){ //依次输出从队首到队尾的元素(测试需要) if(empty()) return; //空队列不处理 int last = rear; if(last<=front) last += MAX; //rear小于front,特殊处理 for(int i=front;i<last;i++) cout<<queue[i%MAX]<<" "; cout<<endl; } int main() { for(int i=1;i<=5;i++) push(i); print(); cout<<getfront()<<endl; cout<<pop()<<endl; print(); push(6);push(7); print(); while(!empty()){ //全部出队 cout<<pop()<<endl; } return 0; }
三、经典例题:使用队列解决约瑟夫问题
再来看前面介绍的约瑟夫问题,其实也可以用循环队列解决问题。开始时编号1~n依次入队;每次数到m的人出圈,其实可以认为队列里前1~m-1个人依次出队并入队,第m个人出队;如此重复操作,直到队列为空为止:
#include<iostream> using namespace std; const int MAX = 110; int queue[MAX]; int front = 0; //队首“指针”,也就是队首对应数组的下标 int rear = 0; //队尾“指针”,其实是下一个入队元素存放位置 int flag = 0; //标记最近的操作是入队还是出队 bool empty(){ //判断空队列 return flag==0 && front==rear; } void push(int n){ //入队 flag = 1; queue[rear] = n; rear = (rear+1)%MAX; } int pop(){ //出队(返回了出队元素) flag = 0; int t = queue[front]; front = (front+1)%MAX; return t; } int main() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) push(i); while(!empty()){ //队列里前m-1人依次出队并再次入队 for(int i=1;i<m;i++) push(pop()); //第m个人出队 cout<<pop()<<" "; } return 0; }
四、利用结构体实现循环队列
【选择阅读】也可以将队列封装成一个结构体,在结构体里使用成员函数实现队列的功能,通过调用结构体变量的成员函数实现入队、出队、判断队列是否为空:
#include<iostream> using namespace std; //使用结构体封装循环队列 const int N = 10000; struct queue{ int pool[N]; int front,rear,flag; queue(){ //构造函数 front = rear = flag = 0; } //判断队列是否为空 bool empty(){ return flag==0 && front==rear; } //判断队列是否为满(存放队列元素的数组没有剩余空间) bool full(){ return flag==1 && front==rear; } //入队(如果队列已满,返回false) bool push(int n){ if(full()) return false; flag = 1; pool[rear] = n; rear = (rear+1)%N; return true; } //出队(如果队列为空,返回false) bool pop(){ if(empty()) return false; flag = 0; front = (front+1)%N; return true; } //取队首元素(如果队列为空,返回一个特殊值-1) int getfront(){ if(empty()) return -1; return pool[front]; } }; void print(queue q){ //依次输出从队首到队尾的元素(测试需要) if(q.empty()) return; //空队列不处理 int last = q.rear; if(last<=q.front) last += N; //rear小于front,特殊处理 for(int i=q.front;i<last;i++) cout<<q.pool[i%N]<<" "; cout<<endl; } int main() { queue q; for(int i=1;i<=5;i++) q.push(i); print(q); if(!q.empty()) cout<<q.getfront()<<endl; q.pop(); print(q); q.push(6);q.push(7); print(q); while(!q.empty()){ //全部出队 cout<<q.getfront()<<endl; q.pop(); } return 0; }