约瑟夫问题实现(java)

约瑟夫问题描述:一共有n个人,从第k个人开始报数,数到m的人出列,它的下一位又从1开始报数,数到m又出列,以此类推,直到所有的人都出列为止,由此产生一个出队的序列;

在这里插入图片描述
例如一共有5个人,编号为1,2,3,4,5从第一个人开始,每数两下就出列一个人,直到最后一个人也出列为止;
①1号先开始报数,一共数了两下,到了2号,所以2号出列,序列剩下1,3,4,5;
②2号出列后,从3号开始报数,数两下,到了4号,所以4号出列,序列剩下1,3,5;
③4号出列后,从5号开始报数,数两下,到了1号,所以1号出列,序列剩下3,5;
④1号出列后,下一个节点是3号,所以从3号开始报数,数两下,到了5号,所以5号出列,序列剩下3
⑤5号出列后,序列只有3号,所以3号最后出列;
出队结束;
出队序列为:2,4,1,5,3

所以在这里可以看出来,从1号开始报数出列,序号大的元素可能会先出列,例如4号先出列了,然后再到1号,再到5号;

所以单链表在这里不适用;但是如果是一个循环链表的话,把头和尾都连接起来,这样形成一个环状,正好就比较适合约瑟夫问题的解决;

所以这里可以采用循环链表的方式来解决约瑟夫问题;

思路
构建单向循环链表:
先创建第一个节点,让first指针指向该节点,并形成环状;
后面每当创建一个新的节点,就把该节点加入到已有的循环链表当中即可;

遍历循环链表:
先创建一个辅助指针temp,先让它指向循环链表的最后一个节点,着这样它的下一个节点就是first了;
从第k个人开始报数,所以在报数之前,先让first和temp往后移动k-1次;
每数m下就出列一个人,当开始报数的时候,让first和temp指针同时往后移动m-1次
这样first指针指向的节点就可以出列了;
然后从下一个节点开始,继续循环,依次出队;
当temp.next == first的时候,遍历结束


代码实现如下:

创建单向循环链表

//创建一个环形的单向链表
class CircleSingleLinkedList {
    //先创建一个first节点,当前没有编号
    private Boy first = null;
    //添加小孩节点,构建一个环形的链表
    public void addBoy(int nums) {   //nums表示要添加的小孩节点的个数
        //nums 作一个数据校验
        if (nums < 1) {  //环形链表当中至少要有一个小孩节点
            System.out.println("nums的值不正确");
            return;
        }
        Boy curBoy = null;  //辅助指针,帮助构建环形链表
        //使用for循环来创建环形链表
        for (int i = 1; i <= nums; i++) {
            //根据编号,创建小孩节点
            Boy boy = new Boy(i);
            //如果是第一个小孩节点
            if (i == 1) {
                first = boy;
                first.setNext(first);  //构成环状
                curBoy = first;  //让curBoy指向第一个小孩
            }
            else {
                curBoy.setNext(boy);
                boy.setNext(first);
                curBoy = boy;
            }
        }
    }

遍历循环链表

 //遍历当前环形链表
    public void showBoy() {
        //判断链表是否为空
        if (first == null) {
            System.out.println("没有任何小孩");
            return;
        }
        //因为first不能动,因此需要一个辅助指针来完成遍历
        Boy curBoy = first;
        while (true) {
            System.out.printf("小孩的编号为 %d \n",curBoy.getNo());
            if (curBoy.getNext() == first) {  //已经遍历到了链表的最后
                return;
            }
            curBoy = curBoy.getNext();  //curBoy后移
        }
    }

计算出队顺序

/**
     *
     * @param startNo  表示从第几个小孩开始数数
     * @param countNum  表示数几下
     * @param nums  表示最初有几个小孩在圈中
     */
    //根据用户的输入,计算出小孩的出圈顺序
    public void countBoy(int startNo, int countNum, int nums) {  //startNo是从第几个小孩开始,countNum是数几下,nums是小孩的数量
         //先对数据进行校验
        if (first == null || startNo < 1 ||startNo > nums) {
            System.out.println("参数输入有误,请重新输入");
        }
        //创建一个辅助指针,帮助小孩完成出圈
        Boy helper = first;
        //先让辅助指针helper事先指向环形链表的最后这个节点
        while (true) {
            if (helper.getNext() == first) {
                break;
            }
            helper = helper.getNext();
        }
        //小孩报数前,先让first和helper移动startNo-1次,first移动到要开始出列的节点startNo,helper指向的是startNo节点的前一个节点
        for (int j = 0; j < startNo - 1; j++) {
            first = first.getNext();
            helper = helper.getNext();
        }
        //当小孩报数时,让first和helper指针同时移动 countNum-1 次,然后出圈
        //这里是一个循环操作,直到圈中只有一个节点
        while (true) {
            if (helper == first) {  //说明圈中只有一个小孩节点
                break;
            }
            //让first和helper指针同时移动countNum-1次,然后出圈
            for (int i = 0; i < countNum - 1; i++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            //这时first指向的节点,就是要出圈的小孩
            System.out.printf("小孩%d出圈\n",first.getNo());
            //这时将first指向的小孩节点出圈
            first = first.getNext();
            helper.setNext(first);  //让helper的next域指向first节点
        }
        System.out.printf("最后留在圈中的小孩编号是%d\n",first.getNo());
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值