模拟约瑟夫环问题
循环数组模拟
问题描述:N个人围成一圈,从第一个人开始从1开始报数,报到m的人出圈,剩下的人继续从1开始报数,报到m的人出圈;如此往复,直到所有人出圈。(模拟此过程,输出出圈的人的序号)
循环的开始和结束:循环的结束取决于圈内是否还有“人”,可以用一个变量alive表示初始人数,每一次出圈,alive - 1。判断alive是否非0即可。
while(alive > 0)
每一次循环,就是“过”一个人,但是,这个人有两种不同的状态:在圈内和不在圈内;在圈内就报数,number+1,不在圈内就不参与报数,number不变。
假设有N个int元素的数组,每一个int元素表示一个“人”;并且,取值为0和1, 1表示在圈内,0表示不在圈内,所以,如果这个人在圈内,number + 1;如果这个人不在圈内,number + 0。那么,在报数的时候,不需要考虑这个人在不在圈内(每一个人都需要加1或加0,所以,可以在这块优化一下程序)。
void joseph(int count, int doom) {
int alive = count; //幸存人数
int number = 0; //计数,当number==doom时,淘汰这个人
int index = 0; //下标,为总人数-1
int *circle = NULL; //根据需求设为循环数组,存储每个人
//用calloc()函数申请得到的空间,自动初始化每个元素为0
//所以,0表示在这个人在约瑟夫环内,1表示这个人出圈,即“淘汰”
circle = (int *) calloc(sizeof(int), count);
//只要幸存人数大于0,则一直进行循环
while(alive > 0) {
number += 1- circle[index]; //每轮到一个人报数,不管是"0"还是"1"都进行计数
if(number == doom) { //当number==doom时,就要淘汰当前这个人
/*
淘汰一个人需要做四步操作:
1、输出这个人的位置
2、把这个人的状态从在圈内"0"改为不在圈内"1"
3、幸存人数alive--
4、 计数器number归零
*/
alive == 1 ? printf("%d", index+1) : printf("%d,", index+1);
circle[index] = 1;
alive--;
number = 0;
}
//与总人数count取余,则可以使index在0~count-1之间 一直循环,达到循环数组的目的
index = (index +1) % count;
}
printf("\n");
free(circle); //结束后一定要释放circle所申请的空间
}
链表模拟
数学分析
初始情况
一共n个人(编号0,1,2… n-1),组成一个环。第一个人从1开始报数,报数到m的人出环,然后,下一个人重新开始从1开始报数,直到环中只有一个人。
分析:
第一个出环的人编号是 (m-1)%n,设之为(k-1)
剩下的n-1个人组成了一个新的约瑟夫环(以编号为k==m%n的人开始):
k k+1 k+2 … n-2, n-1, 0, 1, 2, …,k-3, k-2
现在我们把他们的编号做一下转换:
x’ -> x
k --> 0
k+1 --> 1
k+2 --> 2
…
…
k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗!
x ->x'?(这正是从n-1时的结果反过来推n个人时的编号!)
0 -> k
1 -> k+1
2 -> k+2
…
…
n-2 -> k-2
变回去的公式 x’=(x+k)%n
那么,如何知道(n-1)个人报数的问题的解?只要知道(n-2)个人的解就行了。(n-2)个人的解呢?只要知道(n-3)的情况就可以了 ---- 这显然就是一个递归问题:
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果就是f[n]
递推公式
f[1]=0;
f[i]=(f[i-1]+k)%i = (f[i-1] +m%i) % i = (f[i-1] + m) % i ; (i>1)
1.递归:
#include <stdio.h>
int josephus(int n, int m) {
if(n == 1) {
return 0;
}
else {
return (josephus(n-1, m) + m) % n;
}
}
int main() {
int n, m;
while (scanf("%d", &n) == 1) {
if (!n) {
break;
}
scanf("%d", &m);
int result = josephus(n, m);
printf("%d\n", result+1);
}
return 0;
}
2.迭代:
#include <stdio.h>
int main() {
int n, m, i, result;
while (scanf("%d", &n) == 1) {
if (!n) {
break;
}
scanf("%d", &m);
result = 0;
for (i = 2; i <= n; i++) {
result = (result + m) % i;
}
printf("%d\n", result + 1);
}
return 0;
}