3

作者前言


大家好,我是阿濠,今篇内容跟大家分享的是数据结构之链表,很高兴分享到segmentfault与大家一起学习交流,初次见面请大家多多关照,一起学习进步.

一、介绍认识什么是链表?

链表是有序的列表,某种程度上避免数组的缺陷,即分配数组时需要开辟一串连续的内存空间,但鱼和熊掌不可兼得,链表也牺牲了一些数组的优点:链表不能通过下标快速查询,所以考虑是否需要链表的时候需要先考虑是否算法是否经常需要查询和遍历

比如说单链表它在内中存储图示:

图片.png

介绍小结:
1)链表是以节点的方式来存储,它是以链式存储
2)每个节点包含data域、next域 指向下一个节点. .
3)如图发现链表的各个节点地址不一定是连续存放,比如150地址下一节点指向110地址
4)链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定

二、通过应用示例熟悉单链表

题目:使用带head头单向链表实现-水浒108位英雄排行榜管理

单链表的添加

1)完成对英雄人物的增删改查操作,注: 删除和修改,查找
2)第一种方法在添加英雄时,不按照排名直接添加到链表的尾部
3)第二种方式在添加英雄时,根据排名将英雄插入到指定位置
(如果有这个排名,则添加失败,并给出提示)

第一种方法实现:创建示例图(添加/创建)显示思路分析

图片.png

1.先创建一个head头节点,作用就是表示单链表的头
2.后面我们每添加一个节点,就直接加入到链表的最后

遍历的时候
1.通过一个辅助指针,帮助遍历整个链表

英雄代码编写如下:

//定义英雄节点,每一个herNode对象就是一个节点
public class HeroNode {

    public int no;//编号
    public String name;//名称
    public String nickname;//外号昵称
    public HeroNode next; //指向下一个节点

    //构造器
    public HeroNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name ;
        this .nickname = nickname;
    }

    @Override
    public String toString() {
        return "HeroNode [ no="+no+"],name=["+name+"],nickname=["+nickname+"]";
    }
}

链表管理英雄节点编写如下:

//链表管理英雄节点
public class SingleLinkedList {

    //先初始化一一个头节点,头节点不要动,不存放具体的数据
    private HeroNode head = new HeroNode(0, "", "");

    //添加节点到单项链表里
    public void add(HeroNode heroNode) {
        //思路如下:
        //1.找到当前链表的最后节点
        //2.找到最后节点的next指向新添加的节点
        //3.使用辅助指向head,然后遍历找到最后节点
        HeroNode temp=head;
        while(true){
            //找到节点的next指针为空时退出
            if(temp.next == null){
                break;
            }
            //当前节点的next!=null 则往后移
            temp=temp.next;
        }
        //break结束循环则代表找到
        temp.next=heroNode;
    }
    //显示链表[遍历]
    public void list() {
        //判断链表是否为空
        if(head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //分析思路提到头节点不能动,需要使用临时遍历辅助
        HeroNode temp=head.next;
        while (true){
            //如果临时遍历的下一节点指向为空
            //则代表后面没有值则结束循环
            if(temp == null){
                break;
            }
            //否则则输出信息,并指向下一个节点继续
            //因为重写了toString方法直接可以输出temp
            System.out.println(temp);
            //将temp指向下一个节点
            temp=temp.next;
        }
    }
}

Demo测试数据代码编写如下:

//进行创建节点数据进行测试
HeroNode heroOne=new HeroNode(1,"宋江","及时雨");
HeroNode heroTwo=new HeroNode(2,"卢俊义","玉麒麟");
HeroNode heroThree=new HeroNode(3,"吴用","智多星");
HeroNode heroFour=new HeroNode(4,"林冲","豹子头");

//创建链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
//加入英雄节点
singleLinkedList.add(heroOne);
singleLinkedList.add(heroTwo);
singleLinkedList.add(heroThree);
singleLinkedList.add(heroFour);
//循环遍历链表
singleLinkedList.list();

结果如下:
HeroNode [ no=1],name=[宋江],nickname=[及时雨]
HeroNode [ no=2],name=[卢俊义],nickname=[玉麒麟]
HeroNode [ no=3],name=[吴用],nickname=[智多星]
HeroNode [ no=4],name=[林冲],nickname=[豹子头]

此时显示的结果顺序是正确的,若我是修改一下代码,此时显示顺序是正确的嘛?

singleLinkedList.add(heroOne);
singleLinkedList.add(heroFour);
singleLinkedList.add(heroThree);
singleLinkedList.add(heroTwo);

运行结果如下:
HeroNode [ no=1],name=[宋江],nickname=[及时雨]
HeroNode [ no=4],name=[林冲],nickname=[豹子头]
HeroNode [ no=3],name=[吴用],nickname=[智多星]
HeroNode [ no=2],name=[卢俊义],nickname=[玉麒麟]

第一个使用是松江,第二个就变成林冲了,为什么?
因为是按照添加的顺序来显示的,说明没有考虑编号排序

那么第一种方法添加到链表尾部已完成,现在按照第二种方法来完成添加

第二种方法实现:创建示例图(添加/创建)显示思路分析

图片.png

需要按照编号的顺序添加
1.首先找到新添加的节点的位置,通过辅助变量(指针),需要遍历搞定
2.新的节点.next = 原来 temp.next
3.将temp.next=新的节点

(比如说添加新节点数据3 它是存放在数据2后的,即节点数据2的next指针,原本指向节点数据4,现在变成新节点数据3的next节点指向节点数据4,而节点数据2的next节点变成指向新节点数据3)

链表管理英雄节点添加新方法代码编写如下:

 //第二种方式在添加英雄时,根据排名将英雄插入到指定位置
//(如果有这个排名,则添加失败,并给出提示)
//思路判断
//1.判断是否temp遍历已是链表最后
//2.找到插入节点的合适位置
//3.判断插入节点是否已有对应的节点
public void addBy0rder (HeroNode heroNode) {
    //因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
    //因为单链表,因为我们找的temp是位于添加位置的前一个节点,否则插入不了
    HeroNode temp = head;
    boolean flag = false; // 标志添加的编号是否存在,默认为false
    while (true) {
        if (temp.next == null) {
            //说明temp已经在链表的最后直接添加
            break;
        }
        if (temp.next.no > heroNode.no) {
            //temp的下一个节点的编号>添加节点的编号
            //代表位置找到,就在temp的后面插入
            //比如说节点2 的next指向 节点4
            //此时插入节点3 那么temp指向节点,此时temp的next是节点4
            //节点4的编号>节点3 代表节点3插入在节点2之后,节点4 之前
            break;
        } else if (temp.next.no == heroNode.no) {
            //说明希望添加的heroNode的编号已然存在
            flag = true;
            break;
        }
        //若三个条件都没有满足则进行往后移
        temp = temp.next;
    }
    //判断flag的值,若为true则代表节点已存在
    if (flag){
        System.out.printf("插入的该英雄编号 %d 已存在,不能再次添加了\n",heroNode.no);
    }else{
        //插入到链表中,temp的后面
        //新的节点下一节点指向原本temp的下一节点
        heroNode.next = temp.next;
        //temp的下一节点指向新增的节点
        temp.next  = heroNode;
    }

}

现在我们再次添加测试数据看看能否成功呢?

//加入按照编号的顺序
singleLinkedList.addBy0rder(heroOne);
singleLinkedList.addBy0rder(heroFour);
singleLinkedList.addBy0rder(heroThree);
singleLinkedList.addBy0rder(heroTwo);

运行结果如下:
HeroNode [ no=1],name=[宋江],nickname=[及时雨]
HeroNode [ no=2],name=[卢俊义],nickname=[玉麒麟]
HeroNode [ no=3],name=[吴用],nickname=[智多星]
HeroNode [ no=4],name=[林冲],nickname=[豹子头]

若此时我再添加一次豹子头林冲呢?

singleLinkedList.addBy0rder(heroFour);

运行结果如下:
插入的该英雄编号 4 已存在,不能再次添加了

根据第二方法的代码实现,我们就能在内存中就将顺序排好,这是比数据库中还快

单链表的修改

假如我们给出一个新的节点,完成对原有节点进行名称、昵称修改,那么如何操作呢?

思路分析:
1.根据编号修改
2.编号不变,名称,昵称可变

链表管理英雄节点添加新方法代码编写如下:

 //修改节点的信息,根据no编号来修改,即no编号不能改。
//说明根据newHeroNode的no来修改即可
public void update(HeroNode newHeroNode) {
    //判断是否空
    //因为head是没有数据的
    if (head.next == null) {
        System.out.println("链表为空~");
        return;
    }
    //找到需要修改的节点,根据no编号
    //定义一个辅助变量
    HeroNode temp = head.next;
    boolean flag = false;
    while(true){
        if (temp == null) {
            break; //已经遍历完链表
        }
        //根据temp的值
        if(temp.no == newHeroNode.no) {
            //找到
            flag = true;
            break;
        }//没有找到往后移
        temp = temp. next;
    }
    if(flag){
        //原节点进行修改信息
        temp.name=newHeroNode.name;
        temp.nickname=newHeroNode.nickname;
    }else{
        //没有找到的时候仍然为false
        System.out.printf("没有找到编号 %d 的节点,不能修改\n",newHeroNode.no) ;
    }
}

现在我们测试数据看看能否成功呢?比如将林冲修改为小白,豹子头改为小白龙

HeroNode hero=new HeroNode(4,"小白","小白龙");
singleLinkedList.update(hero);
//显示数据
singleLinkedList.list();

运行结果如下:
修改信息后的数据--------------
HeroNode [ no=1],name=[宋江],nickname=[及时雨]
HeroNode [ no=2],name=[卢俊义],nickname=[玉麒麟]
HeroNode [ no=3],name=[吴用],nickname=[智多星]
HeroNode [ no=4],name=[小白],nickname=[小白龙]

单链表的删除

图片.png

假设我现在有节点数据如上,若我想删除数据4,该怎么操作嘛?

思路分析:
1.仍然需要temp辅助节点,找到需要删除节点的前一个节点temp
2.修改temp.next=temp.next.next

(比如说节点2的next节点是节点4 修改为节点2的next节点的next节点,即是节点4的next节点)

链表管理英雄节点添加新方法代码编写如下:

 //思路
//1. head 不能动,因此我们需要-个temp辅助节点找到待删除节点的前一个节点
//2.说明我们在比较时,是temp .next.no和需要删除的节点的no比较
public void del(int no) {
    HeroNode temp = head;
    boolean flag = false; //标志是否找到待删除节点的
    while(true) {
        if(temp.next == null ) { //已经到链表 的最后
            break;
        }
        if(temp.next.no == no) {
            //找到的待删除节点的前一个节点temp
            flag = true;
            break;
        }
        temp = temp.next; //temp后移遍历
    }
    //判断flag
    if(flag) { //找到 .
        //可以删除
        temp.next = temp. next. next;
    }else {
        System .out .printf("要删除的%d节点不存在",no);
    }
}

现在我们测试数据看看能否成功呢?比如将宋江删除

//删除一个节点
singleLinkedList.del(1);
System .out .println("删除后的链表情况--------------");
singleLinkedList.list();

运行结果如下:
HeroNode [ no=1],name=[宋江],nickname=[及时雨]
HeroNode [ no=2],name=[卢俊义],nickname=[玉麒麟]
HeroNode [ no=3],name=[吴用],nickname=[智多星]
HeroNode [ no=4],name=[小白],nickname=[小白龙]
删除后的链表情况--------------
HeroNode [ no=2],name=[卢俊义],nickname=[玉麒麟]
HeroNode [ no=3],name=[吴用],nickname=[智多星]
HeroNode [ no=4],name=[小白],nickname=[小白龙]

单链表面试题(新浪、百度、腾讯)

1)求单链表中节点的个数
2)查找单链表中的倒数第k个结点[ 新浪面试题]
3)单链表的反转[腾讯面试题]
4)从尾到头打印单链表[百度,要求方式1:反向遍历。方式2: Stack栈]
5)合并两个有序的单链表,合并之后的链表依然有序

题目一:

思路分析:
获取到单链表的节点的个数(如果是带头结点的链表,需求不统计头节点)

/**
 * @param  head 链表的头节点
 * @return 返回的就是有效节点的个数
 */
public static int getLength(HeroNode head) {
    if(head.next == null) { //空链表
        return 0;
    }
    int length = 0;
    //定义一个辅助的变量
    HeroNode cur = head .next ;
    while(cur != null) {
        length++;
        cur = cur.next; //遍 历
    }
    return length;
}

题目二:

思路分析:
1.编写一个方法,接收head节点,同时接收一个index
2.index 表示是倒数第index个节点
3.先把链表从头到尾遍历,得到链表的总的长度getLength
4.得到size后,我们从链表的第-个开始遍历(size-index)个,就可以得到
5.如果找到了,则返回该节点,否则返回nu111

//查找单链表中的倒数第k个结点[新浪面试题]
public static HeroNode findLastIndexNode(HeroNode head, int index) {

    //判断如果链表为空,返回null
    if (head.next == null) {
        return null;//没有找到
    }
    //第一个遍历得到链表的长度(节点个数)
    int size = getLength(head);

    //第二次遍历size-index 位置,就是我们倒数的第K个节点
    //先做index的校验
    // 不能说一共才5个,你要求找倒数第六个数或者负数
    if (index <= 0 || index > size) {
        return null; //没有找到
    }
    //定义给辅助变量,for循环定位到倒数的index
    HeroNode cur = head.next;
    //比如说有效数据为三个 找倒数第一个
    // 3-1 = 2  二次就找到了
    for (int i = 0; i < size - index; i++) {
        cur = cur.next;
    } 
    return cur;
}

题目三:

图片.png

思路分析:
1.先定义一个节点reverseHead =new HeroNode();
2.从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端.
3.原来的链表的head.next =reverseHead.next

图片.png

图片.png

图片.png

//将单链表反转
public static void reversetList(HeroNode head) {

    //如果当前链表为空,或者只有一个节点,无需反转,直接返回
    //链表里只有一个节点时,它的next节点也是空的
    if (head.next == null || head.next.next == null) {
        return;
    }
    //定义一个辅助的指针(变量),帮助我们遍历原来的链表
    HeroNode cur = head.next;
    HeroNode next = null;//指向当前节点[cur]的下一个节点.
    HeroNode reverseHead = new HeroNode(0,"","");
    //遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
    //动脑筋
    while(cur != null) {
        next = cur.next;//先暂时保存当前节点的下一个节点,因为后面需要使用
        cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
        reverseHead.next = cur; //将cur 连接到新的链表上
        cur = next;//icur后移
    }
    //将head. next指向reverseHead.next, 实现单链表的反转
    head.next = reverseHead. next;
}

题目四:

图片.png

思路分析:

要求就是逆序打印单链表.
1.方式1:先将单链表进行反转操作,然后再遍历即可,这样的做的问题是会破坏原来的单链表的结构,不建议
2.方式2:可以利用这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果

图片.png

//演示栈Stack的基本使用
public class TestStack {
    public static void main(String[] args) {
        Stack<String> stack = new Stack();
        //入栈
        stack.add("jack");
        stack.add("tom");
        stack.add("smith");
        //出栈
        while (stack.size() > 0) {
             System.out.println(stack. pop());//pop就是将栈顶的数据取出
         }
    }   
}

运行结果如下:
smith
tom
jack

那么我们发现栈是符合这个要求的,可以使用栈进行逆序打印链表

//方式2:链表结构顺序没有改变,使用逆序打印
//可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果
public static void reversePrint(HeroNode head) {
    if(head.next == null) {
        return;//空链表,不能打印
    }
    //创建要给一个栈,将各个节点压入栈
    Stack<HeroNode> stack = new Stack<HeroNode>();
    HeroNode cur = head.next;
    //将链表的所有节点压入栈
    while(cur != null) {
        stack.push(cur);
        cur = cur.next; //cur后移,这样就可以压入下一个节点
    }
    //将栈中的节点进行打印,pop出栈
    while (stack.size() > 0) {
        System.out. println(stack.pop());
    }
}

三、通过应用示例熟悉双链表

刚刚通过示例认识到单链表,但是单链表相比双链表也是有些缺点的
1)单向链表:查找的方向只能是一个方向,而双向链表可以向前或者向后查找
2)单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是需要找到temp,而temp是待删除节点的的前一个节点

3)示意图理解区别
图片.png

分析双向链表的遍历,添加,修改,删除的操作思路
1>:遍历方法和单链表一样,只是可以向前,也可以向后查找

2>:添加:(默认添加)到双向链表的最后,需要先找到找到双向链表的最后这个节点
2.1>:最后节点temp.next = 新节点 newHeroNode
2.2>:新节点 newHeroNode.pre = 最后节点 temp

3>:修改思路原理和单链表一样

4>: 删除:因为是双向链表 可以实现自我删除某个节点,直接找到需要删除的节点temp
4.1>:temp.pre.next=temp.next;
4.2>:temp.next.pre=temp.pre;

题目:使用带head头单向链表实现-水浒108位英雄排行榜管理

英雄代码编写如下:

//定义英雄节点,每一个herNode对象就是一个节点
public class HeroNode2 {

    public int no;
    public String name;
    public String nickname;
    public HeroNode2 next; //指向下一个节点 默认为null
    public HeroNode2 pre;  //指向上一个节   默认为null
    //构造器
    public HeroNode2(int no, String name, String nickname) {
        this.no = no;
        this.name = name ;
        this .nickname = nickname;
    }

    @Override
    public String toString() {
        return "HeroNode [ no="+no+"],name=["+name+"],nickname=["+nickname+"]";
    }
}

双链表管理英雄节点代码编写如下:

package com.bdqn.it.test;

public class DouobleLikedList {

    //先初始化一一个头节点,头节点不要动,不存放具体的数据
    private HeroNode2 head = new HeroNode2(0, "", "");

    //显示链表[遍历]
    public void list() {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //分析思路提到头节点不能动,需要使用临时遍历辅助
        HeroNode2 temp = head.next;
        while (true) {
            //如果临时遍历的下一节点指向为空
            //则代表后面没有值则结束循环
            if (temp == null) {
                break;
            }
            //否则则输出信息,并指向下一个节点继续
            //因为重写了toString方法直接可以输出temp
            System.out.println(temp);
            //将temp指向下一个节点
            temp = temp.next;
        }
    }
    //添加节点
    public void add(HeroNode2 heroNode) {

        HeroNode2 temp = head;
        while (true) {
            //找到节点的next指针为空时退出
            if (temp.next == null) {
                break;
            }
            //当前节点的next!=null 则往后移
            temp = temp.next;
        }
        //break结束循环则代表找到
        temp.next = heroNode;
        //新增节点的前一个节点为当前最后一个节点
        heroNode.pre=temp;
    }

    //修改节点的信息,根据no编号来修改,即no编号不能改。
    //说明根据newHeroNode的no来修改即可
    public void update(HeroNode2 newHeroNode) {
        //判断是否空
        //因为head是没有数据的
        if (head.next == null) {
            System.out.println("链表为空~");
            return;
        }
        //找到需要修改的节点,根据no编号
        //定义一个辅助变量
        HeroNode2 temp = head.next;
        boolean flag = false;
        while (true) {
            if (temp == null) {
                break; //已经遍历完链表
            }
            //根据temp的值
            if (temp.no == newHeroNode.no) {
                //找到
                flag = true;
                break;
            }//没有找到往后移
            temp = temp.next;
        }
        if (flag) {
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        } else {
            //没有找到的时候仍然为false
            System.out.printf("没有找到编号 %d 的节点,不能修改\n", newHeroNode.no);
        }
    }

    //从双向链表中删除节点
    //思路
    //1.对于双向链表,我们可以直接找到要删除的这个节点
    //2.找到后,自我删除即可
    public void del(int no) {


        if (head.next == null) { //空链表
            System.out.println("链表为空,无法删除!");
        }
        boolean flag = false; //标志是否找到待删除节点的
        //直接指向删除的节点
        HeroNode2 temp = head.next;
        while (true) {
            if (temp == null) { //空链表
               //已经到了链表最后的后一个
                break;
            }
            if (temp.no == no) {
                //找到的待删除节点的前一个节点temp
                flag = true;
                break;
            }
            temp = temp.next; //temp后移遍历
        }
        //判断flag
        if (flag) { //找到 .
            //可以删除
            temp.pre.next = temp.next;
            if(temp.next!=null){
                //若删除的节点是最后,则会出现空指针异常
                temp.next.pre=temp.pre;
            }
        } else {
            System.out.printf("要删除的%d节点不存在", no);
        }
    }

}

现在我们添加测试数据看看能否操作成功?


//创建节点做数据测试
HeroNode2 heroOne=new HeroNode2(1,"宋江","及时雨");
HeroNode2 heroTwo=new HeroNode2(2,"卢俊义","玉麒麟");
HeroNode2 heroThree=new HeroNode2(3,"吴用","智多星");
HeroNode2 heroFour=new HeroNode2(4,"林冲","豹子头");


//创建链表
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();

//加入
doubleLinkedList.add(heroOne);
doubleLinkedList.add(heroTwo);
doubleLinkedList.add(heroThree);
doubleLinkedList.add(heroFour);

System.out.println("当前双链表数据-------------");
doubleLinkedList.list();

System.out.println("修改英雄4,当前链表情况--------------");
HeroNode2 hero=new HeroNode2(4,"小白","小白龙");
doubleLinkedList.update(hero);
doubleLinkedList.list();


//删除一个节点
doubleLinkedList.del(1);
System .out .println("删除英雄1,当前链表情况--------------");
doubleLinkedList.list();

运行结果如下:

当前双链表数据-------------
HeroNode [ no=1],name=[宋江],nickname=[及时雨]
HeroNode [ no=2],name=[卢俊义],nickname=[玉麒麟]
HeroNode [ no=3],name=[吴用],nickname=[智多星]
HeroNode [ no=4],name=[林冲],nickname=[豹子头]
修改英雄4,当前链表情况--------------

HeroNode [ no=1],name=[宋江],nickname=[及时雨]
HeroNode [ no=2],name=[卢俊义],nickname=[玉麒麟]
HeroNode [ no=3],name=[吴用],nickname=[智多星]
HeroNode [ no=4],name=[小白],nickname=[小白龙]
删除英雄1,当前链表情况--------------

HeroNode [ no=2],name=[卢俊义],nickname=[玉麒麟]
HeroNode [ no=3],name=[吴用],nickname=[智多星]
HeroNode [ no=4],name=[小白],nickname=[小白龙]

四、通过应用示例熟悉单向环形链表

单向环形链表介绍

图片.png

Josephu约瑟夫环问题

图片.png

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

示意图分析思路:

图片.png

图片.png

由此产生一个出队编号的序列。
提示:用一个不带头节点循环链表来处理Josephu问题

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

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

图片.png

图片.png

遍历环形链表
1.先让一个辅助指针(变量)curBoy,指向first节点
2.然后通过一个while循环遍历该环形链表即可curBoy.next

图片.png

丢手绢小孩代码编写如下:

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

    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;
    }
}

单向环形链表管理节点代码编写如下:

//创建管理环形的单向链表
class CircleSingleLinkedList {

    //创建一个first节点,当前没有编号
    private Boy first = null ;

    //添加小孩节点,构建成一个环形的链表
    public void createLinkedList(int nums){
        //num 做一个数据校验
        if(nums < -1){
            System.out.println("num的值不正确");
            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指向新的小孩
                boy.setNext(first);//新小孩指向第一个小孩
                curBoy = boy;//当前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 = curBoy. getNext();
        }
    }
}

先让我们完成第一步,看看有多少个小孩参加玩丢手绢的游戏?

//测试-把看看构建环形链表,和遍历是否ok
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.createLinkedList(5);//加入5个小孩玩游戏
circleSingleLinkedList.showBoy();

运行结果如下:

小孩的编号1 
小孩的编号2 
小孩的编号3 
小孩的编号4 
小孩的编号5 

那么如何按照报数出序列呢?

图片.png

图片.png

图片.png

代码实现根据用户输入计算出圈顺序

//根据用户的输入,计算出小孩出圈的顺序
/**
 * @param startNo 表示从第几个小孩开始数数
 * @param countNum 表示数几下
 * @param nums 表示最初有多少小孩在圈中
 */
public void countBoy(int startNo, int countNum, int nums) {
    //先对数据进行校验
    //校验环形链表是否有数据,空链表无法出
    //校验从第几个开始小于1,从-1个小孩开始无法出
    //校验从第几个开始大于环形链表里的值,从5个小孩里第6个无法出
    if (first == null || startNo < 1 || startNo > nums) {
        System.out.println("参数输入有误,请重新输入");
        return;
    }
    //创建要给辅助指针,帮助完成小孩出圈
    Boy helper = first;
    //需求创建-个辅助指针(变量) helper ,事先应该指向环形链表的最后这个节点
    while (true) {
        //说明he1per指向最后小孩节点
        if (helper.getNext() == first) {
            break;
        }
        //后移不断找到最后节点
        helper = helper.getNext();
    }
    //报数前先让first和helper 移动 startNo - 1 次
    //若从二个小孩开始此时first 指向第一个小孩
    //则需要移动 2 - 1 次 将first指向要报数的小孩
    for (int j = 0; j < startNo - 1; j++) {
        first = first.getNext();
        helper = helper.getNext();
    }
    //此时first 指向报数的小孩
    while(true) {
        //说明圈中只留下最后一个小孩
        if(helper == first) {
            break;
        }
        //比如说每个三 就出圈 此时first 指向的是报数的小孩 则代表=1
        //只需要 3 - 1 则找到要出圈的小孩 然后出圈直到只有一个节点
        //让 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.getNext();
        helper.setNext(first);
    }
    System.out.printf("最后留在圈中的小孩 %d \n",first.getNo());
}

来看看刚刚添加五个小孩的出圈顺序吧

//测试一把小孩出圈是否正确
//一共有五个小孩
//出圈规则:从第一个小孩开始数二下出圈
circleSingleLinkedList.countBoy(1, 2, 5);


运行结果如下:
小孩2出圈 
小孩4出圈 
小孩1出圈 
小孩5出圈 
最后留在圈中的小孩 3 

图片.png


28640
116 声望25 粉丝

心有多大,舞台就有多大