159-解决约瑟夫环的问题(两种方法)

题目如下:
据说著名犹太历史学家 Josephus 有过以下故事:在罗马人占领乔塔帕特后,39 个犹太人与 Josephus 及他的朋友躲到一个洞中,39 个犹太人决定宁愿死也不要被敌人抓到,于是决定了一种自杀方式,41 个人排成一个圆圈,由第 1 个人开始报数,报数到 3 的人就自杀,然后再由下一个人重新报 1,报数到 3 的人再自杀,这样依次下去,直到剩下最后一个人时,那个人可以自由选择自己的命运。这就是著名的约瑟夫问题。现在请用单向环形链表得出最终存活的人的编号。

n 表示环形链表的长度, m 表示每次报数到 m 就自杀

解题方法1(递归)

如果只求最后一个报数胜利者的话,我们可以用数学归纳法解决该问题,为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人 继续从0开始报数。求胜利者的编号。

我们知道第一个人(编号一定是(m%n)-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):

k k+1 k+2 … n-2, n-1, 0, 1, 2, … k-2并且从k开始报0。

现在我们把他们的编号做一下转换:
k --> 0
k+1 --> 1
k+2 --> 2


k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解: 例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x’=(x+k)%n
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]。
递推公式:
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
有了这个公式,我们要做的就是从n-1顺序算出f[i]的数值,最后结果是f[n]。 因为实际生活中编号总是从1开始,我们应该输出f[n]+1。

#include<stdio.h>
int LastRemaining_Solution(unsigned int n, unsigned int m)
    {
    
    
        if(n==0)
            return -1;
        if(n==1)
            return 0;
        else
            return (LastRemaining_Solution(n-1,m)+m)%n;
    }

int main()
{
    
    
	printf("%d\n",LastRemaining_Solution(5,3));
	//输出结果为编号3,也就是第4个人
	return 0;
}

方法2(常规方法)

#include <stdio.h>
#include <stdlib.h>

//约瑟夫环
int JosephProblem(int n)
{
    
    
	//创建n个标记
	int *arr = (int *)calloc(n,sizeof(int));
	//默认每个都是0 ,0表示还在游戏,1表示退出
	int i = 0;//arr下标
	int count = n;//还在游戏的人数
	int tmp = 0;//报数器

	while(count > 1)//有1个以上的人在游戏
	{
    
    
		if(arr[i] == 0)
		{
    
    
			tmp++;
			if(tmp == 3)//退出游戏
			{
    
    
				arr[i] = 1;
				count--;//还在游戏的人数减一
				tmp = 0;//报数器还原
			}
		}
		i = (i+1)%n;//此时使用i++;是error的  因为我们要处理环形
	}

	//找到还在游戏的人
	for(i=0;i<n;i++)
	{
    
    
		if(arr[i] == 0)
			break;
	}
	free(arr);

	return i+1;
}

int main()
{
    
    
	int n=JosephProblem(5);
	printf("%d\n",n);
	//输出结果为4,也就是第4个人
	return 0;
}

猜你喜欢

转载自blog.csdn.net/LINZEYU666/article/details/113366455