约瑟夫问题的一般形式是:N个人(或更一般地,对象)围成一圈,从某个人开始报数,每报到第M个人,该人就必须离开圈子,接着从下一个人重新开始报数,这个过程一直持续到只剩下一个人为止
一、简化版
N个人站在一行,从左到右顺次报数,报到 M 的人出列, 接着从下一个人重新开始报数,直到最后一个人报完数
1.输入N 与 M
#include <stdio.h>
int main ()
{int n, m;scanf("%d %d", &n, &m);return 0;
}
2.我们将这N个人编号 ,从 1 到 n
注意:数组下标从零开始,而从1开始报数
int order[n];for(int i = 0; i < n; ++i)order[i] = i+1;
2.定义计数器,并初始化为0
int num = 0;
3.循环
当计数器等于 M 时,需要复位为0. 然后从下一个人继续报数,直到最后一个人才停止报数,
显然 需要循环来实现 计数器的更新与计数
直到最后一个人才停止报数,这就是循环结束条件
int k = 0; //从第1个人开始,下标为零while (k < n) //直到最后一个人报完数截至{++num; //num初始化为0,开始报数 if (num == m){printf("%-3d", order[k]); //-表示左对齐,3表示宽度为3num = 0; //计数器复位} ++k; //下一个人接着报数}
连起来就是:
#include <stdio.h>
int main(void)
{int n, m;scanf("%d %d", &n, &m);int order[100];for (int i = 0; i < n; ++i)order[i] = i + 1;int num = 0;//计数器int k = 0;//第几个人while (k < n){++num;if (num == m){printf("%-3d", order[k]);num = 0;}++k;}return 0;
}
以下是测试结果:
二、约瑟夫问题
1.关于下标
下标 k 应介于 0到n-1 之间 ,想到取模
k = (k+1) % n;
该表达式既完成了 下标加一操作, 又通过取模 将下标限制在 0到n-1 范围之内
2.循环结束条件
如果循环条件不变, 显然, 这将是一个死循环, 因为 下标k永远小于n
题目说,只要圈中只剩下一个人就停止报数, 那么, 圈中的人数就可以作为循环结束条件
但我们最好把 圈中人数大于0 作为循环结束条件
因为只剩下一个人的时候,就算他是一个人在报数,程序正确的话,最终也会得到出列顺序,这样就免去了我们单独在循环体外考虑 ta的下标k是多少 的麻烦
int in_circle = n;while(in_circle > 0)
{
......;
}
3.相应的人需要退出
我们如果不限制已经报数到M的人不再报数,
int num = 0; //计数器
int in_circle = n; //圈中的人数
int k = 0; //从下标为0的人开始报数
while (in_circle > 0)
{++num; //报数if (num == m){printf("%-3d", order[k]);num = 0; //计数器复位--in_circle; //人数少一}k = (k + 1) % n; //下标更新
}
就会发现,编号会重复
所以这个时候,我们只需选择将编号清零,并判断即可
int num = 0;//计数器int in_circle = n;int k = 0;while (in_circle > 0){if (order[k] != 0) //只有在圈内,才能报数进行下一步操作{++num;if (num == m){printf("%-3d", order[k]);num = 0;--in_circle;order[k] = 0;}}k = (k + 1) % n;}
三、实践
#include <stdio.h>
#define MAXN 20void CountOff( int n, int m, int out[] );int main()
{int out[MAXN], n, m;int i;scanf("%d %d", &n, &m);CountOff( n, m, out ); for ( i = 0; i < n; i++ )printf("%d ", out[i]);printf("\n");return 0;
}/* 你的代码将被嵌在这里 */
void CountOff( int n, int m, int out[] )
{int in_circle = n; //圈中人数int num = 0; //计数器int order = 1; //退出顺序static flag[100]; //标志位,假设最多只有100个人//静态数组,默认所有元素为0int i = 0; //下标0表示第一个人while(in_circle > 0){if(!flag[i]) //只有在圈内的人才进行报数{++num;if(num == m){out[i] = order; //排好顺序++order;in_circle--; //人数少一num = 0; //计数器复位flag[i] = 1;} }i = (i+1)% n; //下一个人}
}