约瑟夫问题环循环链表解法

一.简介

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

17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。

约瑟夫问题有循环链表、mod(求余)解法

二.实现

package com.vincent;


public class Main {
    public static void main(String[] args) throws Exception{
        CircleLinkedList<Integer> list = new CircleLinkedList<>();
        for(int i=1;i<6;i++){
            list.add(i);
        }
        System.out.println(list);
        list.solveJosephus(0,2);

    }
}

class CircleLinkedList<T> {
    static class Node<T> {
        private T item;
        Node<T> next;

        public Node(T item,Node<T> next){
            this.item = item;
            this.next = next;
        }
    }

    private int size;
    private Node<T> head;
    private Node<T> tail;

    public void add(T item){
        Node<T> node = new Node<>(item,null);
        if(head == null){
            head = node;
            tail = node;
            tail.next = head;
        }
        else{
            tail.next = node;
            node.next = head;
            tail = node;
        }
        size++;
    }

    private Node<T> nodeByIndex(int index){
        int pos = (size + index) % size;
        if(pos < 0 || pos >= size){
            throw new IndexOutOfBoundsException();
        }
        Node<T> node = null;
        if(pos == size-1){
            node = tail;
        }
        else{
            node = head;
            for(int i=0;i<pos;i++){
                node = node.next;
            }
        }
        return node;
    }

    public T remove(int index){
        int pos = (size + index) % size;
        if(pos < 0 || pos >= size){
            throw new IndexOutOfBoundsException();
        }
        Node<T> node = null;
        if(pos == 0){
            node = head;
            head = node.next;
            tail.next = head;
        }
        else{
            Node<T> prev = nodeByIndex(pos-1);
            node = prev.next;
            prev.next = node.next;
        }
        size--;
        return node.item;
    }

    /**
     * 约瑟夫问题解决
     * @param startPos      开始报数索引
     * @param count         计数总和
     */
    public void solveJosephus(int startPos,int count){
        if(startPos < 0 || startPos >= size){
            throw new IndexOutOfBoundsException();
        }
        if(count > size){
            throw new IllegalArgumentException();
        }
        //先移动到计数开始位置
        // 由于采用单向链表,故需要记录待删除结点的上一个节点,head表示计数开始节点,tail 表示计数上一个节点
        if(startPos > 0){
            head = nodeByIndex(startPos-1);
            tail = nodeByIndex(startPos);
        }

        //循环链表中只有一个节点时 头节点=尾节点
        while(head != tail){
            for(int i=1;i<count;i++){
                head = head.next;
                tail = tail.next;
            }
            System.out.println("出圈人为:" + head.item);
            head = head.next;
            tail.next = head;
            size--;
        }
        System.out.println("最后一个人为:" + head.item);
    }

    public int size(){
        return size;
    }

    @Override
    public String toString() {
        StringBuilder rst = new StringBuilder();
        Node<T> node = head;
        while(node != null){
            rst.append(node.item);
            if(node.next != head){
                rst.append(",");
                node = node.next;
            }
            else{
                break;
            }
        }
        return rst.toString();
    }
}

效果:
在这里插入图片描述

三.总结

采用循环链表解法需要自己实现一个循环链表,操作循环链表节点

采用求余解法可以采用java api ArrayList,LinkedList 等实现

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 酷酷鲨 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读