Josephus是一个著名的犹太历史学家,他有过这样的故事(来自互动百科,http://www.baike.com/wiki/Josephus):在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3个人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。Josephus想了一个办法,帮助自己和朋友逃过了这场死亡游戏。

由这个故事衍生出了一个著名的问题,叫Josephus问题:假定有n个人,编号为1到n,由第1个人开始报数,每报数到m的人就出列,直到剩下最后一个人,求最后一个人的编号,即:
     输入:编号为1到n的n长度数组,参数m
     输出:幸存者编号f(n)
在数学上,通常要得到一个复杂问题的解,可以从一些小的特例出发,再看这些特例的结论能否得到推广,因此,我们可以从m为2的情况开始。

我们先看一个m为2的情况下的特例,我们假定n = 2^k,由此得到:
     第一次遍历后,剩下n/2个人;
     第二次遍历后,剩下n/4个人;
     。。。。。。
     第k次遍历后,剩下1个人,这个人的编号为1(因为每次遍历淘汰的都是偶数位的人,编号为1的人始终处于奇数位)。
这样,当n为2的k次方的情况下,我们可以得到:f(n) = 1。
那么,接下来看看能否将这个特例推广开来。当n不为2的k次方的情况下,我们可以将n表示为:n = 2^k + u,并且u >= 0且u < 2^k,这样,我们很容易得到:n - u = 2^k,由此,我们可以推断:当n减少了u后,问题可以规约到n = 2^k的情况。
由于遍历中每2个人就会淘汰一个人,因此要减少u个人,需要遍历2u个人,那么我们从2u + 1开始,剩下的数将是2^k,并且:
     由于:u < 2^k
     所以:2u < 2^k + u,即2u < n
     因此,2u + 1 <= n
由此,可以得出结论:当m为2时,f(n) = 2u + 1 (u = n - 2^k,并且u >= 0且u < 2^k)。

接下来,我们来看m为2的结论能够得到推广,进一步来看m为3的情况。我们假定n = 3^k,则:
     第一次遍历后,剩下(2/3)*n个人;
     第二次遍历后,剩下(2/3)^2*n个人;
     。。。。。。
     第k次遍历后,剩下(2/3)^k*n个人,带入n = 3^k,得到剩下2^k个人。
这并没有达到我们希望的效果,也就是当m为2时得到的结论并不能推广到m为3时的情况,我们需要考虑其它办法。

我们在每个人通过(未淘汰)之后,就为它分配一个新编号,该新编号为当前最大值加1,这样,我们就能得到:1变为n+1,2变为n+2,3被淘汰,4变为n+3,5变为n+4,6被淘汰,。。。。。。,3k+1变为n+2k+1,3k+2变为n+2k+2,3k+3被淘汰,。。。。。。,3n被淘汰(或者幸存)。为什么最后一个编号是3n?因为每数3个人淘汰一个人,n个人数3n个人则全部淘汰。
看下面的一个例子:

这样,如果我们通过最后一个编号3n,找到3n在上一轮中的值,那么我们就有可能一直向上,找到3n最初的值,即f(n)。
在开始之前,先给出几个定义:
 1)floor(x):表示x的下限,如 floor(3.15) = 3;
 2)ceil(x):表示x的上限,如 ceil(3.15) = 4,
 3)prev(n):表示n上一轮中的值。
我们假定当前的值为N,如果N>n,则N必然存在prev(N),且:N = n + 2k + 1 或者 N = n + 2k +2
由此,我们得到:k = floor((N - n - 1) / 2)。
而由先前的编号分配方式我们得到:prev(N)=3k + 1 或者 prev(N)=3k + 2。
如果我们定义:N = n + 2k + a(a为1或者2),则:
 prev(N) = 3k + a
               = 3k + (N - n - 2k)
               = k + N - n
               = floor((N - n - 1) / 2) + N - n。
我们就得到了prev(N)的一个表达式,由此我们从3n出发,就可以:
     N = 3n
     while N > n do N = floor((N - n - 1) / 2) + N - n
     f(n) = N
这个表达式已经可以保证在n很大的情况下快速计算出f(n),但它还不够简单,我们可以通过一些技巧来简化它。

令:D = 3n + 1 - N
我们考虑用D来替换N,由于N是从3n出发,直到小于n,那么D就应该是从1出发,直到2n(因为当N=3n时,D=1,而当N=n+1时,D=2n),我们假定当前N对应的值为D(D = 3n + 1 - N),则:
 prev(D) = 3n + 1 - prev(N)
               = 3n + 1 - (floor((N - n - 1) / 2) + N - n)
               = 3n + 1 - (floor(((3n + 1 - D) - n - 1) / 2) + (3n + 1 - D) - n)
               = n + D - floor((2n - D) / 2)
               = D - floor(-D / 2)
               = D + ceil(D / 2) 
               = ceil(3/2 * D)。
(注:上面用到了一个技巧 floor(-x) = -ceil(x))
这样,我们得到:
     D = 1
     while D <= 2n do D = ceil(3/2 * D)
     f(n) = 3n + 1 - D
啊哈,一个更简单的表达式出现了。

我们是否能将m为3的结论推广到m为任意值的场景呢?
当m为任意值时,根据上面同样的定义方式,我们能够得到:1变为n+1,2变为n+2,。。。,m-1变为n+m-1,m淘汰,。。。。。。,mk+1变为n+(m-1)k+1,mk+2变为n+(m-1)k+2,。。。,mk+m-1变为n+(m-1)k+m-1,mk+m被淘汰,。。。。。。,mn被淘汰(或者幸存)。
根据同样的推导过程我们能够得到(有兴趣的朋友可以自己推导一下):
     D = 1
     while D <= (m - 1)n do D = ceil(m/(m-1) * D)
     f(n) = mn + 1 - D
到这里整个问题结束。

参考资料:
     《concrete mathematics》second edition

算法(一):Josephus问题相关推荐

  1. python删除链表满足pred的元素_python 数据结构一 之 线性表

    python数据结构教程第一课 从这里将会正式开始讲解python的一些实用的数据结构,原理加上实例源码. 一.简介 二.线性表的抽象数据类型 三.顺序表的实现 四.链接表的实现 1.单链表 2.带尾 ...

  2. Java实现Josephus约瑟夫环问题的算法

    Java实现Josephus约瑟夫环问题的算法 前言 语言:Java 环境:IntelliJ IDEA JDK版本:1.8 源码:GitHub 问题概述 N个人围成一圈,规定报数为M,第一个人从1开始 ...

  3. Josephus Problem的详细算法及其Python, Java语言的实现

      笔者昨天看电视,偶尔看到一集讲述古罗马人与犹太人的战争--马萨达战争,深为震撼,有兴趣的同学可以移步:http://finance.ifeng.com/a/20170627/15491157_0. ...

  4. Python Josephus(约瑟夫问题)算法

    约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉. 代码: // s是Python中下标值,可更改为从第s个下标值报数 // n为总人数,co ...

  5. 算法(第四版)C# 习题题解——1.3

    写在前面 整个项目都托管在了 Github 上:https://github.com/ikesnowy/Algorithms-4th-Edition-in-Csharp 这一节内容可能会用到的库文件有 ...

  6. 一文读懂约瑟夫环算法

    2020-05-25 20:13:40 作者 | 扬帆向海 责编 | 王晓曼 出品 | CSDN博客 问题描述 约瑟夫问题(有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题.在计算机编程的 ...

  7. 可由一个尾指针唯一确定的链表有_极客算法训练笔记(三),链表详细图解,别再逃避了朋友...

    目录 缓存引爆链表 链表单链表双向链表循环链表双向循环链表 LinkedHashMap实现LRU缓存,源码解析(JDK1.8) 算法 爬楼梯 算法 反转链表 算法 链表环检测 缓存引爆链表 存储结构 ...

  8. acm算法模板(1)

    1. 几何 4 1.1 注意 4 1.2 几何公式 4 1.3 多边形 6 1.4 多边形切割 9 1.5 浮点函数 10 1.6 面积 15 1.7 球面 16 1.8 三角形 17 1.9 三维几 ...

  9. 数据结构源码笔记(C语言):置换-选择算法

    //实现置换-选择算法#include<stdio.h> #include<malloc.h> #include<string.h> #include<std ...

最新文章

  1. Source Insight 基本使用(1)-使用Source Insight查看Android Framework 源码
  2. java虚拟机学习(四)类的加载过程
  3. Grove Beginner Kits基础实验 Arduino
  4. 网络流 24 题汇总(LOJ 上只有 22 题???)
  5. 【早知云世】当AI遇上云计算,其应用短板与长处
  6. python csv生成vcf
  7. 线性判别分析LDA算法与python实现
  8. 利用python通过两点构成的空间直线和平面计算交点
  9. Excel 分组后计算
  10. 在图片上涂鸦(其实就是乱画 O(∩_∩)O)
  11. 建行tendyronU盾 插入电脑突然不能自动跳转IE跳出登录密码框
  12. 当AI走进工厂,“小轴承”也可以转动“大产业”
  13. Zxing.jar下载
  14. Word文档怎么删除html标签,word怎么删除一整页的两种方法
  15. valgrind工具使用
  16. r软件自动化测试,App自动化测试工具Airtest
  17. c语言 /= 和 *= 是什么意思?
  18. windows客户端安装时运行时库版本不匹配的问题
  19. 轻量级浏览器NetSurf学习(九)-- 如何基于NetSurf打造自己的浏览器
  20. Win10 连接无线不能输入密码字符,一输入就卡死

热门文章

  1. [转载]VB中CommonDialog控件的使用
  2. oracle特殊字符转码,URL转码方法及不能被转码的特殊字符
  3. 为什么通俗流行音乐与西方古典音乐具有不同的音乐特征,请举例说明(800字)...
  4. RobotGPT:机器人需要从“功能机”向“智能机”升级
  5. 全网络系统升级保佑我吧,阿门
  6. 从我的校长生涯谈原型和原型链
  7. 艾美捷细胞计数试剂盒-8(CCK-8),一步到位
  8. matlab直线拟合的程序,MATLAB最小二乘法拟合直线的程序
  9. otsu java,opencv阈值处理--threshold函数、自适应阈值处理、Otsu处理(大津法)
  10. Optional long parameter ‘busId‘ is present but cannot be translated into a null value