约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
相关的题目有:猴子选大王问题等
解决这类 问题可以用循环环的删除方法解决这类问题,当时相关的复杂度较高。这里我们推导一个数学公式来进行求解。
我们先举一个相关的例子:假设n=5, m=3
我们看出重新编号的情况与其上一个情况是一一对应的,即图中的蓝色框线。红色圆框表示每次重新编号后应该剔除的数字。
i-1 的情况是 i 重新编号情况下删除编号为2的元素后重新排序的结果。
例如:n=3的情况是n=4重新编号情况下,删除元素2后的再排序结果,所有n=3情况下的编号应该与n=4重新编号情况下编号一一对应,即红箭头0对应0。这样最后删除的元素是1,安装上述对应过程可以找出最终其编号为3。
现在我们看下相关的对应情况:即如果知道n-1情况的编号,怎么得到最总n情况的编号。
我们知道在n序列中第一次删除的元素为(m-1)%n,我们用符号k-1表示;那么k位置(k=m%n)报数又开始为0,即对删除数后的序列重新排序。
假设胜出者的编号在n-1重新编号序列(就是下面序列中的编号)中为x,那么他在n-1情况下(上面序列中的)的编号(即对应n序列中实际的编号)为:(x+k)%n代入k=m%n化简得到(x+m)%n
即f(n) = ( f(n-1) + m ) % n
同理n-1的情况是从0到n-2的序列,同样的方法可以得出
f(n-1) = ( f(n-2) + m) % (n-1)
这样要求出最终的结果依次需要递归求出上一次的编号然后转换即可
如果只要求求最后结果地推公式是
y = (y + m) % i 其中 i 大于1且小于n。
程序为:
public static int numDelete(int n, int m){
int index = 0;
if (n != 1){
for (int i = 2; i<=n; i++){
//f(n) = (f(n-1)+m)%n递归n,m为报数剔除的人
index = (index + m) % i;
}
}else{
index = 0;
}
return index;
}
如果需要得出每次删除的元素,需要实现递归函数
f(n,m,i)其中i表示第i次被删除的实际编号:
//递归求出每次出局人的编号
public static int numDeleteDetail(int n, int m, int i){
//当i=1时, f(n,m,i) = (n+m-1)%n
//当i!=1时, f(n,m,i)= ( f(n-1,m,i-1)+m )%n
if (i == 1){
return (n + m - 1) % n;
}else{
return (numDeleteDetail(n-1, m, i-1) + m) % n;
}
}
System.out.println(numDelete(n));
for (int i=1; i<=n; i++){
System.out.println("第"+i+"次出局的人为:"+numDeleteDetail(n,3,i));
}
结果为:
5
3
第1次出局的人为:2
第2次出局的人为:0
第3次出局的人为:4
第4次出局的人为:1
第5次出局的人为:3