4.9 队列应用举例
1. 求解报数问题
【问题描述】
设有 nnn 个人站成一排,从左向右的编号分别为 1∼n1\sim n1∼n ,现在从左向右按照“1,2,1,2,⋯1,2,1,2,\cdots1,2,1,2,⋯” 报数,数到“111”的人出列,数到“222”的立即站到队伍的最右端。报数过程反复进行,直到 nnn 个人都出列为止。要求给出他们的出列顺序。
【分析】
根据问题描述,假设 n=5n=5n=5 ,初始序列为:1,2,3,4,51,2,3,4,51,2,3,4,5 ,如果用队列表示这个序列,则出列顺序为:1,3,5,4,21,3,5,4,21,3,5,4,2 。
由于队列中有元素出队,队头指针会增 1,并且还有元素入队,队尾指针也增 1,如果采用顺序队,当初始空间设置的不是足够大(以 n=5n=5n=5 为例,初始空间至少设置为 8),则会引起“假溢出”,故应该使用循环队列作为本题中队列的数据结构。
【算法步骤】
- 出队一个元素,输出其编号(报数为 1 的人出列)
- 若队列不空,再出队一个元素,并将此元素从队尾入队(报数为 2 的“立即站到队伍的最右端,即入队)
【算法描述】
//循环队列,即顺序队的存储结构
#define MAXQSIZE 100 //队列可能达到的最大长度
typedef struct{
QElemType *base; //存储空间的基地址
int front; //队头指针
int rear; //队尾指针
}SqQueue;
//循环队列的初始化
Status InitQueue(SqQueue * &Q){
//构造一个空队列 Q
Q.base = new QElemType[MAXQSIZE]; //为队列分配数组空间
if(!Q.base) exit(OVERFLOW); //存储分配失败
Q.front = Q.rear = 0; //头指针和尾指针置为零,队列为空
return OK;
}
//循环队列的入队
status EnQueue(SqQueue * &Q, QElemType e){
//插入元素 e 为 Q 的新的队尾元素
if((Q.rear + 1) % MAXQSIZE == Q.front)//判断队满
return ERROR;
Q.base[Q.rear] = e; //新元素插入队尾
Q.rear = (Q.rear + 1) % MAXQSIZE; //队尾指针加 1
return OK;
}
//循环队列的出队
Status DeQueue(SqQueue * &Q, QElemType &e){
//删除 Q 的队头元素,用 e 返回其值
if(Q.front == Q.rear) //队空
return ERROR;
e = Q.base[Q.front]; //保存队头元素
Q.front = (Q.front + 1) % MAXQSIZE; //队头指针加1
return OK;
}
//判断是否队空
bool QueueEmpty(SqQueue * &Q){
return Q.front == Q.rear;
}
//销毁队列
vodi DestroyQueue(SqQueue * &Q){
free(Q);
}
//报数问题的函数
void number(int n){
int i;
ElemType e;
SqQueue * Q; //循环队列指针 Q
InitQueue(Q);
for(i = 1; i <= n; i++) //构建初始序列
EnQueue(Q, i);
printf("报数出列顺序:");
while(!QueueEmpty(Q)){ //队列不空
DeQueue(Q, e); //出队报数 1 的人
print("%d", e);
if(!QueueEmpty(Q)){
DeQueue(Q, e); //出队报数 2 的人
EnQueue(Q, e); //将其入队
}
}
print("/n");
DestroyQueue(Q); //销毁队列 Q
}
2. 双端队列
前面介绍的队列,都是从队尾入队,队首出队,并且只有一端可以入队,另一端可以出队。此外,还有一种名为双端队列的队列,即队列两段都可以进行入队和出队操作,如图 4.9.1 所示。
在双端队列中,前端进队的元素排在后端进队的元素之前;不论从前端还是从后端出队,先出的元素排在后出的元素前面。
例如,有序列 a, b, c, d
,按照图 4.9.2 所示的位置进队,则得到了图中所示的队列。
若 b
从前端出队,其他元素从后端出队,则得到序列:b, d, c, a
。
此外,还有“受限”的其他类型的双端队列,如:
- 允许两端进队,但只允许一端出队。
- 允许两端出队,但只允许一端进队。