一、约瑟夫环是什么?
约瑟夫环是一个古老的问题,起源于一个犹太故事。
我们可以将它简化为一个围圈丢手绢的问题,有n个同学的同学按照编号1-n依次围成一个圈在玩手绢游戏,有一个裁判在圈内正对1号同学,每数到第m个同学就淘汰该同学,最后剩下的一个同学为游戏获胜者。
例如:5个同学游戏,m为2,首先淘汰2号,接着淘汰4号,1号。此时,只剩余3号同学,他获得该游戏的胜利!
二、三个方法
1.模拟法
模拟法顾名思义就是模拟游戏的过程来实现,时间复杂度往往较高,不过有利于我们来理解(主要是想不到别的55),特点就是一步一步来进行,比较形象。时间复杂度: f ( n ) = O ( n m ) f(n)=O(nm) f(n)=O(nm)
代码如下:
public static void FindWinner(int n,int k){
boolean[] visit=new boolean[n+1];
int count=0;
int pos=1; //裁判初始时在1号小伙伴面前
int sum=n;
while(sum>1){
if(!visit[pos]){ //如果玩家在圈内
count++; //计数
if(count%k==0){ //计数达到要求
visit[pos]=true; //淘汰编号为pos的玩家
sum--; //剩余多少人?
System.out.println("小伙伴"+pos+"离开!");
}
}
pos=pos%n+1; //类似于循环队列的知识
}
for(int i=1;i<n+1;i++){
if(!visit[i]){
System.out.println("小伙伴"+i+"获胜!");
break;
}
}
}
2.递归算法
现在只要求我们找到最终的胜利者,我们是否还需要上述操作呢。我们回到题目,n个同学玩游戏,一共淘汰多少轮呢?答案是n-1轮。当我们进行完第一轮的淘汰,编号为(m-1)%n+1的同学被淘汰了 。剩下的n-1个人,组成了一个新环。给他们重新编号(被淘汰的同学的后一位为1号)。如果我们找到最后,找到了一个胜者。这个胜者不仅是环为n-1时的胜者,也是n时的胜者,只是他的编号变了,因此我们做的事情就是从1个人为环开始,建立两轮编号之间的联系
f
(
n
)
=
(
m
+
f
(
n
−
1
)
−
1
)
m
o
d
n
+
1
f(n)=(m+f(n-1)-1) mod n+1
f(n)=(m+f(n−1)−1)modn+1
代码如下:
public static int Winner(int n,int m){
if(n==1)
return 1;
return (m+Winner(n-1,m)-1)%n+1;
}
3.递推算法
看懂了递归,递推公式也就出来了。
public static int Winner(int n,int m){
int ans=1;
for(int i=2;i<=n;i++){
ans=(m+ans-1)%i+1;
}
return ans;
}
总结
约瑟夫环的问题并不难,但是我们在遇到这些数学问题时还是要先找规律,有的模拟起来可能非常困难(比如蚂蚁爬杆问题)。以上就是今天的分享。