单向环形链表——解决约瑟夫问题Java

Josephu问题

Josephu问题为:
设编号为1, 2, …n的n个人围坐一圈,约定编号为k (1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

思路:

  • 我们不妨创建一个单向环形链表来模拟这种情况,根据小孩的出列断开不同的节点,连接新的节点进行模拟。

代码:

package 链表;

public class JosePhu问题 {
    public static void main(String[] args) {
        CircleLinkedList circleLinkedList = new CircleLinkedList();
        circleLinkedList.addBoy(5);   //此时环形链表有五个节点
        circleLinkedList.list();
        circleLinkedList.countBoy(1,2,5);
    }

}

class CircleLinkedList{
    private Boy first = null;    //初始头节点

    //构建指定长度的环形链表
    public void addBoy(int num){
        Boy cur = null;  //用来遍历的辅助指针
        for(int i=1;i<=num;i++){
            Boy boy = new Boy(i);
            if(i==1){
                //第一次构建需要自身指向自身
                first = boy;
                first.next = first;
                cur = first;
            }
            else{
                cur.next = boy;   //将新建立的节点加入环形链表中,此时cur的位置就是原先创建的环形链表的尾端
                boy.next = first; //将添加进来的新节点的尾部指向头节点,形成环状
                cur = cur.next;  //cur每次要移动到尾部
            }
        }
    }

    //按照添加顺序显示环形链表
    public void list(){
        if(first==null){
            System.out.println("环形链表为空");
            return;
        }
        Boy cur = first;  //用来遍历的临时指针
        while(true){
            System.out.println(cur.num);  //先打印
            if(cur.next==first){  //如果其next指向头指针,则代表遍历完全
                return;
            }
            cur = cur.next;
        }
    }
/*
* startNo  表示从第几个小孩开始数数
* countNum 表示数几下
* nums     表示最初有多少小孩在里面
* 这个是用来解决约瑟夫问题的方法,即指定小孩人数,第一次报数从哪个小孩开始,每数到几让谁出来 后,我们使用环形链表来模拟出队顺序
* */
    public void countBoy(int startNo,int countNum,int nums){
        /*
        * 其实可以不用两次循环,使用一次循环来解决,即first向前移动时,help跟着移动,只不过移动的要比first少1即可;
        * 但是这样的话对于countNum为0时则不行
        * */
        Boy help = first;  //辅助指针,指向头指针的前一个位置
        /*因为我们下面是设置头指针直接移动到我们要出队的节点,而又由于我们是一个单向环形队列,所以头指针位置要出队,那么我们
         为了保存头指针位置,只能从头指针的前一个位置得到,所以我们设置辅助指针的目的就是为了通过其的next指针来得到删去的
        头指针位置!!!! 所以其实这个也可以不设置,不过设置了会更加清晰*/

        //先将辅助指针移动到尾部,因此此时头指针在头部,所以刚好形成前后关系
        while(true){
            if(help.next==first){
                break;
            }
            help = help.next;
        }

        //因此从第startNo个小孩开始报数,所以头指针的实际位置需要改变
        for (int i=0;i<startNo-1;i++){
            first = first.next;
            help = help.next;   //help是头指针的前一个,所以也跟着动
        }

        //模拟报数出队,直到全部出完
        while(true){
            for (int i=0;i<countNum-1;i++){  //先报数,即头指针先移动到指定要出队的节点的位置
                first = first.next;
                help = help.next;  //辅助指针跟着移动
            }
            System.out.println(first.num);  //找到要出队的了
            if(help==first){   //如果两个指针重合,则一定代表就剩下一个了,因为一个指针的下一个指针指向自己了!
                break;
            }
            first = first.next;  //将头指针此时指向的位置删除,并指向自己原先位置指向的的下一个位置
            help.next = first;  //将被删除的节点的上一个节点的next指向设置为删除位置的下一个位置 , 这样才起到删除作用
        }
    }
}

class Boy{
    int num;
    Boy next;

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

    public Boy() {
    }

}
上一篇:是该公司的股东是


下一篇:【剑指Offer1】复杂链表的复制