Java解决约瑟夫环问题

1 约瑟夫环

约瑟夫环问题起源于一个犹太故事。约瑟夫环问题的大意如下:

  罗马人攻占了乔塔帕特,41个人藏在一个山洞中躲过了这场浩劫。这41个人中,包括历史学家Joseph(约瑟夫)和他的一个朋友。剩余的39个人为了表示不向罗马人臣服,决定集体自杀。大家决定了一个自杀方案,所有这41个人围成一个圆圈,由第1个人开始顺时针报数,每报数为3的人就立刻自杀,然由再由下一个人重新开始报数,仍然是每报数为3的人就立刻自杀……,直到所有人都自杀身亡为止。

  约瑟夫和他的朋友些不想自杀,于是约瑟夫想到丁一个计策,他们两个同样参与到自术方案中,但是最后却躲过了自杀。请问,他们是怎么做到的?

  一个明显的求解方法便是将41个人排成一个环,内圈为按照顺时针的最初编号,外圈为每个人数到数字3的顺序。可以使用数组来保存约瑟夫环中该自杀的数据,而数组的下标作为参与人员的编号,并将数组看作环形来处理。
在这里插入图片描述

package math_problem;

import java.util.Scanner;

public class JosephRingDemo {
    /**
     * @description : 约瑟夫环问题求解
     * @params : [number:总人数, key:关键字]
     * @return :
     * @time   : 2020/2/7 20:29
     */
    private static void josephRing(int number, int key) {
        // 若第 i 个人存活,则 men[i] == 0,否则 men[i] == 死亡序号
        int[] men = new int[number];
        int count = 0;          // 人的死亡序号
        int temp = 0, pos = -1; // pos:每个人对应的数组元素的下标
        while (number - count >= key) {
            do {
                pos = (pos + 1) % number;
                if (men[pos] == 0)
                    temp++;
                // 若对应的人该死,则跳出循环,显示数据
                if (temp == key) {
                    temp = 0;
                    break;
                }
            } while (true);

            // 将该自杀的人序号所对应的元素值由 0 变为 自杀序号
            men[pos] = ++count;
            System.out.printf("第 %-3d 个人自杀!初始位置编号为 %-3d ,\n", count, pos + 1);
        }

        System.out.print("\n可存活的人的初始位置编号为:");
        for (int j = 0; j < number; j++) {
            if (men[j] == 0) {
                System.out.printf("%-3d,", j + 1);
            }
        }
    }

    public static void main(String[] args) {
        josephRing(41, 3);
    }
}

1   个人自杀!初始位置编号为 3   ,2   个人自杀!初始位置编号为 6   ,3   个人自杀!初始位置编号为 9   ,4   个人自杀!初始位置编号为 12  ,5   个人自杀!初始位置编号为 15  ,6   个人自杀!初始位置编号为 18  ,7   个人自杀!初始位置编号为 21  ,8   个人自杀!初始位置编号为 24  ,9   个人自杀!初始位置编号为 27  ,10  个人自杀!初始位置编号为 30  ,11  个人自杀!初始位置编号为 33  ,12  个人自杀!初始位置编号为 36  ,13  个人自杀!初始位置编号为 39  ,14  个人自杀!初始位置编号为 1   ,15  个人自杀!初始位置编号为 5   ,16  个人自杀!初始位置编号为 10  ,17  个人自杀!初始位置编号为 14  ,18  个人自杀!初始位置编号为 19  ,19  个人自杀!初始位置编号为 23  ,20  个人自杀!初始位置编号为 28  ,21  个人自杀!初始位置编号为 32  ,22  个人自杀!初始位置编号为 37  ,23  个人自杀!初始位置编号为 41  ,24  个人自杀!初始位置编号为 7   ,25  个人自杀!初始位置编号为 13  ,26  个人自杀!初始位置编号为 20  ,27  个人自杀!初始位置编号为 26  ,28  个人自杀!初始位置编号为 34  ,29  个人自杀!初始位置编号为 40  ,30  个人自杀!初始位置编号为 8   ,31  个人自杀!初始位置编号为 17  ,32  个人自杀!初始位置编号为 29  ,33  个人自杀!初始位置编号为 38  ,34  个人自杀!初始位置编号为 11  ,35  个人自杀!初始位置编号为 25  ,36  个人自杀!初始位置编号为 2   ,37  个人自杀!初始位置编号为 22  ,38  个人自杀!初始位置编号为 4   ,39  个人自杀!初始位置编号为 35  ,

可存活的人的初始位置编号为:1631 ,
Process finished with exit code 0

2 复杂的约瑟夫环

在历史上,约瑟夫环被广泛研究。一个推广的约瑟夫环问题如下:

  有n个人环坐一圈,按照顺时针方向依次编号为1、2、3、…、m。有一个黑盒子中放置着许多纸条,其上随机写有数字。每个人随机取一个纸条,纸条上的数字为出列数字。游戏开始时,任选一个出列数字。从第一个人开始,按编号顺序自1开始顺序报数,报到m的人出列,同时将其手中的数字作为新的出列数字。然后,从下一个人开始重新从1报数,如此循环报数下去。问最后剩下哪个人?

  先来分析一下这个问题,同前面的约瑟夫环相比,这里要明显复杂,主要体现在:

“参与人的个数为n是一个可变量,因此,不能使用数组来表示,应该使用链表结构来表示。另外,这n个人首尾相接构成一个环,因此应该使用循环链表来处理该问题。

package math_problem;

import java.util.Scanner;

class LNode {
    int no;         // 游戏者编号
    int key;        // 保存游戏者出列数字
    LNode next;     // 指向下一个节点

    public LNode(int no, int key, LNode next) {
        this.no = no;
        this.key = key;
        this.next = next;
    }

    public LNode(int no, int key) {
        this.no = no;
        this.key = key;
    }
}

// 循环链表:尾指针 next 指向头结点
public class CircularLinkedList {
    static LNode head = null;
    static LNode tail = null;
    int listLen = 0;

    private void addHead(int no, int key) {
        // 新建头结点,此时 head.next 为空
        head = new LNode(no, key, null);
        // 只有一个结点,头结点也是尾结点
        if (tail == null)
            tail = head;
        listLen++;
    }

    private void addTail(int no, int key) {
        // 新建结点作为尾结点的后继结点
        tail.next = new LNode(no, key);
        // 将新建结点作为新的尾结点
        tail = tail.next;
        // 使新尾结点的 next 指向 head,使链表成环
        tail.next = head;
        listLen++;
    }

    /**
     * @description : 求解复杂的约瑟夫环
     * @params : [cListHead:第一个开始游戏的人, key:第一个出列数字]
     * @return : 
     * @time   : 2020/2/7 22:43
     */    
    private static void josephRing(LNode cListHead, int key) {
        // 先使 tempHead 指向序号为 1 的游戏者
        LNode tempHead = cListHead, tempTail = cListHead;
        // 使 tempTail 指向序号最大的游戏者
        while (tempTail.next != tempHead)
            tempTail = tempTail.next;

        System.out.println("游戏者出列顺序为:");
        // 若剩余人数 > 1,则一直循环
        while (tempHead.next != tempHead) {
            // 搜寻该出列的游戏者,并用 tempHead 指向该游戏者
            for (int i = 0; i < key - 1; i++) {
                // 将 tempTail 作为 tempHead 的前驱节点
                tempTail = tempHead;
                tempHead = tempHead.next;
            }
            tempTail.next = tempHead.next;

            System.out.println("出列序号:" + tempHead.no + " ,下一个出列数字:" + tempHead.key);
            key = tempHead.key;         // 更新出列数字
            tempHead = null;            // 从链表中删除出列的游戏者
            tempHead = tempTail.next;   // 从出列者下一位重新开始游戏
        }
        System.out.println("最后一个人的序号:" + tempHead.no + " ,出列数字:" + tempHead.key);
    }

    public static void main(String[] args) {
        System.out.println("约瑟夫环问题求解:");

        System.out.print("请输入游戏人数:");
        Scanner scanner = new Scanner(System.in);
        int number = scanner.nextInt();

        System.out.print("请按顺序输入每个人手中的出列数字:");
        // 初始化第一个游戏者的信息
        CircularLinkedList circularLinkedList = new CircularLinkedList();
        int key = scanner.nextInt();
        circularLinkedList.addHead(1, key);
        // 初始化剩余游戏者的信息
        for (int i = 2; i <= number; i++) {
            key = scanner.nextInt();
            circularLinkedList.addTail(i, key);
        }

        System.out.print("请输入第一次出列的数字:");
        key = scanner.nextInt();
        josephRing(head, key);
    }
}

约瑟夫环问题求解:
请输入游戏人数:8
请按顺序输入每个人手中的出列数字:3 5 6 2 1 5 7 2
请输入第一次出列的数字:4
游戏者出列顺序为:
出列序号:4 ,下一个出列数字:2
出列序号:6 ,下一个出列数字:5
出列序号:3 ,下一个出列数字:6
出列序号:5 ,下一个出列数字:1
出列序号:7 ,下一个出列数字:7
出列序号:8 ,下一个出列数字:2
出列序号:2 ,下一个出列数字:5
最后一个人的序号:1 ,出列数字:3

Process finished with exit code 0
发布了50 篇原创文章 · 获赞 38 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_42250302/article/details/104215326