约瑟夫问题的三种解法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuechen_gemgirl/article/details/87931274

约瑟夫问题是个很经典的问题,没事玩杀人游戏,N个人站成一圈,从1开始报数,报到M时必须死掉,不想死的那个人站在哪个位置,可以幸免于难(描述不详细,凑合看哈)。
  方法一:这个问题初看可以看到,这不就是一个游戏么,游戏怎么说就怎么做,暴力模拟吧。模拟过程见程序:

int main(){
	int n,m;
	cin >> n >> m;
	int cnt = 0 ,dead = 0 , i = 0;
	while(dead <= n){
		i++;
		if(i > n) i = 1;  //当计数超过n时,需要回到1
		if(!vis[i]) cnt ++;  //报数是只有没有出列的人才能报数
		if(cnt == m) {   //报数时报到m的人需要出列
			cnt = 0;   //下一轮需要从0开始报数
			dead ++;    //出列的人个数
			vis[i] = 1;  //标记其已经出列
			if(dead == n) //当第n个人出列时,它就是安全的
		    	    cout << i ;
		}
	} 	
 	return 0;
}

方法二:在上面程序中,那些已经退出的人每次都被点到,但因为vis状态是不存活的,所以不报数,那么后面多出了好多无用的循环次数,那么我们可以考虑将已经出列的人让他真正的出列,不再出现在游戏的环中。我们可以考虑用链的形式将所有人串起来,当报到M时,将其出列。从链起的环中去掉,因此对于每个人,增加一个属性值next,代表他的下一个人的编号。去掉一个人的操作如图:
在这里插入图片描述
这样便可以让模拟的过程去掉大量不需要的重复,提高效率。

struct monkey{
	int num;
	int next;
};
monkey a[306];
int main(){
	int n,m,live,count = 0,cur,pre	;
	cin >> n >> m;
	for(int i = 1;i< n; i++){
		a[i].num = i;
		a[i].next = i+1;
	}
	a[n].num = n, a[n].next = 1;
	live= n;
	cur = 1;
	pre = n;
	while(live> 1){
		count ++;
		if(count == m){
			a[pre].next = a[cur].next;
			live-- ;
			count = 0;			
		}
		else{
			pre = cur;
		}
		cur = a[cur].next;
	}
	cout << a[cur].num << endl;
	return 0;
}

方法三:数学方法
  看下图,先是n个人玩这个游戏,编号从0->n-1编号,数m退出,当第一个m-1号人退出后,就变成了n-1个人玩这个游戏了,将这n-1个人重新编号,如下图,会发现是由原来的编号 - m得到这重新编的号。那么这时问题变成了n-1个人玩这个游戏了,机智的你会发现问题没变,只是问题规模变了,这是不是有点递归的味道。那么想想递归式,设f(n)表示n个人数m退出时问题的解,那么f(n-1)是n-1个人数m退出时问题的解,假设f(n-1)已求出,那么:
  f( n ) = f(n - 1) + m;
 因为考虑到如图中红色字样的编号为 n-m、n-m+1、n-m+2…加过m之后会超出n,那么需要将等式更改为:
  f( n ) = (f(n - 1) + m)%n;
  那么,当得知f(n-2)的解时,反推至f(n-1)时,需要编号不超过n-1,因此等式为:
  f(n-1)=(f(n-2) + m) % n-1;
  那么推广到一般:f( i ) = (f (i - 1) + m )% i;
  那么递归的边界是什么呢,想想当只有一个人玩的时候f(1) = 1;那么程序便可以用递归写了,当然,也可以反之从已知的1推出未知的n的解。
在这里插入图片描述
具体程序如下:
/数学解法/
int main(){
int n,m;
cin >> n >> m;
int f = 0;
for(int i = 2;i<= n;i++){
f = (f+ m)%i;
}
cout <<f+1 << endl;
return 0;
}

猜你喜欢

转载自blog.csdn.net/xuechen_gemgirl/article/details/87931274
今日推荐