1.前言
最近有点emo,写博客也没有什么特别想写的内容 这篇博客把约瑟夫环用C语言来写一下 巩固一下原来学过的知识。
2.什么是约瑟夫环
这有个历史故事还是比较有意思的:据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
简而言之,这个问题概括就是:有41个人围成一圈,顺序排号。从第1个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位?
3.算数分析问题
(以12个人举例进行画图分析)
正如同这张图所表示的:
第一次淘汰:从第一个人开始报数的时候3的倍数全部死亡
第二次淘汰:从上次死亡的后一位开始来进行从1开始的报数
…
…(循环上述的步骤)
…
直到最后判断是否剩下一个人,如果剩下一个人则结束。
经过四次循环 游戏结束 最终只剩下一位幸存者为第10位。
四.编程思想分析
(1)由于对于每个人只有死和活两种状态,我们可以对每个人的生命值进行赋值 存活为1,死亡为0。
(2)开始时每个人都是活的,所以数组初值全部赋为1
(3)循环前先判断剩余的幸存者是否大于1个
(4)如果出现淘汰的人下一个人从1开始重新开始计数
五.编程实现
函数原型:
int Josephus_problem(int n)//这里面的n为参与的总人数。
考虑到游戏人数由n传入,存储大小未定因此我们需要用到动态内存的知识,来定义一个动态数组给他们依次编号。
int* arr = (int*)malloc(n * sizeof(int));
因为在最初开始所有的人都是存活的所以就将数组初值赋为1代表存活。
for (int i = 0; i < n; i++)
{
arr[i] = 1;
}
随着报数的不断进行,总人数肯定会不断减少,并且我们需要通过判断幸存人数来判断是否结束循环 所以我们引入一个变量tmp来代表剩余人数。
int tmp = n;//幸存人数
而且有一个关键性的变量,那就是各个玩家所报出来的数字,我们用这个数字来淘汰每个报数报到3的玩家,在这里我们将他命名为count。
int count = 0;//报的号
我们需要一个循环来进行报数操作,但是在循环前我们需要判断剩余人数是否为1,如果大于1才可以正常执行下列循环操作
while (tmp > 1)
{
}
判断完剩余人数后,进行循环,通过循环来进行报数操作并且完成每位成员依次报数与报到3就将他的生命值赋为0:
for (int i = 0; i <n; i++)
{
if (arr[i] == 1)
{
count++;
if (count == 3)
{
arr[i] = 0;
tmp--;
count = 0;
}
}
}
最后加个判断 如果传入的人数为负值或者出现其他错误,直接返回-1相当于报错 我们也可以知道中途出现了错误。
for (int i = 0; i <n; i++)
{
if (arr[i] == 1)
{
return i;
}
}
return -1;
六.完整代码及运行结果
#include<stdio.h>
#include<stdlib.h>
int Josephus_problem(int n)
{
int* arr = (int*)malloc(n * sizeof(int));
for (int i = 0; i < n; i++)
{
arr[i] = 1;
}
int tmp = n;//幸存人数
int count = 0;//报的号
while (tmp > 1)
{
for (int i = 0; i <n; i++)
{
if (arr[i] == 1)
{
count++;
if (count == 3)
{
arr[i] = 0;
tmp--;
count = 0;
}
}
}
}
for (int i = 0; i <n; i++)
{
if (arr[i] == 1)
{
return i;
}
}
return -1;
}
int main()
{
printf("%d\n", Josephus_problem(人数)+1);
return 0;
}
运行结果:
以12人为例
与上面画图分析结果相同。
以41人为例
七.小结
对于约瑟夫环这个问题,最重要的就是将数学思维转换为C语言思维,最终运用代码表示出来;并且在编写代码的过程中还需要注意一些小的细节:需要用动态内存分配存储空间,还有学会运用变量这样可以使问题变得更加简便。
(如有问题,欢迎大佬指正)