经典算法:从约瑟夫问题说开去

By Long Luo

约瑟夫问题是每个学计算机的同学都会遇到的经典编程题,下面我们就来通过这道题对好好学习下算法和编程吧,Let's go!

一、约瑟夫问题简介

据说著名犹太历史学家Josephus有过以下的故事:

在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。

然而Josephus和他的朋友并不想遵从这个规则,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

二、约瑟夫问题算法分析

约瑟夫问题可用代数分析来求解,将这个问题扩大好了,假设现在您与m个朋友不幸参与了这个游戏,您要如何保护您与您的朋友?

只要画两个圆圈就可以让自己与朋友免于死亡游戏,这两个圆圈内圈是排列顺序,而外圈是自杀顺序,如下图所示:

image

假如使用编程来求解的话,只要将array当作环状来处理就可以了,在array中由计数1开始,每找到3个无数据区就填入1个计数,直到计数达41为止,然后将array由索引1开始列出,就可以得知每个位置的自杀顺序,这就是约瑟夫排列,41个人而报数3的约琴夫排列如下所示:

14 36 1 38 15 2 24 30 3 16 34 425 17 5 40 31 6 18 26 7 37 19 8 35 27 9 20 32 10 41 21 11 28 39 12 22 33 13 29 23

由上可知,最后一个自杀的是在第31个位置,而倒数第二个自杀的要排在第16个位置,而之前的人都死光了,所以他们也就不知道约瑟夫与他的朋友并没有遵守游戏规则了。

这是一道经典算法问题,每个学编程的同学都会遇到。一般常见的问法有以下几种:

  1. 输出自杀顺序
  2. 输出最后一个自杀同学编号

下面我们就来动手实践下,具体实现代码如下所示:

private static int Josephus(int n, int m) {
        int[] peopleArr = new int[n];
        for (int i = 0; i < n; i++) {
            peopleArr[i] = i + 1;
        }

        int index = 0;
        int length = n;
        int count = 1;

        while (length > 0) {
            if (peopleArr[index % n] > 0) {
                if (count % m == 0) {
                    // 找到要出圈的人,并把圈中人数减1
                    System.out.print(peopleArr[index % n] + "  ");
                    peopleArr[index % n] = -1;
                    count = 1;
                    index++;
                    length--;
                } else {
                    index++;
                    count++;
                }
            } else {
                // 遇到空位了,就跳到下一位,但count不加1,也就是这个位置没有报数
                index++;
            }
        }

        return index;
    }

三、递归实现

这道题也可以使用递归来实现,原理如下:

令f[n]表示当有n个候选人时,最后当选者的编号。则:
f[1] = 0
f[n] = (f[n - 1] + K) mod n

方法证明:

上述公式可以用数据归纳法简单证明其正确性:

f[1] = 0

当只有一个候选人的时候,显然结果应该是0

f[n] = (f[n - 1] + K) mod n

f[n - 1]为第n - 1次数到的id序列,则第n次就是再往下数k个,最后进行取模运算即可得到结果序列

这种算法的时间复杂度为O(N),空间复杂度为O(1),效率有所提高!

以n=5, m=3为例,一开始有这么5个人:

0 1 2 3 4

第一轮报数后2号死亡,圈子里剩下来的人的编号是这些:

3 4 0 1

这里把3号写在最前面了,因为轮到3开始报数。如果我们有办法知道n=4时的答案,即初始圈子为:

0 1 2 3

时的答案,那么可以把n=4的初始圈子的所有数x变换成(x+3) mod 5,得到:

3 4 0 1

这个恰好就是n=5时第一轮结束后的圈子状态,也就是说我们可以根据n=4的答案推出n=5时的答案。

归纳如下:

设f(n)为初始有n人时最后一个自杀的人的编号,那么有如下递推式:
f(n) = (f(n-1) + m) mod n

手工演算一下就能发现n = z时的圈子第一轮结束后(即m-1号自杀后)的圈子状态,可以由n = z - 1的初始圈子施以变换x -> (x+m) mod z得到。于是我们可以从n = 1开始(此时的答案是0),推出n=2的答案,再推出n=3,直到计算到所要求的n。

下面为具体实现:

    private static int Josephus(int n, int m) {
        int s = 0;
        for (int i = 2; i <= n; i++) {
            s = (s + m) % i;
        }

        return s;
    }

四、扩展

假如有k个好人,和k个坏人,所有人站成一圈,前k个人是好人,后k个人是坏人, 编写程序计算一个最小的m,使k个坏人都被处决,而不处决任何好人。
输入:
k 为正整数
输出:
返回: 最小的m,使k个坏人都被处决,而不处决任何好人。

算法及代码实现如下:

    public static int getMinimumM(int K) {
        /* 在这里实现功能 */
        int m = K + 1;

        while (true) {
            int index = 0;
            int size = 2 * K;
            while (true) {
                index = (index + m - 1) % size;
                if (index < K) {
                    break;
                }
                size--;
            }
            if (size == K) {
                return m;
            }
            m++;
        }
    }

以上。

本文链接:经典算法:从约瑟夫问题说开去...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容