环形链表介绍以及约瑟夫问题

约瑟夫问题

具体内容:

设编号为1到n的n个人围坐成一圈,约定编号为k(k是1到n之间任何一个数)的人开始报数,数到m的那个人出列,他的下一位又从1开始报数,数到m那个人又出列,依此类推,直到所有人出列,产生一个出列编号的序列

解决方法:
  1. 数组取模的环形运算
  2. 单向环形链表

单向环形链表

单链表与循环链表的区别

  1. 循环链表是头尾相接的单链表,单链表的终端结点指针端由空指针指向头结点
  2. 遍历的判断条件上:单链表的判断条件是p.next是否等于null,循环链表的判断条件是p.next是否等于头结点
循环链表解决约瑟夫问题
构建单向循环链表
  1. 创建结点的类,包含编号和next指针
  2. 选择初始结点为头结点first,头结点不动
  3. 定义辅助变量curBoy来添加新的结点
  4. 从单向循环的空链表开始构建
代码实现:

我的代码:

package circularlinkedlist;

public class CirLinkedlist {
    public static void main(String[] args) {
        CLL c1 = new CLL();
        c1.add(5);
        c1.show();
    }
}
class CLL{
    private Boy first = null;

    public void add(int nums){
        //传入约瑟夫环的人数,先要校验一下认数
        Boy curBoy = null;
        if(nums < 1){
            System.out.println("输入人数有误");
           // return;
            //加一个return,结束程序,加了return就没有必要再加上else 
        }else{
            for(int i = 1;i <= nums ; i ++){
                if(i == 1){
                    first = new Boy(1);
                    first.setNext(first);
                    curBoy = first;
                }else{
                    Boy boy = new Boy(i);
                    curBoy. setNext(boy);
                    boy.setNext(first);
                    curBoy = boy;
                }
            }
        }
    }
    public void show(){
        Boy curBoy = first;
        while (true){
            System.out.println(curBoy);
            curBoy = curBoy.getNext();
            if(curBoy == first){
                break;
            }

        }
    }
}
class Boy{
    private int no;
    private Boy next;

    public int getNo() {
        return no;
    }

    public Boy getNext() {
        return next;
    }

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

    public void setNo(int no) {
        this.no = no;
    }

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

    @Override
    public String toString() {
        return "Boy{" +
                "no=" + no +
                '}';
    }

教程代码:

class CLL{
    private Boy first = null;

    public void add(int nums){
        //传入约瑟夫环的人数,先要校验一下认数
        Boy curBoy = null;
        if(nums < 1){
            System.out.println("输入人数有误");
           // return;
            //加一个return,结束程序,加了return就没有必要再加上else
        }else{
            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);
                    boy.setNext(first);
                    curBoy = boy;
                    //辅助指针后移
                }
            }
        }
    }
public void show(){

    if(first == null){
        System.out.println("空链表");
        return;
    }
    Boy curBoy = first;
    while (true){
        System.out.println(curBoy);
        if(curBoy == first){
            break;
        }
        curBoy = curBoy.getNext();
    }
}

对比:

  1. 对于if条件句他用return要较多,而不是else,确实较少了嵌套
  2. 遍历时判断循环链表是否为空,直接看头指针是否为空

产生一个出圈的数列

思路分析 :
  1. 出圈意味着删除,单向链表的删除必须要找到待删除结点的前一个结点,故而设置一个辅助指针helper
  2. 解决报数问题,first开始报数,根据k的值first向后移,同时helper跟着first移动等待删除对应的指针
  3. 输出的同时删除对应的结点,first = first.next,helper.next = first
代码实现:

我的代码:

   public void run(int k){
        if(k < 0 || k > nums){
            System.out.println("输入的数字有问题");
            return;
        }
        Boy helper = new Boy();
        helper.setNext(first);
        while (true){
            for(int i = 1; i < k; i++){
                first = first.getNext();
                helper = helper.getNext();
                //helper = setNext(first);
            }
            System.out.println(first);
            first = first.getNext();
            helper.setNext(first);
            if (first == null){
                System.out.println(first);
                break;
            }
        }
    }

运行异常:
错误运行的代码
问题分析:陷入死循环,而且仅仅重负输出三号,说明迭代没有成功,把helper = helper.getNext()改为helper.setNext(first),让helper始终以first为后继指针,而不是仅仅自己去循环。

问题:两种方式效果不是相同的吗?

问题:就算我不把curBoy设置成尾结点,但是我设置成头结点前一个指针仍旧可以开始循环?

确实可以
出现如下的错误:
错误描述
说明输出的顺序没有问题,但是终止条件有问题,不能跳出循环,说明first永远不会为空。就说明没有有效的删除相应的结点。好吧!对了很久,也没想通为什么没删除!确实没删除,你就是完全按照单数输出的。

问题解决

出现未删除结点的情况,是因为helper.setNext(first);你的helper从来没有进过循环,是空的指针,要使其真正删除结点,必须把helper迭代进入循环才可以

教程代码:
public void run(int startNo,int countNum,int nums){
        if(first == null||countNum < 1 || countNum > nums){
            System.out.println("输入的数字有问题");
            return;
        }
        //关于参数校验,想到了可能数的数有问题,但是没想到回事空链表
        Boy helper = first;
        //需求创建一个辅助指针helper,实现指向环形链表的最后的结点
        //helper.setNext(first);我对单向链表的理解还是有问题的
        while (true){
            if(helper.getNext() == first){
                break;
            }
            helper = helper.getNext();
        }
        //必须而且只能够通过这样的方式才能够获得最后一个指针的地址
        //通过头指针只能向后看,而且必须看一圈,才能够看清最后一个是啥
        //小孩报数前,先让first和helper移动到对应的位置
        for(int j = 0;j < startNo - 1;j ++){
            first = first.getNext();
            helper = helper.getNext();
        }
        //当开始报数时,让first和helper指针同时移动m-1次,然后出圈
        while (true){
            if (helper == first){
                break;
            }
            //判断条件说明仅仅只有一个结点,helper == first
            for(int i = 0; i < countNum - 1; i++){
                first = first.getNext();
                helper = helper.getNext();
            }
            System.out.println(first);
            first = first.getNext();
            helper.setNext(first);
        }
        System.out.println(first);
    }

}

总结与对比:

  1. 对于单向链表有一个错误的理解,仅仅把helper.next定位first找不到尾节点,只能一步一步遍历一遍才行
  2. 关于数字的校验,没想到会是空链表,仅仅想到了数字的范围
  3. 对于循环链表的理解不够,他不可能把自己变成空的,循环结束条件应该为两个指针重合
  4. 最重要的时对于题目的分析,真的是没有任何条理性和步骤性

兄弟水太浅,还是要多练!!!

代码改良:

public void run(int startNo, int countNum, int nums) {
        //校验输入的数字
        if(first == null || countNum < 0 || countNum > nums){
            System.out.println("输入的数字有问题");
            return;
        }
        Boy helper = new Boy();
        helper.setNext(first);
        for(int j = 0;j < startNo - 1;j ++){
            first = first.getNext();
            helper = helper.getNext();
        }
        while (true){
            for(int i = 1; i < countNum; i++){
                first = first.getNext();
                helper = helper.getNext();
            }
            System.out.println(first);
            first = first.getNext();
            helper.setNext(first);
            if (first == helper){
                System.out.println(first);
                break;
            }
        }
    }

没必要让helper一开始就是尾结点,让他的下一个指针是头结点就行,随着第一次循环的开始,他就会进入循环链表,跟在first后面开始循环。如果出现未删除的指针,说明你的辅助指针没有进入循环链表,单个first是删除不了的。所以先把循环放在第一步。

发布了19 篇原创文章 · 获赞 3 · 访问量 624

猜你喜欢

转载自blog.csdn.net/Blackoutdragon/article/details/103806929