约瑟夫问题是一个经典的问题,下面来介绍约瑟夫问题及其变体的解决方法。
1. 约瑟夫问题
设有编号为1、2、3、…、n的n个人围成一个圈,从第一个人开始报数,报到m的人出圈,再从他的下一个人起从新报数,报到m的人出圈,如此下去,直到所有人全部出圈为止。给定任意的n和m,设计算法求n个人出圈的次序。
1.1 解决方案
我们尝试使用基本的数组来解决这一问题。
1.2 代码
//约瑟夫问题
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 100;
void Josephus(int a[], int n, int m)
{
int k = 0; //记录出局的人数
int num = -1; //正在报数的人编号
while (k < n)
{
for (int i = 0; i < m;)
{
num = (num + 1) % n;
if (a[num] != 0)
i++;
}
cout << a[num] << endl;
a[num] = 0;
k++;
}
return;
}
int main()
{
int n, m;
scanf("%d %d", &n, &m);
int a[N] = { 0 };
for (int i = 0; i < n; i++)
{
a[i] = i + 1;
}
Josephus(a, n, m);
return 0;
}
使用数组的解决方案,代码相当简洁,虽然可读性差了一点。那么,如果开始报数的不是1号,而是由输入决定的p号呢?下面我们来引入约瑟夫问题的变体。
2. 约瑟夫问题变体
n个小孩围坐成一圈,并按顺时针编号为1、2、3、…、n,从编号为p的小孩开始报数,由1报到m,报到m的人出圈,再从他的下一个人起从新报数,报到m的人出圈,如此下去,直到所有人全部出圈为止。请按出去的先后顺序输出小孩的编号。
2.1 解决方案
我们这次尝试使用循环队列来解决问题
2.2 代码
//用循环队列解决约瑟夫问题
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
void Josephus(int n, int p, int m)
{
//初始化编号
queue<int> arr;
for (int i = 0; i < n; i++)
{
arr.push((p - 1 + i) % n + 1);
}
int count = 1; //记录队头的报号
int data;
while (arr.size() != 0)
{
data = arr.front(); //取出队头的数据
arr.pop();
if (count == m) //如果报到m
{
printf("%d ", data);
count = 1;
continue;
}
arr.push(data);
count++;
}
return;
}
int main()
{
int n, p, m;
printf("Input n ,p and m : ");
while (scanf("%d %d %d", &n, &p, &m) != EOF)
{
if (n == 0 && p == 0 && m == 0)
{
printf("Over!\n");
break;
}
printf("Data in turn : ");
Josephus(n, p, m);
printf("\nInput n ,p and m : ");
}
return 0;
}
3. 拓展
上面我们使用了数组、循环队列来解决约瑟夫问题。不管采用什么样的数据结构,解决的算法都是一样的。而程序是由数据结构和算法组成的,这也是数据结构课程中最开始就强调的概念。解决约瑟夫问题还可以尝试使用循环链表来解决,实现思想与循环队列类似,但是实现难度要高得多,这也说明了STL极大地降低了我们解决问题的难度。