昨天遇见了约瑟夫环这个问题。当时只想到了一种解决方法,用环模拟整个过程。后来又在网上看见了约瑟夫问题的数学解法,看了几遍,都没看懂,直到遇见“每天都满满的太阳”这篇博客:http://wewe39.blogbus.com/logs/121389435.html ,才完全理解了约瑟夫问题。那篇博客讲解了约瑟夫问题数学解法的各个细节,还提出了理解上可能遇见的几个困难。文章很好,但有三个关键疑点,博主还是语焉不详。所以,本文希望能够在那篇博客的基础上解释那几个关键疑点,最好先阅读原博客再来看我这篇水文,若能完全理解原博客,那么恭喜你,你很聪明;若暂时不能理解,也不要气馁,再聪明的人也有被卡住的时候,希望我这篇水文能带你绕过障碍。下文红色部分为引用原博客的,黑色部分为我的理解。
疑点1
"""
【思考过程】
首先一开始的序列
序列1: 1, 2, 3, 4, …, n-2, n-1, n
此时出队列的第一个人,位置为k,号码肯定是m%n。这个应该没有问题,也就是取余操作使得数组类似能够有循环的功能。
此时序列2: 1, 2, 3, 4, … k-1, k+1, …, n-2, n-1, n
此时k出队列,序列2中为n-1个人了。
根据序列2,得到序列3:
k+1, k+2, k+3, …, n-2, n-1, n, 1, 2, 3,…, k-2, k-1
从序列2得到序列3很简单,也就是将k+1作为开始,然后将1连到n的后面,其实只是位置的不同,但是本质两个序列是一样的。所以同样,这里也是n-1个元素。
序列3可以映射得到序列4:
1, 2, 3, 4, …, 5, 6, 7, 8, …, n-2, n-1
这里我就乱掉了,这个映射是可以做,但是映射关系是怎样的?
"""
从序列3映射到序列4,这个过程有些巧妙,但并不复杂。所谓映射,等于有一圈人,每个人身上贴一个号码,按照序列3的规律排列,再将每个人身上的号码重新排一次,排成序列4,但是保持每个人不动以及人与人之间的前后关系不变。照此,可画个映射图:
k+1 ----> 1
k+2 ----> 2
.................
.................
.................
n-2 -----> n-k-2
n-1 -----> n-k-1
n -------> n-k "序列3从k+1开始,到n-1,映射过程就是减k的过程”
1 -------> n-k+1
2 ------> n-k+2
.................
.................
.................
k-2 -----> n-k+1+(k-3)=n-2
k-1 -----> n-k+1+(k-2)=n-1 "序列3从1到k-1,映射过程就是两者之间的距离加上n-k“
从上面可以看出,序列4与序列3存在映射关系。
疑点2
“”“”
对于序列4,我们假设能够得到解,也就是最后一个退出的人的号码,设置为x。如果我们能够通过映射关系,将x在序列3中对应的号码x’求出来,那我 们就可以得到序列3的解,因为序列3其实和序列2是同一个,所以序列2的解我们也就得到了,序列2就是序列1去掉一个k,这个k对于序列1的解没有任何影 响,所以序列1的答案就是求出来的x’。
那关键就是如何通过x得到x’ ,也就是映射关系的问题。
对于序列4,如果我们要得到1到n-1序列中的值,我们也是做取余操作,只不过除数变为n-1了。
但是如何得到关系为 x'=(x+m)%n,从而得到递推式
f(i)=(f(i-1)+m)%i
还是没法理解出来。
“”“”
上述问题,可以归纳为,假设某人在序列4的位置为x,相应地,他在序列3的位置应该为多少呢?假设为x'。如何求出来x'呢?
仔细看上面我推出的映射图最后一项来看,序列4的位置为n-1,序列3的位置为k-1,两者之间有个取模的关系式,即((n-1)+k)%n=k-1(k<n),序列4的其他项也存在同样的规律,用符号替换一下上述关系式,即(x+k)%n=x',而用函数表达式形容这个关系,也就是f(i)=(f(i-1)+m)%i。f(i)为某人在序列中的位置。当然,有人会问,如果k>=n,怎么破,其实k>=n,结果也一样,这也就是原博客中出现了m%n的原因,m表示第m个人被拖出去斩了,直接用m%n代替k,上述关系式就变成了(x+m%n)%n=x',也就是(x+m)%n=x',还是一样的公式。
得出了这个数学公式,就可以根据这个数学公式写代码了。
疑点3
原博客中的代码些地方不好理解,我用黑色字体注释的方式加以说明。
”“”
递归操作的代码:
非递归操作的代码,也就是递推
“”“
【循环链表解法】
#ifdef __cplusplus extern "C" { #endif #include <stdio.h> #include <stdlib.h> #include <assert.h> struct _node { int key; struct _node *next; }; int main() { int i, N, M; struct _node *pnow, *head; while(1) { pnow = (struct _node *)malloc(sizeof(struct _node)); pnow->key = 1; head = pnow; printf("Please enter two numbers:\t"); scanf_s("%d %d", &N, &M); if(N <= 0 || M <= 0) { printf("N < 0, or M < 0, it's wrong\n"); return -1; } /* initial the circle linklist */ for(i = 2; i < N; i++) { pnow->next = (struct _node *)malloc(sizeof(struct _node)); assert(pnow->next != NULL); pnow = pnow->next; pnow->key = i; } pnow->next = head; /* delete the M'th node */ while(pnow->next != pnow) { for(i = 1; i < M; i++) { pnow = pnow->next; } printf("this time deletes %d\n", pnow->next->key); head = pnow->next; pnow->next = pnow->next->next; free(head); } printf("this time deletes %d\n", pnow->key); free(pnow); } return 0; } #ifdef __cplusplus } #endif
【致敬】
希望我的讲解能够有助于快速理解约瑟夫问题,但是,这一切都是建立在”每天都满满的阳光”的文章的基础上。非常感谢“每天都满满的太阳”,“每天都满满的太阳“,如果你看到了这篇文章,请原谅我这样“糟蹋”你的博文。
【附录】
http://blog.csdn.net/solofancy/article/details/4211770 这篇博客补充了约瑟夫问题的另外几种解法。