约瑟夫环问题 ( 最简单的数学解法)

基本问题描述:
已知n我的(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为1的人开始报数,数到m的那我的出列;他的下一我的又从1开始报数,数到m的那我的又出列;依此规律重复下去,直到圆桌周围的人所有出列。(也相似于变态杀人狂问题)一般解决这类问题时咱们把编号从0~n-1,最后结果+1即为原问题的解。一般,咱们会要求输出最后一位出列的人的序号。那么这里主要研究的是最后一个出列的人的序号要怎么肯定。html

当n,m数据量很小的时候,咱们能够用循环链表模拟约瑟夫环的过程。当模拟到人数等于1的时候,输出剩下的人的序号便可。
具体解法这种方法每每实现起来比较简单,并且也很容易理解。可是时间复杂度倒是很糟糕的,达到了O(nm),这样的话,其实在n,m比较大的时候(nm达到10^8或者更大),那么要得出结果每每须要耗费很长的时间,可是咱们能够运用一点数学上的技巧,将最后结果推导出来。c++

为了简化出列的过程:
首先咱们把这n我的的序号编号从0~n-1(理由很简单,因为m是可能大于n的,而当m大于等于n时,那么第一个出列的人编号是m%n,而m%n是可能等于0的,这样编号的话可以简化后续出列的过程),当数到m-1的那我的出列,所以咱们编号完成以后,开始分析出列的过程:
第一次出列:
一开始的时候,全部人的编号排成序列的模式即为:
0,1,2,3,4,5...n-2,n-1
那么第一次出列的人的编号则是(m-1)%n1,那么在第一我的出列以后,从他的下一我的又开始从0开始报数,为了方便咱们设k1 = m%n1(n1为当前序列的总人数)那么在第一我的出列以后,k1则是下一次新的编号序列的首位元素,那么咱们获得的新的编号序列为:
k1,k1+1,k1+2,k1+3...n-2,n-1,0,1,2...k1-3,k1-2 (k1-1第一次已出列)
那么在这个新的序列中,第一我的依旧是从0开始报数,那么在这个新的序列中,每一个人报的相应数字为:
0,1,2,3....n-2
那么第二次每一个人报的相应数字与第一次时本身相应的编号对应起来的关系则为:
0 --> k1
1 --> k1+1
2 --> k1+2
...
n-2 ---> (k1+n-2)%n1(n1为当前序列的总人数,由于是循环的序列,k1+n-1可能大于总人数)
那么这时咱们要解决的问题就是n-1我的的报数问题(即n-1阶约瑟夫环的问题)
可能以上过程你仍是以为不太清晰,那么咱们重复以上过程,继续推导剩余的n-1我的的约瑟夫环的问题:
那么在这剩下的n-1我的中,咱们也能够为了方便,将这n-1我的编号为:
0,1,2,3,4...n-2
那么此时出列的人的编号则是(m-1) % n2(n2为当前序列的总人数),一样的咱们设k2 = m % n2,那么在这我的出列了之后,序列重排,重排后新的编号序列为:
k2,k2+1,k2+2,k2+3...n-2,n-1,0,1,2...k2-3,k2-2 (k2-1第一次已出列)
那么在这个新的序列中,第一我的依旧是从1开始报数,那么在这个新的序列中,每一个人报的相应数字为:
1,2,3,4....n-2
那么这样的话是否是又把问题转化成了n-2阶约瑟夫环的问题呢?
后面的过程与前两次的过程如出一辙,那么递归处理下去,直到最后只剩下一我的的时候,即可以直接得出结果
当咱们获得一我的的时候(即一阶约瑟夫环问题)的结果,那么咱们是否能经过一阶约瑟夫环问题的结果,推导出二阶约瑟夫环的结果呢?
借助上面的分析过程,咱们知道,当在解决n阶约瑟夫环问题时,序号为k1的人出列后,剩下的n-1我的又从新组成了一个n-1阶的约瑟夫环,那么
假如获得了这个n-1阶约瑟夫环问题的结果为ans(即最后一个出列的人编号为ans),那么咱们经过上述分析过程,能够知道,n阶约瑟夫环的结果
(ans + k)%n(n为当前序列的总人数),而k = m%n
则有:
n阶约瑟夫环的结果数组

(ans + m % n)%n,那么咱们还能够将该式进行一下简单的化简:code

当m<n时,易得上式可化简为:(ans + m)% nhtm

而当m>=n时,那么上式则化简为:(ans % n + m%n%n)% n
即为:(ans % n + m%n)% n
而 (ans + m)% n = (ans % n + m%n)% n
所以得证
(ans + m % n)%n = (ans + m)% n
这样的话,咱们就获得了递推公式,因为编号是从0开始的,那么咱们能够令
f[1] = 0; //当一我的的时候,出队人员编号为0
f[n] = (f[n-1] + m)%n //m表示每次数到该数的人出列,n表示当前序列的总人数
而咱们只须要获得第n次出列的结果便可,那么不须要另外声明数组保存数据,只须要直接一个for循环求得n阶约瑟夫环问题的结果便可
因为每每现实生活中编号是从1-n,那么咱们把最后的结果加1便可。blog

代码:递归

#include <stdio.h>
int main()
{
    int n, m, i, s = 0;
    printf ("N M = ");
    scanf("%d%d", &n, &m);
    for (i = 2; i <= n; i++)
    {
        s = (s + m) % i;
        printf("%d\n",s);
    }
    printf ("\nThe winner is %d\n", s+1);
}
相关文章
相关标签/搜索