Thinking系列:Josephus Problems

来源:互联网 发布:java可以从事什么职业 编辑:程序博客网 时间:2024/06/07 14:07

  慢慢也能静下来心来做那么点事了, 希望坚持下去来个thinking系列吧, 主要还是偏数学,算法,数据结构这块的一些思考吧。今天献丑写下第一篇了: Josephus问题。

  个人打算就只写比较抽象些的东西,如果哪个地方需要更加细节点,欢迎读者下面评论看情况再编辑添加了:(好吧,这应该算公告了..)

 1. 问题描述:

     n个人(编号1~n),从1开始报数,报到m的退出(自杀),剩下的人继续从1开始报数。按顺序输出列者编号。

2. 如果你愿意想基本都会考虑到的两种策略: 数组 || 循环链表; 如果自己学习过循环链表的话 往往会用到这个例子吧。下面我就来谈谈对这两种策略的看法:怎么说呢, 我一直认为再复杂的东西都是由简单的东西叠加起来的,再复杂也只是里面的逻辑比较"错乱"而已;所以一直强调基础真的一点不为过了; 大家都说"程序 = 数据结构 + 算法";(在后来加入了架构);而还有一种附加说法,当你的数据结构选定之后,剩下的算法并不多了(于是乎,数据结构也就跑到第一位了,当然对这些比较抽象的观点,大家仁者见仁,智者见智了,不一定争个你对我错了)。而每种数据结构都有其独特的优势,当然肯定也有其对应的弊端了;数组:随机存储,但不适合动态改变大小 || 链表:动态改变,但不可随机存储   最典型的也就提的这个点了,至于对数组或链表进行一些改造使其在其弊端方面有所改观这里就不考虑了(比如链表中的跳跃表,可持续化数据结构;数组表示的静态链表等这里暂时不考虑了)我们如果比较仔细地推敲这个问题,我们并不难推出用链表来实现会比较简单了(而且还是用循环链表哦:循环链表基础你搞定么有撒~?我还在继续学习吧..),毕竟题目中如果能够动态的去删除掉报过数的人,那么接下来的报数就不会像数组实现那样对数组元素进行条件判断了(无辜地增加时间复杂度);当然了如果你对循环链表的基础ok的话,解决这个问题便不难实现了,具体实现代码还是以自然形势描叙吧:

1.创建一个循环链表(链表长度为n), 用tail指针唯一表示这个链表

2.while(tail != tail->next)  逢第m个人就删除这个元素(顺便做些附加操作撒,打印相关信息啥的)("按顺序输出编号)

3. 当只剩下最后一个元素的时候(tail == tail->next), 也就是最后一个存活者,同理打印这个信息,只是最后一个存活者比较有意义而已(如果全部让他们死的话,干脆一起自杀就好了撒。。)


大致思想也就这么些了, 大致觉得解决这个问题的时候再辅助一代码验证完思想,我们再反过来看这个问题的话; 这个解决方案的缺点在哪里呢?删除的话首先呢,我们可以考虑到如果你是对原链表进行操作的话,将会破坏原有数据结构(可持续化),线程不安全; 然后呢? 链表嘛, 占用空间撒;毕竟它的那个自引用指针不是免费的午餐(时间换空间呗);再然后呢? 如果我们一味追求效率再增加使用空间可以使我们的效率更高么?(tradeoff)我相信肯定是有的了, 如果你愿意坚持实现的话;有兴趣可以去试试了, 哈哈 对于我来说目前属于过早优化了先不管它了吧,就提上面几点了。

用数组实现其实也不那么复杂了,基本思想其实跟循环链表一样的, 只是删除操作变为将数组里面的元素置为0而已(假设初始化为1), 只是后面的向下计数操作需要判断数组元素值而已(为0,此次计数失效,继续前进)


对这个问题的求解大致先谈这么多了, 而如果我们改变问题?? 只需要输出最后一个幸存者的编号?我们还需要一个个地顺序输出吗?

好吧, 不愿意废脑子的童鞋现在告诉你答案了,显然是有撒~!  当然咯, 有兴趣地可以自己先推推了!

以下描述来源于http://www.cnblogs.com/qlwy/archive/2012/07/11/2587254.html

我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):
   k   k+1   k+2   ... n-2, n-1, 0, 1, 2, ... k-2
并且从k开始报0。
现在我们把他们的编号做一下转换:
k     --> 0
k+1 --> 1
k+2 --> 2
...
...
k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x'=(x+k)%n (我们只需要最后通过最后一个编号一步步反推回去,不就是这个幸存者的初始编号么~)

递推式子:

f[1]=0;
f[i]=(f[i-1]+m)%i;   (i>1)

这里面其实还有个小技巧了,自己去想的话,当然也可以考虑从1开始计数了,那样f(1) = 1; 但是呢f[i]就可能变为0了撒(此时需要 +i 了)

#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 ("The winner is %d/n", s+1);}

谈到这,貌似可以勾起了你再次好好品味的兴趣哈~? 继续努力呗!


这篇博客大致就说这么多了, 关于这个专题网上还有蛮多解释很精辟的了~! 而且对于这个问题可以一步步抽象(最后貌似可以抽象成起始点随便指定)

欢迎大家批评指正了, 你的看法是偶前进的动力; 真心的,还不怎么习惯写博客了,毕竟我一直还是那么浮躁的人, 感觉说出来就好了...




原创粉丝点击