------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------
约瑟夫问题
今天,公司有个同事问我一个问题,是关于约瑟夫问题的,然后我就跟他讲解了半天,直到他弄懂了为止。其实,在这个过程中,不仅他从中学到了不少收获,而且我从中也得到了不少锻炼,我觉得这道题还是蛮不容易懂的,而且可以从这个问题中学到很多知识,在很多面试中也会问到,如果能把这道题写出来,对我们的工资又多了一点筹码啊,在这里我就班门弄斧了,这仅仅只是我个人的见解,如果有技术大牛不同意我的看法,可以提出来。
咱们就开始正题了啊,问题是这样的:耶稣有13个门徒,其中有一个就是出卖耶稣的叛徒,请用排除法找出这位叛徒:13人围坐一圈,从第一个开始报号:1,2,3,1,2,3...。凡是报到“3”就退出圈子,最后留在圈子内的人就是出卖耶稣的叛徒。请找出它原来的序号。
问题说到这里,有很多人就会提问或者是疑问啊(大致都提了这些问题):
1、一轮(这里代表从第一个人开始报数,直到最后一个人,也就是我们这里的第十三个人)报数完了之后,又怎么从第一个人重新开始报数呢?
2、怎么去表示某个人已经被踢出去了呢?
3、报数为3的这个人报数完了之后,就会被踢出去,那怎么样让紧接着他后面的人开始从1开始重新开始报数呢?
4、怎么确定参与循环的人数呢?
1、一轮(这里代表从第一个人开始报数,直到最后一个人,也就是我们这里的第十三个人)报数完了之后,又怎么从第一个人重新开始报数呢?
2、怎么去表示某个人已经被踢出去了呢?
3、报数为3的这个人报数完了之后,就会被踢出去,那怎么样让紧接着他后面的人开始从1开始重新开始报数呢?
4、怎么确定参与循环的人数呢?
如果大家把这几个问题解决之后,那么这道题的答案就已经出来了。
那么我们就来捋一捋思路啊。
第一步:从题目中我们可以获取几个变量,一个是耶稣门徒的人数,
第一步:从题目中我们可以获取几个变量,一个是耶稣门徒的人数,
我们定义为people_num,报数的号码,定义为step,还有一个就是
我们是通过什么,每次报数的人都是下一个人,这里我们就要通过
循环,遍历每个人,所以这里我们要通过数组的下标,取出每个人
的位置,所以就还得需要一个下标变量,我们定义为pos (position位置)
第二步:我们得把每个人的位置存着,所以我们的定义一个数组,名为people
第三步:每次报数完了之后,就会有个人出去,所以每次都得减少一个人,
直到数组里面没有人之后,就不再报数这时我们就可以确定循环条件。
第四步:我们之前所说的把报数为3的人踢出去,其实是个假象,
我们并没有把它踢出去,而是将它的值修改成0,如果把这个人踢出去,
就改变数组的结构,我们就不知道它原来的位置了。所以我们这里判断
此时报数的人编号不为0,我们就可以报数。
第五步:还是判断,如果报数的这个人编号不为0,而且报的号是3,
那么我们就让这个人“踢出去”,将报数为3的这个人的编号改为0,
那么下次循环的时候,就会跳过他。
第六步:还是判读,如果报数的这个人的编号为13,那么的下一个
报数的人得从第一个人开始,所以我们得把下标重置为0
伪代码:
//如果报数的人大于0,就说明数组里面还有人,就还得循环
while(报数的人数 大于 0)
{
//如果报数人的编号不为0,那么他的报数就应该加1
if(报数人的编号 不为 0)
{
报的数字加1,也就是step++
}
if(报的数为3 并且 报数人的编号不为0)
{
那么这个人就可以被踢出去了,并把他的位置打印出来
并且把这个被踢出去的人的编号改为0
此时,参与报数的人就会少一个
}
//下一个人报数,所以的遍历数组
下标加1
if(如果这个报数的人的位置超过数组的长度,就表示已经越界,我们就得从第一个人开始报数)
{
下标重置为0
}
if(如果报的号是3就踢出去,我们又得从1开始报数,所以我们得把报数重置){
报数重置为0
}
}
//如果报数的人大于0,就说明数组里面还有人,就还得循环
while(报数的人数 大于 0)
{
//如果报数人的编号不为0,那么他的报数就应该加1
if(报数人的编号 不为 0)
{
报的数字加1,也就是step++
}
if(报的数为3 并且 报数人的编号不为0)
{
那么这个人就可以被踢出去了,并把他的位置打印出来
并且把这个被踢出去的人的编号改为0
此时,参与报数的人就会少一个
}
//下一个人报数,所以的遍历数组
下标加1
if(如果这个报数的人的位置超过数组的长度,就表示已经越界,我们就得从第一个人开始报数)
{
下标重置为0
}
if(如果报的号是3就踢出去,我们又得从1开始报数,所以我们得把报数重置){
报数重置为0
}
}
//实际代码的实现:
#import <stdio.h>
#define ALL_PEOPLE_NUM 13
#define STEP 3
int main(int argc, const char * argv[])
{
int people_num = ALL_PEOPLE_NUM;
int step = 0;
int pos = 0;
int people[ALL_PEOPLE_NUM];
for(;pos < people_num; pos++)
{
people[pos] = pos + 1;
}
//此时的pos为13
pos = 0;//因为我们得从第一个人开始报数
while(people_num > 0)
{
if(people[pos])
{
step++;
}
if(step == STEP && people[pos])
{
printf("%d\n",people[pos]);
people[pos] = 0;
people_num--;
}
pos++;
if(pos == ALL_PEOPLE_NUM)
{
pos = 0;
}
if(step == STEP)
{
step = 0;
}
}
return 0;
}
第二种方法:使用for循环的嵌套,其实,思路都是一样的
#import <stdio.h>
#define ALL_PEOPLE_NUM 13
#define<span style="white-space:pre"> </span>STEP 3
int main(int argc, const char * argv[])
{
int people[ALL_PEOPLE_NUM] = {0};//这里的0就是每个人的编号
int step = 0;
int pos;//这个代表每个人的位置
for(int j = 0; j < ALL_PEOPLE_NUM; j++)
{
//这句话就会从第一个人开始报数,直到最后一个人,但是没有取完
//比如13个人,就只取出了3、6、9、12,所以如果要找出最后一个人,就得在最外面再加一层for循环
for(pos = 0; pos < ALL_PEOPLE_NUM;pos++)
{
//如果报数人的编号为0,就参与报数
if(people[pos] == 0)
{
step++;
//如果代码能执行到这里,说明这个报数人的编号为0
//并且如果报数人报的数为3,就将他“踢出去”,变为非0的值
if(step == STEP)
{
people[pos] = -1;
step = 0;
printf("%d\n",pos+1);
}
}
}
}
return 0;
}
注意:使用嵌套for循环,效率很低,因为不管数组里面还有没有人,它都会去循环,时间复杂度会提高,消耗更多的内存资源,所以不建议使用。
第三种方法:使用递归(既然给大家讲得详细一点,我就去查了一些资料)
假设下标从0开始,0,1,2 .. m-1共m个人,从1开始报数,报到k则此人从环出退出,问最后剩下的一个人的编号是多少?
现在假设m = 10 k = 3,那么它的下标就是
0 1 2 3 4 5 6 7 8 9
第一个人踢出去之后的序列为:
0 1 3 4 5 6 7 8 9
接下来,就是第三个人开始报数 即:
3 4 5 6 7 8 9 0 1 (*)
我们把该式转化为:
0 1 2 3 4 5 6 7 8 (**)
则你会发现:((**)+3) % 10则转化为(*)式了
也就是说,我们求出9个人中第9次出环的编号,最后进行上面的转换就能得到10个人第10次出环的编号了
设f(m,k,i)为m个人的环,报数为k,第i个人出环的编号,则f(10,3,10)是我们要得结果
当i = 1时,f(m,k,i) = (m+k-1)%m
当i!= 1时,f(m,k,i) = (f(m-1,k,i-1)+k)%m
所以代码如下:
#import <stdio.h>
int fun(int m,int k,int i);
int main(int argc, const char * argv[])
{
for(int i = 1;i < 13; i++)
printf("第%d次出环:%d\n",i,fun(13,3,i)+1);
return 0;
}
int fun(int m,int k,int i)
{
if(i == 1)
return (m+k-1)%m;
else
return (fun(m-1,k,i-1)+k)%m;
}
}
第四种,通过数学方法 //约瑟夫环问题数学解法,时间复杂度为O(n)
代码如下:
#import <stdio.h>
#define N 13
#define M 3
int main(int argc, const char * argv[])
{
int i, s = 0;
for (i = 2; i <= N; ++i)
s = (s + M) % i;
printf("叛徒 is %d\n", s + 1);
return 0;
}