如何简单解决约瑟夫环问题?

目录

今日良言:总有人会成功,为什么不能是你呢?

一、问题描述

二、环形单链表

1.思路分析

2.解决步骤

3.效果展示

4.完整代码


今日良言:总有人会成功,为什么不能是你呢?

 

 一、问题描述

约瑟夫环问题是一个很经典的问题:一个圈共有N个人,第一个人的编号为1,第二个人的编号就为2号,第三个人的编号就为3号,第N个人的编号就为N号,现在提供一个数字M,从第一个人也就是1号开始报数,第二个人报的数是2,依次类推,报到M这个数字的人出局,紧接着从出局的这个人的下一个人重新开始从1报数,和上面过程类似,报到M的人出局,直到N个人全部出局,请问,这个出局的顺序是什么?

图文结合总归好理解一点,如下图就是上述问题:

我们假设有5个小孩围成一圈, 从1号小孩开始报数,每次报2个数字,由此可知出局顺序是:2 -> 4 -> 1 ->  5-> 3

 

二、 环形单链表

1.思路分析

   由上述图片也可看出,小孩围成的圈就是一个环形,我们这里使用环形单链表来解决这个问题。

    结点下标从1开始,然后以用户输入的数字结束,我们先通过遍历该单链表,得到最后一个结点cur头结点时first,这里依旧假设是5个小孩,最后一个结点的小孩编号是5,我们要求报两个数,第一个小孩报1,第二个小孩报2,因此,我们先让第二个小孩出圈,出圈的过程其实也就是删除这个结点的过程,我们先输出这个小孩的编号,然后让first指向下一个结点,再让cur指向first即可,不难发现,这个出圈的过程是一个循环,而循环结束的条件就是只剩下一个结点 ,也就是 cur == first时。

  分析可知:每次让cur和first走2-1 = 1步,因为first这个小孩需要报数。

2.解决步骤

第一步当然是我们亲爱滴单链表的创建了,分析问题可知,我们创建一个类CircleList 来表示这个单链表,同时我们创建一个类Node来表示结点,Node类中有两个私有属性,一个data(数据域),一个Node类型的next(指针域),构造方法以及get和set方法是必不可少的喔,如下代码:

 结点类实现后,我们创建CircleList类,这个类首先有一个Node类型的私有属性first,可以表示结点,不用初始化,默认为null即可,如下代码:

private Node first;// 头结点

 接下来我们开始进行各种操作:

添加数据

思路:用户输入一个数字data表示多少个孩子,然后循环创建结点,我们创建一个Node类型的curr用来帮助我们构建链表,当第一次创建一个data 等于1的结点时,将该结点赋给firs和curt,需要注意的是,一定要让first指向自己构成环,然后第二个结点创建好后,先让cur.next指向第二个结点,然后让第二个结点的next指向first构成环,然后再将第二个结点赋给cur,依次类推,如下代码:

 public void add(int data) {
        if (data < 1) {
            System.out.println("data的值不正确");
            return;
        }
        Node cur = null;  // 辅助结点,帮助链接链表
        for (int i = 1; i <= data; i++) {
            // 根据编号创建结点
            Node node = new Node(i);
            // 如果是第一个结点
            if (i == 1) {
                first = node;
                first.setNext(first);// 构成环形
                cur = node;
            } else {
                cur.setNext(node);
                node.setNext(first);// 让最后一个结点继续指向头结点
                cur = node;
            }
        }

 遍历单链表

思路:我们设置一个Node类型的cur,先将first赋给cur,然后让cur先打印data域的值,然后继续向后遍历打印,直到cur.getNext == first时结束循环。如下代码:

public void show() {
        // 判空
        if (first == null) {
            System.out.println("链表为空,操作失败");
            return;
        }
        Node cur = first;
        while (true) {
            System.out.println("小孩的编号是:"+cur.getData());
            if (cur.getNext() == first) {
                break;
            }
            cur = cur.getNext();
        }

接下来就是最最重要的计算出圈的方法啦!!!

出圈方法:

思路:该方法有三个参数,第一个参数startNum表示从第几个小孩开始,第二个参数表示每次报countNum个数,第三个参数表示共多少个孩子。

  我们首先创建一个Node类型的cur,遍历该单链表,让cur指向最后一个结点,然后让cur和first同时开始走,直到first指向当前的startNum结点结束,然后让cur和first同时开始走countNum -1 步(因为first也需要报数),当走完以后,此时first的位置就是需要出圈的小孩的编号,先让该小孩出圈,然后再让fitst指向它的下一个结点,再让cur指向first即可,不难看出这是一个循环,循环结束的条件是:只有一个结点时,结束循环,然后让输出该小孩的编号即可。如下代码:

public void count(int startNum,int countNum,int nums) {
        // 检验各种数据的合法性
        if (first == null || startNum < 1 || startNum > nums) {
            System.out.println("输出的数据有误");
            return;
        }
        // 创建一个辅助结点,指向最后一个结点
        Node cur = first;
        while (true) {
            if (cur.getNext() == first) {
                break;
            }
            cur = cur.getNext();
        }
        // 此时cur 就是最后一个结点
        // 此时让cur和first一起走
        while (true) {
            //  说明此时就一个结点
            if (cur == first) {
                break;
            }
            for (int j = 0; j < countNum - 1; j++) {
                first = first.getNext();
                cur = cur.getNext();
            }
            // 此时first指的结点就是要出圈的结点
            System.out.println("出圈的小孩编号是:"+first.getData());
            // 然后删除这个结点
            first = first.getNext();
            cur.setNext(first);
        }
        System.out.println("最后出圈的小孩编号是:"+cur.getData());
    }

3.效果展示

从1号小孩开始报数,每次报2个数

从1号小孩开始,每次报3个数

 从4号小孩开始,每次报3个数

 当然,上述操作可以封装成一个菜单哦

4.完整代码



    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }
}
class CircleList {
    private Node first;// 头结点
    public void add(int data) {
        if (data < 1) {
            System.out.println("data的值不正确");
            return;
        }
        Node cur = null;  // 辅助结点,帮助链接链表
        for (int i = 1; i <= data; i++) {
            // 根据编号创建结点
            Node node = new Node(i);
            // 如果是第一个结点
            if (i == 1) {
                first = node;
                first.setNext(first);// 构成环形
                cur = node;
            } else {
                cur.setNext(node);
                node.setNext(first);// 让最后一个结点继续指向头结点
                cur = node;
            }
        }
    }
    /**
     * 遍历当前链表
     */
    public void show() {
        // 判空
        if (first == null) {
            System.out.println("链表为空,操作失败");
            return;
        }
        Node cur = first;
        while (true) {
            System.out.println("小孩的编号是:"+cur.getData());
            if (cur.getNext() == first) {
                break;
            }
            cur = cur.getNext();
        }
    }

    /**
     * 出圈方法
     * @param startNum 从几号孩子开始
     * @param countNum 总共数几下
     * @param nums 共多少个孩子
     */
    public void count(int startNum,int countNum,int nums) {
        // 检验各种数据的合法性
        if (first == null || startNum < 1 || startNum > nums) {
            System.out.println("输出的数据有误");
            return;
        }
        // 创建一个辅助结点,指向最后一个结点
        Node cur = first;
        while (true) {
            if (cur.getNext() == first) {
                break;
            }
            cur = cur.getNext();
        }
        // 让first指向用户要求的那个结点,cur需要一起走
        while (startNum - 1 > 0) {
            first = first.getNext();
            cur = cur.getNext();
            startNum--;
        }
        // 此时cur 就是最后一个结点
        // 此时让cur和first一起走
        while (true) {
            //  说明此时就一个结点
            if (cur == first) {
                break;
            }
            for (int j = 0; j < countNum - 1; j++) {
                first = first.getNext();
                cur = cur.getNext();
            }
            // 此时first指的结点就是要出圈的结点
            System.out.println("出圈的小孩编号是:"+first.getData());
            // 然后删除这个结点
            first = first.getNext();
            cur.setNext(first);
        }
        System.out.println("最后出圈的小孩编号是:"+cur.getData());
    }
}
public class Josepfu {
    public static void main(String[] args) {
        CircleList circleList = new CircleList();
        circleList.add(5);
        System.out.println("显示5个小孩的编号");
        circleList.show();
        System.out.println("=================开始出圈===================");
        circleList.count(4,3,5);
    }
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值