1 什么是约瑟夫环问题?

约瑟夫,是一个古犹太人,曾经在一次罗马叛乱中担任将军,后来战败,他和朋友及另外39个人躲在一口井里,但还是被发现了。罗马人表示只要投降就不死,约瑟夫想投降,可是其他人坚决不同意。怎么办呢,他想到一个主意:
让41个人围成一个圆圈,从第一个人开始报数,数到3的那个人被旁边的人杀死。这样就可以避免自杀了,因为犹太人的信仰是禁止自杀的。结果一群人杀来杀去最后只剩下两个了,就是约瑟夫和他朋友,于是两人愉快地去投降了。

约瑟夫和朋友站在什么位置才保住了性命呢,这就是我们今天要讲的约瑟夫环问题。

2 问题的重要性

这是个BAT常用面试题,而且本质上是一个游戏,可以广泛应用于生活中,工作生活好帮手就是它了。

3 约瑟夫环抽象问题

这个问题实际在讲:N个人围成一圈,第一个人从1开始报数,报M的被杀掉,下一个人接着从1开始报,循环反复,直到剩下最后一个,那最后胜利者的初始位置在哪里?

模拟流程:
假如有5个人报数,报到3被杀,情况如下
A   B  C1  D  E  (初始位置,C第一个被杀)
D   E  A2  B  (C死后的第二次排位,A第二个被杀)
B   D  E3  (A死后的第三次排位,E第三个被杀)
B4 D  (E死后的第四次排位,B第四个被杀)
D   (D留在了最后,初始位置是4)

解决方法:
1 循环遍历法

public static int josephus(int n, int m) {
        //n个人,  0 1 2..n-1
         int[] people = new int[n];
        //人的索引
         int index = -1;
        //报数记录,  1 2 3..m
         int count = 0;
        //剩余人数  初始值为n
         int remain = n;

//为了找到最后一个幸存者的位置,假设所有人都会被杀
        while (remain > 0) {   
             index++;         //找到报数的人
             if (index == n) {  //所有人遍历一圈后从头遍历
                   index = 0;
              }
              if (people[index] == -1) { //如果当前的人被杀  跳过
                  continue;
              }

count++;  //报数
              if (count == m) {  
                  people[index] = -1;  //报数到m后杀人
                  count = 0;  //报数重置
                  remain--;   //剩余人数递减
               }
        }
        return index;
}

将41传入方法后,可得结果为30, 因为是从0开始计数,所以等价于现实世界的第31位。
如果约瑟夫也用这种常规方式去解决问题,那么就无法快速计算出自己应该站的位置了,所以一定有更简方法,那就是"递归法"。

2 递归法

模拟流程(第一种情况和上边表述相同):
假如有5个人报数,报到3被杀,情况如下
0   1   2    3   4  (假设从0开始计算位置)
A   B  C1  D  E  (初始位置,C第一个被杀)
D   E  A2  B  (C死后的第二次排位,A第二个被杀)
B   D  E3  (A死后的第三次排位,E第三个被杀)
B4 D  (E死后的第四次排位,B第四个被杀)
D  (D留在了最后,初始位置是3)

假如有4个人报数,报到3被杀,情况如下
0   1    2    3  
A   B  C1  D(初始位置,C第一个被杀)
D   A  B2  (C死后的第二次排位,B第二个被杀)
D3 A  (B死后的第三次排位,D第三个被杀)
A  (D留在了最后,初始位置是0)

可以看到,n个人报数,死掉一个人后,变成了n-1个人报数的问题,和n-1个人直接报数的区别只在于下角标。那我们来看一下下角标使怎么变化的?我们把重新排位前的位置叫old,新的位置叫new, 则情况如下:
   old   new
D  3  -> 0
A   0 -> 1
B   1 -> 2
old = (new + 3)%4   -- 为了不超过n本身  对n取模
old等价于f(n,m), n个人报数m的情况, 而new等价于f(n-1,m),即为n-1个人报数m的情况。
则最终公式为  f(n,m) = (f(n-1,m) + m)%n

递归:是把复杂问题递推为最简问题,然后将结果回归的过程。
步骤:找规律 &  找出口
则编写代码如下。

public static int josephusByRec(int n, int m) {
        if (n == 1) {  //出口
            return 0;
        }
        return (josephusByRec(n - 1, m) + m) % n;
}

4  总结

这是发生在公元66到67年间的故事,是一个来自2000年前古人的智慧,追古思今,你学会了吗?

———来自若愚小姐的算法课

约瑟夫环问题详解(java版)的更多相关文章

  1. 约瑟夫环问题详解 (c++)

    问题描述: 已知n个人(以编号0,2,3...n-1分别表示)围坐在一起.从编号为0的人开始报数,数到k的那个人出列:他的下一个人又从1开始报数,数到k的那个人又出列:依此规律重复下去,直到圆桌周围的 ...

  2. 超全详解Java开发环境搭建

    摘自:https://www.cnblogs.com/wangjiming/p/11278577.html 超全详解Java开发环境搭建   在项目产品开发中,开发环境搭建是软件开发的首要阶段,也是必 ...

  3. 「跬步千里」详解 Java 内存模型与原子性、可见性、有序性

    文题 "跬步千里" 主要是为了凸显这篇文章的基础性与重要性(狗头),并发编程这块的知识也确实主要围绕着 JMM 和三大性质来展开. 全文脉络如下: 1)为什么要学习并发编程? 2) ...

  4. 详解Java GC的工作原理+Minor GC、FullGC

    详解Java GC的工作原理+Minor GC.FullGC 引用地址:http://www.blogjava.net/ldwblog/archive/2013/07/24/401919.html J ...

  5. Protocol Buffer技术详解(Java实例)

    Protocol Buffer技术详解(Java实例) 该篇Blog和上一篇(C++实例)基本相同,只是面向于我们团队中的Java工程师,毕竟我们项目的前端部分是基于Android开发的,而且我们研发 ...

  6. 详解Java中的clone方法

    详解Java中的clone方法 参考:http://blog.csdn.net/zhangjg_blog/article/details/18369201/ 所谓的复制对象,首先要分配一个和源对象同样 ...

  7. java基础(十五)----- Java 最全异常详解 ——Java高级开发必须懂的

    本文将详解java中的异常和异常处理机制 异常简介 什么是异常? 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常. Java异常的分类和类结构图 1.Java中的所 ...

  8. 异常处理器详解 Java多线程异常处理机制 多线程中篇(四)

    在Thread中有异常处理器相关的方法 在ThreadGroup中也有相关的异常处理方法 示例 未检查异常 对于未检查异常,将会直接宕掉,主线程则继续运行,程序会继续运行 在主线程中能不能捕获呢? 我 ...

  9. 第三节:带你详解Java的操作符,控制流程以及数组

    前言 大家好,给大家带来带你详解Java的操作符,控制流程以及数组的概述,希望你们喜欢 操作符 算数操作符 一般的 +,-,*,/,还有两个自增 自减 ,以及一个取模 % 操作符. 这里的操作算法,一 ...

随机推荐

  1. php装上sqlserver驱动后仍然显示Call to undefined function sqlsrv_connect()问题

    今天老师要求本来的php+mysql改为php+sqlserver,在网上搜索了相应的教程,说是“需要下载安装Microsoft Drivers for PHP for SQL Server驱动”,下 ...

  2. nfs 存储服务

    今日内容: NFS 1.什么是nfs? network file system 网络文件系统 nfs共享存储 2.nfs能干什么? nfs 能为 不同主机系统之间 实现 文件的共享 3.为什么要使用n ...

  3. [Leetcode] 第319题 灯泡开关

    一.题目描述 初始时有 n 个灯泡关闭. 第 1 轮,你打开所有的灯泡. 第 2 轮,每两个灯泡你关闭一次. 第 3 轮,每三个灯泡切换一次开关(如果关闭则开启,如果开启则关闭).第 i 轮,每 i  ...

  4. C#控件及常用属性

    1.窗体(Form) 1.常用属性 (1)Name 属性:用来获取或设置窗体的名称,在应用程序中可通过Name 属性来引用窗体. (2) WindowState 属性: 用来获取或设置窗体的窗口状态. ...

  5. 基于操作系统原理的Linux 的用户管理

    一.实验目的 1.掌握为root用户修改密码的方法. 2.掌握创建新用户的方法. 3.掌握用户组的管理方法. 4.掌握为用户授权的方法. 二.实验内容 1.Linux的用户管理 (1)创建新用户创建新 ...

  6. springboot 集成Redis一主二从三哨兵

    1.Centos7 Redis一主二从三哨兵配置 Redis一主二从三哨兵环境搭建 2.接入过程 与集成redis单机不同的是jedis相关的配置做了修改,JedisPool换成了JedisSenti ...

  7. Flask基础(17)-->防止 CSRF 攻击

    CSRF CSRF全拼为Cross Site Request Forgery,译为跨站请求伪造. CSRF指攻击者盗用了你的身份,以你的名义发送恶意请求. 包括:以你名义发送邮件,发消息,盗取你的账号 ...

  8. RBD 基本使用 - Storage6

    块存储管理系列文章 (1)RBD 基本使用 - Storage6 (2)iSCSI 网关管理 (3)使用 librbd 将虚拟机运行在 Ceph RBD (4)RBD Mirror 容灾 Ceph 块 ...

  9. BERT论文解读

    本文尽量贴合BERT的原论文,但考虑到要易于理解,所以并非逐句翻译,而是根据笔者的个人理解进行翻译,其中有一些论文没有解释清楚或者笔者未能深入理解的地方,都有放出原文,如有不当之处,请各位多多包含,并 ...

  10. Java 学习笔记之 实例变量非线程安全

    实例变量非线程安全: 如果多个线程共同访问1个对象中的实例变量,则可能出现“非线程安全”问题. public class UnSafeHasSelfPrivateNum { private int n ...