队列是我们在数据结构中所学的受限制的线性表,符合“先进先出”,常使用在一些“公平”等待的场景中。
在机试中C++的标准库中(#include <queue>)可以进行队列的实现,下列代码使用了队列的常用功能。( push(i)、pop()、front()、empty() )
#include <queue>
#include <cstdio>
using namespace std;
int main(){
queue<int> myQueue;//初始化队列,元素都是int类型
for (int i = 0; i < 5; ++i) {
myQueue.push(i);//入队
}
while (true){
if(myQueue.empty()){//队列是否为空
printf("myQueue is empty!\n");
break;;
}
printf("front of myQueue is %d\n",myQueue.front());//打印队首元素
myQueue.pop();//出队
}
}
例题1 约瑟夫问题
描述
n 个小孩围坐成一圈,并按顺时针编号为1,2,…,n,从编号为 p 的小孩顺时针依次报数,由1报到m ,当报到 m 时,该小孩从圈中出去,然后下一个再从1报数,当报到 m 时再出去。如此反复,直至所有的小孩都从圈中出去。请按出去的先后顺序输出小孩的编号。
输入
每行是用空格分开的三个整数,第一个是n,第二个是p,第三个是m (0 < m,n < 300)。最后一行是:
0 0 0
输出
按出圈的顺序输出编号,编号之间以逗号间隔。
样例输入
8 3 4
0 0 0
样例输出
6,2,7,4,3,5,1,8
题目分析
样例的大意是有8个孩子围成一圈,从3号孩子开始报数,每次数到4,那个孩子就要离开,直至没有孩子。我们可以通过以下的图发现,队首从3开始,8个孩子正好到序号为2的孩子,之后又会重新连到孩子3(循环队列)。
那把问题抽象化后发现:
(1)从序号p开始入队,入队n个,即到序号p-1为止;
for (int i = p, j = 0; j < n; ++j) {
//i 用来遍历孩子的编号
//j 用来记录已经遍历的孩子的数量
children.push(i);
++i;// p->p+1->p+2->...->n->1->...->p-1
if(i > n){
i = 1;
}
}
(2)要让报数到m的孩子离开,我们可以换种方式书写,即从队首开始报数并出队,若报的不是m,则归队,而报的正好是m,就不归队。
代码示例
//约瑟夫问题
#include <cstdio>
#include <queue>
using namespace std;
int main(){
int n,p,m;
while (true){
scanf("%d%d%d",&n,&p,&m);
if(n == 0 && p == 0 && m == 0){
break;
}
queue<int> children;//队列中的元素是孩子的编号
//把 第一轮 要喊编号的孩子排好队
for (int i = p, j = 0; j < n; ++j) {
//i 用来遍历孩子的编号
//j 用来记录已经遍历的孩子的数量
children.push(i);
++i;// p->p+1->p+2->...->n->1->...->p-1
if(i > n){
i = 1;
}
}
//喊号的过程
int num = 1;//将要喊的编号
while (true){
int cur = children.front();// cur是队首孩子的编号,喊1
children.pop();//喊完了,队首出队
if(num == m){
//检查一下刚才喊的号码是不是m,是的话不需要归队了,不用push(cur)
num = 1;//下一个同学喊的号就是1
if(children.empty()){
//最后一个同学出来了
printf("%d", cur);
break;
}
else{
//还有同学在喊号
printf("%d,",cur);
}
}
else{
//喊的号码不是m,继续喊2,3,..,m-1
num = num + 1;
children.push(cur);//喊的不是m,刚刚出队的重新归队
}
}
}
}