Java学习——数据结构之约瑟夫问题

约瑟夫 问题描述:设编号为1、2、3... ...n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人先出列,他的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人都出列为止,由此产生一个出队编号的序列,求此序列。

 

算法思路:

先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,保存对应节点的编号,并将节点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表删除。

代码分析:

1.构建环形链表:

  • 创建第一个结点,并让first指向该结点,并将该节点的next域指向自己形成环形。

  • 每创建一个新节点,就把该节点加入到已有的环形链表中

     

2.遍历环形链表

  • 定义一个辅助变量curBoy ,指向first节点

  • 通过while循环找到最后一个加入环形链表的结点,并将curBoy指向这个节点,即:curBoy.next==first

  • (添加这个辅助变量的目的是保存first的前趋,便于删除报数完毕后first指向的结点)

3.报数

  • 每一轮报数时,使用for循环m次,每循环一次,first和curBoy同时往下一位报数的人的方向移动一次。

  • 当循环完毕m次时,保存first的编号,然后删除first结点:

    • int n=first.no

    • first=first.next;

    • curBoy.next=first

  • 每一轮报数结束,使用数组存储出列的编号,当圈中只剩下一个人时,直接将其编号添加至数组,最后依次打印数组的值即为出队编号的序列。

     

代码实现

结点:

//创建一个Boy类,表示一个节点
class Boy{
    private int no;  //编号
    private Boy next; //指向下一个结点,默认null

    public Boy(int no) {
        this.no = no;
    }

    public int getNo() {
        return no;
    }

    public Boy getNext() {
        return next;
    }

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

链表:

//创建一个环形的单向链表
class CircleLinkedList{
    //创建一个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;
            }else {
                curBoy.setNext(boy);
                curBoy=boy;
                boy.setNext(first);
            }
        }

    }
    //遍历当前的环形链表
    public void showBoy(){
        //判断链表是否为空
        if(first==null) {
            System.out.println("链表为空,没有任何小孩!");
            return;
        }
        Boy curBoy = first;
        while (true){
            System.out.printf("小孩的编号:%d\n",curBoy.getNo());
            if(curBoy.getNext()==first){
                //说明已经遍历完毕
                break;
            }
            curBoy=curBoy.getNext(); //curBoy后移直到其next指向first
        }
    }

    //根据用户的输入,计算出小孩的出圈顺序
    /*
        startNo 开始的小孩的编号
        countNo 表示数几下
        nums    表示最初的小孩的个数
     */
    public int[] countBoy(int startNo,int countNo,int nums){
        Boy boyFirst = first;
        for (int i=1;i<startNo;i++)
            boyFirst=boyFirst.getNext();  //遍历到开始报数的小孩
        Boy pre=boyFirst; //记录每一轮开始报数的前一个小孩,也就是每一圈的最后一个小孩
        for (int i=1;i<nums;i++)
            pre = pre.getNext();

        int[] boys = new int[nums]; //记录出圈的编号序列
        for (int j=0;j<nums;j++) {
            for (int i = 0; i < countNo - 1; i++) {                               //下一圈的开始和最后一起后移,一直移到出圈的小孩
                pre = pre.getNext();
                boyFirst = boyFirst.getNext();
            }

            boys[j] = boyFirst.getNo();//记录出圈的小孩编号

            boyFirst=boyFirst.getNext();//删除出圈小孩的结点
            pre.setNext(boyFirst);
        }
        return boys;

    }
}

 

上一篇:css定位 伪类 伪元素


下一篇:CF1006B 题解