数据结构与算法 — 约瑟夫问题(Josephu)

问题背景

著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,一开始要站在什么地方才能避免被处决?

问题描述

设编号为1,2,…n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

题目分析

这里使用的是环形单链表来解决这个问题。假设一共有5个人,从编号为1的人开始报数,数到2的那个人就要淘汰。如下图,先将5个人加入到环形链表中。
在这里插入图片描述
从1开始报数,标号2的报到2,所以淘汰还剩下1 3 4 5 淘汰2
在这里插入图片描述
然后从3号开始,重新报数,4号被淘汰。 剩下1 3 5 淘汰2 4

在这里插入图片描述
4号淘汰后,从5号开始报数,淘汰掉1号。剩下3 5 淘汰 2 4 1
在这里插入图片描述
再然后就从3号开始,淘汰掉3号。最后只剩下5号
所以最终出队的顺序为 2 4 1 3 5。

代码实现

1.创建一个Person类,作为节点

这里把成员变量都设为私有了,所以下面加上了set和get方法。也可以设置为公共,就不需要下面的set和get方法了。

class Person {
    private int id;
    private Person next;

    public Person(int n) {
        id = n;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Person getNext() {
        return next;
    }

    public void setNext(Person next) {
        this.next = next;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                '}';
    }
}
2.新建环形链表CircleSingleLinkedList

设置一个first指针,指向开始节点。最开始里面是没有节点的,所以先为空。

class CircleSingleLinkedList {
    private Person first = null;
    
}
3.设置玩游戏的总人数(添加节点)

添加节点的步骤如下图(将5号加入到链表中)
在这里插入图片描述
传参为参加游戏的总人数。

public void addPerson(int n) {
        if (n < 1) {
            System.out.println("添加人数要大于等于1人");
            return;
        }
        //cur指向当前的节点,也就是最后加入的节点。
        Person cur = null;
        for (int i = 1; i <= n; i++) {
            Person person = new Person(i);
            if (i == 1) {
                first = person;
                first.setNext(first);
                cur = person;
            } else {
                cur.setNext(person);
                person.setNext(first);
                cur = person;
            }
        }
    }
4.显示链表
public void show() {
        if (first == null) {
            System.out.println("该链表为空。。。");
            return;
        }
        Person curPerson = first;
        while (true) {
            System.out.println(curPerson.getId());
            if (curPerson.getNext() == first) {
                break;
            }
            curPerson = curPerson.getNext();
        }
    }
5.取出淘汰的人

如图,要将2号淘汰,只需要指向所示的两部,就没有指针指向2号,那么2号就会被回收机制所回收。
在这里插入图片描述
传参 startid为开始报数的位置,count为报到几后淘汰。n为总人数,用来校验参数的合理性。

public void run(int startid, int count, int n) {
        if (first == null || startid < 1 || count > n) {
            System.out.println("请给出合理的参数...");
            return;
        }
        //设置一个辅助指针helper,使它始终指向first的前一个节点,因为first最后指向的节点就是要被淘汰的,单向链表,我们就需要用helper来淘汰此节点。
        Person helper = first;
        while (true) {
            if (helper.getNext() == first) {
                break;
            }
            helper = helper.getNext();
        }
        //使first指向开始报数的人,helper也指向对应的前一个人。
        for (int i = 0; i < startid - 1; i++) {
            first = first.getNext();
            helper = helper.getNext();
        }
        while (true){
            if (first == helper){
                break;
            }
            for (int i =0;i<count -1 ;i++){
                first = first.getNext();
                helper = helper.getNext();
            }
            System.out.println("淘汰的为" + first.getId());
            first = first.getNext();
            helper.setNext(first);
        }
        System.out.println("最后剩下的为"+ first.getId());
    }
6.运行结果
public static void main(String[] args) {
        CircleSingleLinkedList cs= new CircleSingleLinkedList();
        cs.addPerson(5);
        cs.show();
        cs.run(1,2,5);
    }

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43773562/article/details/107745558