数据结构-单向环形链表解决 Josephu问题

Josephu(约瑟夫, 约瑟夫环)是一个数学应用问题

  • n个人按编号1, 2, …n围坐一圈, 从编号为 k的人开始报数, 数到 m的那个人出列, 此时它的下一个人又从1开始报数, 数到 m的那个人再出列, 依次反复, 直到所有的人全部出列为止, 最后输出出列人的编号顺序

解决思路

  • 首先创建有 n个节点的不带头的单向环形链表, 然后从 k节点开始计数, 计到 m时, 删除对应当前节点, 然后再从被删节点的下一个节点开始重新计数, 依次反复, 直到最后一个节点从链表中删除算法结束

  • 示意图

在这里插入图片描述

代码示例


/** 定义人(表示节点)*/
class Person {
    /** 人编号*/
    private int no;
    /** 指向下一个节点*/
    private Person next;

    public Person(int no) {
        this.no = no;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public Person getNext() {
        return next;
    }

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

/** 定义不带头的单向环形链表*/
class CircularSingleLinkedList {
    /** 定义起始节点*/
    private Person first;

    /** 构建 n个节点的链表*/
    public void addPersons(int n) {
        if (n < 1) {
            System.out.println("构建链表失败, 参数错误!");
            return;
        }

        Person current = null;
        for (int i = 1; i <= n; i++) {
            /** 设定节点编号*/
            Person person = new Person(i);
            /** 定义起始节点*/
            if (i == 1) {
                first = person;
                /** 起始节点, 指向自己(自己指向自己)*/
                first.setNext(first);
                /** 保存当前节点, 用于下次循环时配置下一个节点的引用*/
                current = first;
            } else {
                /** 之前的有效环形的指向是 first, 改成当前节点(current的下一个节点)*/
                current.setNext(person);
                /** 当前节点的下一个节点引用起始节点*/
                person.setNext(first);
                current = person;
            }
        }
    }

    /** 打印所有节点*/
    public void print() {
        if (first == null) {
            System.out.println("链表为空!");
            return;
        }

        Person current = first;
        while (true) {
            System.out.printf("节点编号 %d \n", current.getNo());
            if (current.getNext() == first) {
                break;
            }
            /** 往后移动一个节点*/
            current = current.getNext();
        }
    }

    /**
     * 计算后打印每个人出列的顺序编号:
     * @param startNo: 表示从第几个小孩开始数数
     * @param countNum: 表示数几下
     * @param nums: 表示最初有多少小孩在圈中
     * */
    public void countPerson(int startNo, int countNum, int nums) {
        if (first == null || startNo < 1 || startNo > nums) {
            System.out.println("参数错误!");
            return;
        }

        Person helper = first;
        /** 将 helper指向最后节点*/
        while (true) {
            /** 循环后, 当前节点与起始节点相同, 则循环结束*/
            if (helper.getNext() == first) {
                break;
            }

            helper = helper.getNext();
        }

        /**
         * 例: startNo = 1; -1 = 0时不成立跳过!, 如果 startNo大于1就会调整起始节点
         * */
        for(int i = 0; i < startNo - 1; i++) {
            first = first.getNext();
            helper = helper.getNext();
        }

        /** 每次计数时, 将起始节点与最后节点同时向下移动  m - 1次, 然后出列*/
        while(true) {
            /** 当链表中只有一个节点时结束*/
            if(helper == first) {
                break;
            }

            /** 每次循环, 将起始节点与最后节点向下移动: (计数指定次 -1)个节点, */
            for(int j = 0; j < countNum - 1; j++) {
                first = first.getNext();
                helper = helper.getNext();
            }

            /** 此时起始节点就是要出列的节点*/
            System.out.printf("%d号节点出列\n", first.getNo());
            /** 将起始使用第二个节点覆盖*/
            first = first.getNext();
            /** 最后节点的下一个节点(之前的起始节点), 改成目前的起始节点*/
            helper.setNext(first);
        }

        System.out.printf("最后剩余的节点编号 %d \n", first.getNo());
    }

}

public class JosephuApp {
    public static void main(String[] args) {
        /** 创建单向环形链表实例*/
        CircularSingleLinkedList linkedList = new CircularSingleLinkedList();
        /** 构建5个节点*/
        linkedList.addPersons(5);
        System.out.println("输出所有节点编号:");
        linkedList.print();

        System.out.println("计算后打印每个人出列的顺序编号:");
        linkedList.countPerson(1, 2, 5);
    }

}

输出:
> 输出所有节点编号:
> 节点编号 1 
> 节点编号 2 
> 节点编号 3 
> 节点编号 4 
> 节点编号 5 
> 计算后打印每个人出列的顺序编号:
> 2号节点出列
> 4号节点出列
> 1号节点出列
> 5号节点出列
> 最后剩余的节点编号 3 

如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!

©️2020 CSDN 皮肤主题: 创作都市 设计师:CSDN官方博客 返回首页