循环链表之约瑟夫问题java代码实现

参考blibli上面的视频和笔记进行的整理,相关视频链接:
https://www.bilibili.com/video/BV1B4411H76f
https://www.bilibili.com/video/BV1iJ411E7xW

文章目录

    • 一、循环链表介绍
    • 二、单向循环链表应用场景
        • 2.1 约瑟夫问题版本一
        • 2.2 约瑟夫问题版本二

一、循环链表介绍

循环链表,顾名思义,链表整体要形成一个圆环状。在单向链表中,最后一个节点的指针为null,不指向任何节点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头节点即可。
循环链表之约瑟夫问题java代码实现_第1张图片

二、单向循环链表应用场景

2.1 约瑟夫问题版本一

1. Josephu(约瑟夫、约瑟夫环)问题:

设编号为1,2,3 ,…,n的n个人围坐一圈,约定编号为k(1=

提示:

用一个不带头节点的循环链表来处理Josephu问题:先构成一个有n个节点的单循环链表,然后从k节点起从1开始计数,计到m时,对应节点从链表中删除,然后再从被删除节点的下一个节点又从1开始计数,直到最后一个节点从链表中删除算法结束。

2. 创建及遍历循环链表的思路

创建一个单向的环形链表
1.先创建第一个节点,让first指向该节点,并形成环形
2.后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可

遍历一个单向的环形链表
1.先让一个辅助指针(变量)curBoy,指向first节点
2.然后通过一个while循环遍历该环形链表即可curBoy.next = = first结束循环链表之约瑟夫问题java代码实现_第2张图片
3. 从循环链表中取出节点(小孩出圈)的思路

(1) 需要创建一个辅助指针(变量)helper,指向环形链表的最后一个节点
(2) 小孩报数前,先让first和helper指针移动k-1次(因为first默认指向第一个节点1,约定从节点k开始报数,所以移动k-1次)
(3) 当小孩报数时,让first和helper指针同时移动m-1次,找到要出圈的第m个小孩
(4) 这样就可以将first指向的小孩节点出圈
   first = first.next
   helper.next = first
 原来first指向的节点就没有任何引用,就会被回收
循环链表之约瑟夫问题java代码实现_第3张图片
代码实现

package test01;

public class Josephu1 {
    public static void main(String[] args) {
        //测试构建环形链表和遍历
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);//加入5个小孩的节点
        circleSingleLinkedList.showBoy();
        //测试出圈是否正确
        circleSingleLinkedList.countBoy(1,2,5); //2->4->1->5->3
    }
}

//创建一个环形的单向链表
class CircleSingleLinkedList{
    //创建一个first节点,当前没有编号
    private Boy first = null;
    //添加小孩节点,构建成一个环形的链表
    public void addBoy(int 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){ //说明已经遍历完毕
                break;
            }
            curBoy = curBoy.getNext(); //curBoy后移
        }
    }

    //根据用户的输入,计算出小孩出圈的顺序
    //startNo:表示从第几个小孩开始数数
    //countNum:表示数几下
    //nums:表示最初有多少小孩在圈中
    public void countBoy(int startNo,int countNum,int nums){
        //先对数据进行校验
        if(first==null || startNo<1 || startNo>nums){
            System.out.println("参数输入有误,请重新输入");
            return;
        }
        //创建辅助指针,帮助小孩完成出圈
        Boy helper = first;
        //创建一个辅助指针(变量)helper,事先应该指向环形链表的最后一个节点
        while(true){
            if(helper.getNext() == first){ //说明helper指向最后一个小孩节点
                break;
            }
            helper = helper.getNext();
        }
        //小孩报数前,先让first和helper移动k-1次,找到第一个报数的节点
        for(int j=0;j<startNo-1;j++){
            first = first.getNext();
            helper = helper.getNext();
        }
        //当小孩报数时,让first和helper指针同时移动m-1次,同时出圈
        //这里是一个循环操作,知道圈中只有一个节点
        while(true){
            if(helper == first){
                break;
            }
            //让first和helper指针同时移动countNum-1
            for(int j=0;j<countNum-1;j++){
                first = first.getNext();
                helper = helper.getNext();
            }
            //这时first指向的节点,就是要出圈的小孩节点
            System.out.printf("小孩%d出圈\n",first.getNo());
            //这时将first指向的小孩节点出圈
            first = first.getNext();
            helper.setNext(first);
        }
        System.out.printf("最后留在圈中的小孩编号%d\n",first.getNo());
    }
}

//创建一个Boy类,表示一个节点
class Boy{
    private int no; //编号
    private Boy next; //指向下一个节点,默认null
    //构造方法,给编号赋值
    public Boy(int no){
        this.no = no;
    }
    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;
    }
}

2.2 约瑟夫问题版本二

问题描述:

传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,从而逃过了这场死亡游戏 。

问题分析:

41个人坐一圈,第一个人编号为1,第二个人编号为2,以此类推,第n个人编号为n

  1. 编号为1个人开始从1报数,依次向后,报数为3的那个人退出圈;
  2. 从退出那个人开始的下一个人再次从1开始报数,以此类推;
  3. 求出最后退出的那个人的编号

如图所示:
循环链表之约瑟夫问题java代码实现_第4张图片
解题思路:

(1) 建含有41个节点的单向循环链表,分别存储1~41的值,分别代表41个人;
(2) 使用计数器count,记录当前报数的值;
(3) 遍历链表,每循环一次,count++
(4) 判断count的值,如果是3,则从链表中删除这个节点并打印节点的值,把count 重置为0.

代码实现:

public class Josephu2{
    public static void main(String[] args) {
        //1.构建循环链表
        Node first = null;
        //记录前一个节点
        Node pre = null;
        for(int i = 1;i<=41;i++){
            //第一个元素
            if(i==1){
                first = new Node(i,null);
                pre = first;
                continue;
            }
            Node node = new Node(i,null);
            pre.next = node;
            pre = node;
            if(i==41){
                //构建循环链表,让最后一个节点指向第一个节点
                pre.next = first;
            }
        }

        //2.使用count,记录当前的报数的值
        int count = 0;
        //3.遍历链表,每循环一次,count++
        Node n = first;
        Node before = null;
        while(n!=n.next){
            //4.判断count的值,如果是3,则从链表中删除这个节点并打印
            count++;
            if(count == 3){
                //删除当前节点
                before.next = n.next;
                System.out.print(n.item+",");
                count = 0;
                n = n.next;
            }else{
                before = n;
                n = n.next;
            }
        }
        //打印剩余的最后一个人
        System.out.println(n.item);
    }

}
//节点类
class Node{
    //存储数据
    public int item;
    //指向下一个节点
    public Node next;
    public Node(int item,Node next){
        this.item = item;
        this.next = next;
    }
}

运行结果:
在这里插入图片描述

你可能感兴趣的:(数据结构)