约瑟夫问题的思路及代码实现(超详细的注释)

约瑟夫问题

设编号为1,2,…,n的n个人围在一圈,约定编号为k(1≤k≤n)的人从1开始报数,数到m的那个人出列,出列的人的下一位又开始从1开始报数,数到m的那个人又出列,以此类推,直到所有人出列为止,产生一个出队编号的序列

假设n=5,即有5个人围在一圈;k=1,即从第一个人开始报数;m=2,即数两下,则最后出队编号的序列为24153

思路:可以用一个不带头结点的循环链表来处理,先构成一个有n个结点的单循环链表(单向环形链表),然后由k结点从1开始计数,计到m时,对应的结点从链表中删除,然后再从被删除结点的下一个结点又开始从1计数,直到最后一个结点从链表中删除,算法结束

构建第一个单向环形链表的思路

  1. 先创建first头节点,创建第一个节点之后,使first头节点指向该节点,并使此第一个节点的next指向头节点,从而形成环形链表
  2. 创建第二个节点,使第一个节点的next指向第二个节点(这一步需要使用辅助指针temp),第二个节点的next指向first头节点即可。以此类推

遍历环形链表

  1. 创建一个辅助指针temp,指向first节点
  2. 通过while循环遍历该环形链表即可,要注意遍历结束的条件,temp.next == first即为遍历结束

设环形链表的大小为n,编号为k(1≤k≤n)的节点从1开始数,数到m的节点出链表,生成出链表的顺序

  1. 需要一个辅助指针(变量)temp,事先应该指向环形链表的最后的节点;first指针指向头节点
  2. 计数前,使first和temp指针移动k-1次,first指针移动到指向k节点,temp指针移动到指向k节点前一个节点
  3. 当给定m,开始计数的时候,first和temp指针同时移动m-1次,因为从k节点开始数的时候,k节点是数的第一下,比如m=2时,first指向k+1,temp指向k
  4. 此时first指针指向的节点k+1出链表

出链表的条件为:first先后移,first = first.next
temp的next指针指向first,temp.next =first
k+1节点没有任何的引用,就会被JVM垃圾回收机制回收

约瑟夫问题的实现代码
直接拷贝至你们的开发工具就能运行,代码上我写了详细的注释,思路应该比较清晰
代码实际上就是上述实现思路的翻译

package com.test.chap2;

public class Josephu {

    public static void main(String[] args) {
        CircleSingleLinkedList csl = new CircleSingleLinkedList();
        csl.addNode(5);//加入5个节点
        csl.showNode();

        //测试约瑟夫问题
        csl.countNode(5, 1, 2);
    }
}

//创建一个环形的单向链表
class CircleSingleLinkedList {
    // 创建一个first头节点,当前没有编号
    private Node first = null;

    // 添加节点,构成一个环形链表
    public void addNode(int numbers) {// numbers代表整个环形链表一共有多少个节点
        // numbers的数据校验
        if (numbers < 1) {
            System.out.println("numbers的值错误");
            return;
        }

        // 创建辅助指针temp,帮助构建链表
        Node temp = null;

        // for循环来创建环形链表
        for (int i = 1; i <= numbers; i++) {
            // 根据编号,创建节点
            Node node = new Node(i);

            // 如果是第一个节点
            if (i == 1) {
                first = node;
                first.setNext(first);// 构成环形
                temp = first;// 使temp指向第一个节点,方便后续添加节点
            } else {
                temp.setNext(node);// 使temp的next指向新节点
                node.setNext(first);// 新节点的next指向first节点
                temp = node;// 临时指针temp后移,指向新节点
            }
        }
    }

    // 遍历当前的环形链表
    public void showNode() {
        // 判断链表是否为空
        if (first == null) {
            System.out.println("链表为空");
            return;
        }
        // 因为first是固定的,所以需要一个辅助指针temp完成遍历
        Node node = first;
        while (true) {
            System.out.printf("节点的编号为:%d\n", node.getNumber());

 	    //如果node的next节点为first头节点,说明链表已经遍历完了,直接退出
            if (node.getNext() == first) {
                break;
            }
            node = node.getNext();// 使temp后移
        }
    }

    /*
     * 根据用户的输入,numbers指定链表大小 k指定从哪个节点开始 m指定计数的大小 计算出节点出链表的顺序
     */
    public void countNode(int numbers, int k, int m) {
        // 先对链表进行校验
        if (first == null || k < 1 || k > numbers) {
            System.out.println("参数输入有误!请重新输入");
            return;
        }
        // 创建辅助指针temp,帮助完成节点出链表
        Node temp = first;
        //辅助指针temp,事先应该指向环形链表最后的节点
        while(true) {
            if(temp.getNext() == first) {//temp的next是first头节点,说明temp指向了最后的节点
                break;
            }
            temp = temp.getNext();//如果if条件没有满足,temp后移,直到满足if条件
        }
        //退出while循环之后,temp就了指向最后的节点
        //计数前,first和temp指针移动k-1次
        for(int i = 0; i < k - 1; i++) {
            first = first.getNext();
            temp = temp.getNext();
        }
      /*
       * 给定m值,开始计数,first和temp指针同时移动m-1次
       * first指向的节点即为待删除节点
       * 需要循环操作来删除节点,直到链表中只剩一个数据
       */
        while(true) {
            if(temp == first) {//说明链表中只有一个节点
                break;
            }
            //first和temp指针同时移动m-1次
            for(int i = 0; i < m - 1; i++) {
                first = first.getNext();
                temp = temp.getNext();
            }
            //此时first指向的节点就是要出链表的节点
            System.out.printf("节点%d出链表\n", first.getNumber());
            //将first指向的节点出链表
            first = first.getNext();//first指针后移
         /*
          * 因为next是私有属性,不能使用temp.getNext() = first;来把temp的next域指向first
          * 需要使用temp.setNext(first);
          */
            temp.setNext(first);
        }
        //循环结束,链表中只剩一个数据
        System.out.printf("最后留在链表中的节点的编号为:%d\n", first.getNumber());
    }
}

//创建Node类,表示一个节点,节点的信息只需要一个number
class Node {
    private int number;// 编号

    /*
     * 指向下一个节点next,默认为null,注意是私有变量,通过getter和setter来对值进行操作
     * next能够指向下一个节点,因为addNode()方法给next赋予指向下一个节点的逻辑
     */
    private Node next;

    public Node(int number) {
        this.number = number;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public Node getNext() {
        return next;
    }

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值