问题引入
约瑟夫环是一种经典的益智问题,一个圈共有N个人(N为不确定的数字),第一个人的编号为0或者1(两个都可以,看你的程序如何编写),假设这边我将第一个人的编号设置为1号,那么第二个人的编号就为2号,第三个人的编号就为3号,第N个人的编号就为N号,现在提供一个数字M,第一个人开始从1报数,第二个人报的数就是2,依次类推,报到M这个数字的人出局,紧接着从出局的这个人的下一个人重新开始从1报数,和上面过程类似,报到M的人出局,直到N个人全部出局,请问,这个出局的顺序是什么?
事例分析
我们先看一组例子分析。假设有10个人,每报到3的人出局,那么结果是什么呢?
然后报到3的人出局变成下图
然后开始下一轮报数
继续循环
当到末尾时再返回第一个成环报数
以此类推 ,随后出局的人是7 1 8 5 10 最后剩的人是4。
这样看起来逻辑不是特别复杂,我们便有了第一种用数组模拟环进行运行结果。
解决方案一
这种方案主要采用循环判断的方法一个个找出出局人。
#include<stdio.h>
int main()
{
int arr[100] = { 0 };
int peo = 0, n = 0;
int i = 0, count = 0, d = 0;
scanf("%d %d", &peo, &n);
for (i = 0; i < peo; i++)
{
arr[i] = i + 1;//初始化数组让里面元素为1,2,3便于后续操作
}
while (count < peo)//找到n人结束
{
i = i % peo;//让i的范围再不断循环假设有三人,0 1 2 0 1 2 0 1 2
if (arr[i] != 0)
{
d++;
}
if (d == 3)
{
printf("%d ", arr[i]);
arr[i] = 0;
d = 0;
count++;
}
i++;//每次对i进行加一
}
return 0;
}
这充分运用了取余形成循环的特点,也是比较通俗易懂的方式。下面然我们看看方案二,一种神奇的规律。
解决方案二(神奇的规律)
让我们把视角再返回到第一个事例,10个人报倒3出局。(以下红色为新序列排号,黑色为最初排号)
我们第一次数到三,显然我们知道是第三个人,即3,它可以表达为 3%10=3.第二个人是什么样子呢?
是第6个人,此时他的序号因为消失一人变成5, 是不是可以表达为 (3-1+3)%9=5,这里为什么是9而不是10,答案十分简单,因为此时一共也只有9人,没有10人。再次继续下一循环。
此时出局的人是第九人,那么他是不是可以模仿前面写成 (5-1+3)%8=7,即(i-1+n)%m(i 前一个出局数的序号,初始为0;n要报到出局的数; m当前总人数)到了这里相信你与我最初发现它一样,激动于这是不是一条普遍规律。我们接着来验证。
出局人是第二人,我们接着写。 (7-1+3)%7=2,对于环形我们第一个循环问题显然也是可以的。 继续操作。
同理可写 (2-1+3)%6=4,我们接着操作。
同理可写 (4-1+3)%5=1,继续
同理可写 (1-1+3)%4=3,这个公式同样适用
接着下一个
同理可写 (3-1+3)%3=2,
同理可写 (2-1+3)%2=0,此时我们发现10的下标不是0,而是2,我们不难发现此时要出局的人位于队尾,刚好是m的整数倍,摸m之后为0,因此我们对这种情况要特别处理。(i-1+n)%m(i 前一个出局数的序号,初始为0;n要报到出局的数; m当前总人数)如果计算结果为0,就输出序号为m的数。也只有他符合取余为0.此时的 i应当为1.
(1-1+3)%1=0,我们输出队尾元素。即4.
基于以上分析,我们可以写出如下代码。
#include <stdio.h>
int main()
{
int arr[100] = { 0 };
int i=0, n=0,peo=0 ,i2 = 0;
int c = 0;
scanf("%d %d", &peo, &n);
for (i = 1; i <= peo; i++)
{
arr[i] = i;
}//此时i=1开始初始化,是为了更好与我们发现的规律吻合
//i=0开始,只需略微修改一下代码即可
for (i = 0; i < peo; i++)
{
int tmp = (c + n)%(peo - i);
//这里用i表示循环的次数peo - i正好是当前人数。
//c初始为0,输出后为tmp - 1,对应原来的序列数减一。
if (tmp == 0)
{
printf("%d ", arr[peo-i]);
c = 0;
}
else
{
printf("%d ", arr[tmp]);
c = tmp - 1;
//此时我们要将出局人后面的人全都提前一位,去除空位存在。
for (i2 = tmp; i2 < peo - i; i2++)
{
arr[i2] = arr[i2 + 1];
}
}
}
return 0;
}
相信你看完上述代码与分析可以明白这个循环的意义。
方案二的循环看起来有些繁琐,当我们学完链表时,可以用链表优化这个循环。期待以后我们可以一起优化吧。
到这里这篇文章就结束了,感谢坚持看到这的读者。大家如果喜欢就点点赞收藏吧!期待我们下次再见。