算法(一):Josephus问题
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问题相关推荐
- python删除链表满足pred的元素_python 数据结构一 之 线性表
python数据结构教程第一课 从这里将会正式开始讲解python的一些实用的数据结构,原理加上实例源码. 一.简介 二.线性表的抽象数据类型 三.顺序表的实现 四.链接表的实现 1.单链表 2.带尾 ...
- Java实现Josephus约瑟夫环问题的算法
Java实现Josephus约瑟夫环问题的算法 前言 语言:Java 环境:IntelliJ IDEA JDK版本:1.8 源码:GitHub 问题概述 N个人围成一圈,规定报数为M,第一个人从1开始 ...
- Josephus Problem的详细算法及其Python, Java语言的实现
笔者昨天看电视,偶尔看到一集讲述古罗马人与犹太人的战争--马萨达战争,深为震撼,有兴趣的同学可以移步:http://finance.ifeng.com/a/20170627/15491157_0. ...
- Python Josephus(约瑟夫问题)算法
约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉. 代码: // s是Python中下标值,可更改为从第s个下标值报数 // n为总人数,co ...
- 算法(第四版)C# 习题题解——1.3
写在前面 整个项目都托管在了 Github 上:https://github.com/ikesnowy/Algorithms-4th-Edition-in-Csharp 这一节内容可能会用到的库文件有 ...
- 一文读懂约瑟夫环算法
2020-05-25 20:13:40 作者 | 扬帆向海 责编 | 王晓曼 出品 | CSDN博客 问题描述 约瑟夫问题(有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题.在计算机编程的 ...
- 可由一个尾指针唯一确定的链表有_极客算法训练笔记(三),链表详细图解,别再逃避了朋友...
目录 缓存引爆链表 链表单链表双向链表循环链表双向循环链表 LinkedHashMap实现LRU缓存,源码解析(JDK1.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 三维几 ...
- 数据结构源码笔记(C语言):置换-选择算法
//实现置换-选择算法#include<stdio.h> #include<malloc.h> #include<string.h> #include<std ...
最新文章
- Source Insight 基本使用(1)-使用Source Insight查看Android Framework 源码
- java虚拟机学习(四)类的加载过程
- Grove Beginner Kits基础实验 Arduino
- 网络流 24 题汇总(LOJ 上只有 22 题???)
- 【早知云世】当AI遇上云计算,其应用短板与长处
- python csv生成vcf
- 线性判别分析LDA算法与python实现
- 利用python通过两点构成的空间直线和平面计算交点
- Excel 分组后计算
- 在图片上涂鸦(其实就是乱画 O(∩_∩)O)
- 建行tendyronU盾 插入电脑突然不能自动跳转IE跳出登录密码框
- 当AI走进工厂,“小轴承”也可以转动“大产业”
- Zxing.jar下载
- Word文档怎么删除html标签,word怎么删除一整页的两种方法
- valgrind工具使用
- r软件自动化测试,App自动化测试工具Airtest
- c语言 /= 和 *= 是什么意思?
- windows客户端安装时运行时库版本不匹配的问题
- 轻量级浏览器NetSurf学习(九)-- 如何基于NetSurf打造自己的浏览器
- Win10 连接无线不能输入密码字符,一输入就卡死
热门文章
- [转载]VB中CommonDialog控件的使用
- oracle特殊字符转码,URL转码方法及不能被转码的特殊字符
- 为什么通俗流行音乐与西方古典音乐具有不同的音乐特征,请举例说明(800字)...
- RobotGPT:机器人需要从“功能机”向“智能机”升级
- 全网络系统升级保佑我吧,阿门
- 从我的校长生涯谈原型和原型链
- 艾美捷细胞计数试剂盒-8(CCK-8),一步到位
- matlab直线拟合的程序,MATLAB最小二乘法拟合直线的程序
- otsu java,opencv阈值处理--threshold函数、自适应阈值处理、Otsu处理(大津法)
- Optional long parameter ‘busId‘ is present but cannot be translated into a null value