约瑟夫问题

问题来源

约瑟夫问题是以弗拉维奥·约瑟夫命名的,他是1世纪的一名犹太历史学家,他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定杀掉谁,约瑟夫和另外一个人是最后留下的两个人。约瑟夫说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫把他的存活归因于运气或天意,他不知道是究竟是哪一个。

约瑟夫问题是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。

人们站在一个等待被处决的圈子里。 计数从圆圈中的指定点开始,并沿指定方向围绕圆圈进行。 在跳过指定数量的人之后,处刑下一个人。 对剩下的人重复该过程,从下一个人开始,朝同一方向跳过相同数量的人,直到只剩下一个人,并被释放。

问题即,给定人数、起点、方向和要跳过的数字,选择初始圆圈中的位置以避免被处决。

问题描述

编号为1~N 的N个士兵站成一个圆圈(编号分别为1,2,3…N),从编号为k的士兵开始依次报数(1,2…m),数到m的士兵会出列,之后的士兵再从1开始报数。直到剩下最后一个士兵,求这个是士兵编号。

解法

比较简单的做法是用循环单链表模拟整个过程。如下图所示,由5个结点组成一圈,编号分别为1,2,3,4,5。

约瑟夫问题

比如,从2号开始数3个数,即1,2,3,则4号出圈;

继续从5号开始数3个数,即1,2,3,则2号出圈;依次类推,最后一个结点为5号。
约瑟夫问题

代码实现

定义一个头指针head和尾指针tail分别指向1号结点和5号结点,定义一个当前结点指向tail结点,首先找到开始报数的编号为k结点的前一个结点。继续向后遍历m次,找到出圈结点的前一个结点,这样就可以删除出圈结点,依次类推…

约瑟夫问题

public class JosephusCycle {

    // 指向第一个结点
    private Node first;

    // 指向最后一个结点
    private Node last;

    public boolean add(Integer item) {
        // 新创建一个结点
        Node newNode = new Node(item, null);

        // 判断是否为第一个结点,需要特殊处理
        if(first == null && last == null) {
            // 新结点的next指向自身
            newNode.next = newNode;
            first = newNode;
            last = newNode;

        }else {
            newNode.next = last.next;
            last.next = newNode;
            last = newNode;
        }
        return true;
    }

    /**
     * 约瑟夫问题
     * eg.
     * 1 2 3 4 5 从第2个结点开始从1数到3
     * 依次出圈顺序为 4 2 1 3 5
     * @param n 总结点数,每个结点都有一个编号,从1开始
     * @param k 编号为k开始报数,1 2 3 ...
     * @param m 数到m的结点出圈
     */
    public void josephus(int n, int k, int m) {
        if(n < 1) {
            throw new IllegalArgumentException("结点数n不能小于1");
        }
        if(k > n) {
            throw new IllegalArgumentException("参数k不能大于n");
        }

        for(int i = 1; i <= n; i++) {
            this.add(i);
        }

        // 找到编号为k的前一个结点
        Node tmp = last;
        for(int j = 0; j < k-1; j++) {
            tmp = tmp.next;
        }
        System.out.println(String.format("第%s个结点开始,向后数%s", tmp.next.item, m));

        while (true) {
            // 向后数到1...m
            // 找到待删除的前一个结点
            for(int i = 0; i < m-1; i++) {
                tmp = tmp.next;
            }

            if(tmp.next == tmp) {
                System.out.println("最后一个出圈的结点 = " + tmp.next.item);
                break;
            }
            System.out.println("出圈的结点 = " + tmp.next.item);

            // 删除结点
            tmp.next = tmp.next.next;
        }
    }

    private static class Node {

        private Integer item;

        private Node next;

        public Node() {
        }

        public Node(Integer item, Node next) {
            this.item = item;
            this.next = next;
        }
    }

}

「更多精彩内容请关注公众号geekymv,喜欢请分享给更多的朋友哦」
geekymv

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值