环形单向链表之约瑟夫问题详解

环形单向链表之约瑟夫问题详解

说明:

  1. 约瑟夫环问题可以使用单向环形链表形象的模拟解决
  2. 需要构建一个单向环形链表
  3. 然后用打印并删除节点的方式模拟小孩出圈
  4. 具体逻辑思路见代码注释

源码及分析

节点类
//创建一个小孩节点
class Boy {
    //小孩编号
    private int no;
    //指向下一个节点的指针
    private Boy next;

    //构造器
    public Boy(int no) {
        this.no = no;
    }

    //getter 和 setter


    public int getNo() {
        return no;
    }

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

    public Boy getNext() {
        return next;
    }

    public void setNext(Boy next) {
        this.next = next;
    }
}
环形单向链表类
//创建单向环形链表
class CircleSingleLinkedList {

    //先创建第一个节点,然后置为空
    private Boy first = null;

    //编写方法创建一个指定大小的单向环形链表

    /**
     * @param nums 指定要创建的环形链表的大小
     */
    //思路分析
    //1. 先创建大小为1个节点的环形链表,即让自己指向自己
    //2. 然后循环向这个链表中添加节点
    //3. 每添加一个节点,都让这个链表成环状
    //4. 再添加完所有的节点后,这个环形链表仍然是成立的
    
    public void addBoy(int nums) {
        //先进行数据校验,判断输入的实参是否合格
        if (nums < 1) {
            System.out.println("数据不合格~~");
            return;
        }
        //因为第一个节点first不能动,所以需要一个辅助指针cur,不断的移动添加
        Boy cur = null;
        //循环向环形链表中添加节点
        for (int i = 1; i <= nums; i++) {
            //创建节点
            Boy boy = new Boy(i);
            //考虑第一个节点,让第一个节点自己成环,即自己指向自己
            if (i == 1) {
                //第一个节点自己指向自己
                first = boy;
                first.setNext(first);
                //辅助指针指向第一个节点
                cur = first;
                //如果是其他的节点,依次加入到环形链表中
            } else {
                cur.setNext(boy);
                boy.setNext(first);
                //指针后移
                cur = cur.getNext();
            }
        }
    }

    //编写方法显示环形链表中的节点
    public void showBoy() {
        //先判断环形链表是否为空
        if (first == null) {
            System.out.println("链表是空的~~");
            return;
        }
        //否则遍历环形链表
        //因为第一个节点不能动,因此需要定义辅助指针cur
        Boy cur = first;
        //遍历结束的条件为:当且仅当cur.next = first
        while (true) {

            //先打印节点的信息
            System.out.println("小孩的编号:" + cur.getNo());
            //判断是否遍历结束
            if (cur.getNext() == first) {
                break;
            }
            //指针后移
            cur = cur.getNext();
        }
    }

    //编写方法实现小孩出圈
    //思路分析:
    //1. 因为是单向链表,所以删除节点时需要找到该节点的前一个节点
    //2. 但是要打印要出圈的节点的编号,需要找到这个节点本身,因此需要定义两个节点
    //3. 一个节点指向要删除节点的前一个节点,一个节点表示要删除的节点本身
    //4. first表示每次开始数数的哪个节点
    //5. 定义cur指针之前first指针的前一个节点
    //6. 先让first指向环形链表的第一个节点,cur指向最后一个节点,即first节点的前一个节点
    //7. first节点和cur节点向后移动startNo - 1次,则找到要开始数数的这个节点
    //8. 再向后移动countNum - 1次,则找到要出圈的节点,即要删除的节点

    /**
     * @param startNo  从哪个小孩开始数数
     * @param countNum 每次数几个小孩
     * @param nums     环形链表的大小
     */
    public void countBoy(int startNo, int countNum, int nums) {
        //先进行数据校验
        if (first == null || startNo < 1 || countNum < 1 || nums < 1) {
            System.out.println("数据错误~~");
            return;
        }
        //定义辅助指针cur指向第一个节点的前一个节点
        Boy cur = first;
        while (true) {
            if (cur.getNext() == first) {
                break;
            }
            cur = cur.getNext();
        }
        //循环结束后cur指向first节点的前一个节点

        //根据输入的开始的小孩编号移动first和cur,使得first指向要开始的节点,cur指向前一个节点
        for (int i = 0; i < startNo - 1; i++) {
            first = first.getNext();
            cur = cur.getNext();
        }
        //循环删除数到的每个节点,直到只剩下最后一个节点
        while (true){
            //如果只剩下一个节点,循环结束
            if (cur == first){
                break;
            }
            //将first和cur的指针向后移动countNum - 1次找到要删除的节点和其前一个节点
            for (int i = 0; i < countNum - 1; i++) {
                first = first.getNext();
                cur = cur.getNext();
            }
            System.out.println("小孩 " + first.getNo() + " 出圈~~");
            //first指向他的下一个节点
            first = first.getNext();
            //继续设置cur为first的前一个指针
            cur.setNext(first);

        }
        //循环结束后则只剩下一个指针
        System.out.println("最后一个小孩 " + cur.getNo() + " 出圈");
    }
}

测试类
public static void main(String[] args) {

        //环形链表测试
        //创建环形链表
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(100);
//        circleSingleLinkedList.showBoy();
        circleSingleLinkedList.countBoy(6,10,100);

    }
posted @ 2021-05-06 22:29  mx_info  阅读(146)  评论(0)    收藏  举报