JAVA-Linked-List-1

链表

一、单向链表

1、基本介绍

1. 链表是有序的列表,但是他再内存中的存储如下 ----------->
2. 链表是以节点的方式来存储数据的,是链式存储
3. 每个节点包含data域(存数据) next域(存下一个节点的地址)
4. 链表中各个节点不一定是连续存储。(可以充分利用碎片内存)
5. 链表分为 带头节点链表 和 不带头节点链表,根据实际需求来确定

2、带头节点的链表

1. head节点不存放任何具体数据,作用就是表示单链表的头。

2. 最后一个节点的next域 存放的是null,表示这是尾部

3. 从单项链表中添加一个节点的思路

• 直接在添加时添加到链表的尾部
• 首先判断链表是否为空  head.next == null 
• 创建一个temp辅助变量,temp = head
• 遍历时 如果当 temp.next = null, 说明跳出循环,进行添加  temp.next = 新节点
• 每循环一次,temp向后移动一位
• 根据id的大小,按顺序添加到链表中
• 首先判断是否链表是否为空
• 需要一个temp辅助变量 temp = head
• 首先需要搞清楚,如果 现在链表中有 1 3  两个节点,你需要将2 添加进去
• 在循环中 如果  temp.next.id > 新节点.id   则表示可以将节点插入到 temp 和 temp.next 之间,break;退出循环 
• 使用一个辅助变量 flag ,表示id是重复,就给赋值上true
• 在循环中,如果temp.next.id == 新节点.id 则表示id重复,不能进行插入 falg = true
• 每循环一次,temp向后移动一位
• 判断如果为false 就给插入 //2.next = 1.next;  1.next = 2;  这样 2 就被插入到 1 和 3的中间了
• 如果是true 给出提示信息,表示id重复

4. 根据id更新一个节点的数据,不更新 id 和 next ,其余都可以更新 的思路

• 先遍历 (遍历前先判断链表是否为空)
• 在循环中 判断 if(temp.next.id == 新节点.id) 找到了就退出循环
• 没循环一次 temp就往后移动一位
• 可以使用一个flag变量 来根据变量判断 是否找到了 如果为true则更新 为false表示没找到

5. 删除节点思路

• 我们先找到需要删除的这个节点的 前面一个节点 temp
• 然后 temp.next = temp.next.next  这样就删除了中间的那个节点  
• 举例:1   2        3 个节点,1的下一个节点是       1.next.next  则 1.next      = 3 ,这样2 就没了
• 被删除的节点没有了其他引用的指向,会被垃圾回收器机制回收 

二、单项链表代码实现

package com.yixuexi.linkedList;
import java.util.Stack;
/**
 * 2020/10/10   20:10
 */
//模拟带头节点的单向链表
public class HeadLinkedListDemo {
    public static void main(String[] args) {
        //进行测试
        //首先创建几个人物数据
        HeroNode n1 = new HeroNode(1,"松江","及时雨");
        HeroNode n2 = new HeroNode(2,"李逵","黑旋风");
        HeroNode n3 = new HeroNode(3,"吴用","智多星");
        HeroNode n4 = new HeroNode(4,"卢俊义","玉麒麟");
        HeroNode n5 = new HeroNode(5,"武大郎","烧饼哥");
        //创建一个链表
        HeadLinkedList headLinkedList = new HeadLinkedList();
        //往链表中添加数据
        headLinkedList.add(n1);
        headLinkedList.add(n2);
        headLinkedList.add(n5);
        //查看链表中的数据
        headLinkedList.list();
        //往链表中添加数据
        headLinkedList.addByOrder(n3);
        headLinkedList.addByOrder(n4);
        headLinkedList.addByOrder(n4);
        //分割线
        System.out.println("------------------------------------------------->");
        //再次查看链表中的数据
        headLinkedList.list();
        //分割线
        System.out.println("------------------------------------------------->");
        //更新链表中的数据
        HeroNode newn5 = new HeroNode(5,"武松","打虎哥");
        HeroNode newn6 = new HeroNode(6,"武松","打虎哥");
        headLinkedList.update(newn5);
        headLinkedList.update(newn6);
        //再次查看链表中的数据
        headLinkedList.list();
        //分割线
        System.out.println("------------------------------------------------->");
        //删除链表中的数据
        headLinkedList.delete(2);
        //查看删除之后的数据
        headLinkedList.list();
        //查看链表中有效数据的个数
        System.out.println(HeadLinkedList.getLength(headLinkedList.getHead()));
        //查看链表中倒数第2个节点
        HeroNode last = HeadLinkedList.getLast(headLinkedList.getHead(), 2);
        System.out.println(last);
        //分割线
        System.out.println("------------------------------------------------->");
        //反转链表
        //HeadLinkedList.reverse(headLinkedList.getHead());
        //headLinkedList.list();
        //分割线
        System.out.println("------------------------------------------------->");
        // 反向打印链表
        HeadLinkedList.reversePrint(headLinkedList.getHead());
    }
}
//创建一个单向链表类,用来管理单项链表
class HeadLinkedList {
    //在这个单项链表类中有一个 头节点,这个头节点不能动
    private HeroNode head = new HeroNode(0, "", "");
    public HeroNode getHead() {
        return head;
    }
    /**
     * 此方法 添加数据的时候没有顺序,是按照先添加的在前面,后添加的在后面的顺序
     *
     * @param heroNode
     */
    public void add(HeroNode heroNode) {
        //由于每次添加节点都是往链表的最后去添加,所以先要遍历一遍链表,找出最后那个元素
        //这里遍历我们使用一个 temp临时变量 辅助我们遍历
        //这里这个temp就是头节点,使用循环,如果temp.next == null,则表示它是最后一个节点
        //如果 temp.next != null 则表示它不是最后一个节点,则往后移一位, temp = temp.next;
        HeroNode temp = head;
        while (true) {
            if (temp.next == null) {
                //说明此时 temp是最后一个节点 直接跳出循环
                break;
            }
            //能执行到这里,则表示不是最后一个节点,往后移动一位,继续循环
            temp = temp.next;
        }
        //执行到这里,说明此时的temp一定为最后一个节点,直接将节点添加到 temp.next 即可
        temp.next = heroNode;
    }
    /**
     * 按照id的顺序进行添加
     * 添加的时候按照数据 的id 进行排序,id 为 1 的在空节点后面 1 ->> 2 ->> 3 ->> 4
     */
    public void addByOrder(HeroNode heroNode) {
        //假设:现在链表中的数据为  1  3  4
        //现在 需要将2 插入到 1 和 3 的中间去
        //因为头节点不能动,所以需要一个temp辅助变量 来帮助我们遍历
        HeroNode temp = head;
        //需要使用一个flag辅助我们 判断是否插入的数据id已经存在了
        Boolean flag = false;
        //首先判断,如果这个链表为空,则直接添加在链表的后面即可
        if (temp.next == null) {
            temp.next = heroNode;
            return;
        }
        //循环 找到插入数据的位置  假设 将 2 插入到 1 3 中间
        // 则 2.next = 1.next;  1.next = 2;  这样 2 就被插入到 1 和 3的中间了
        while (true) {
            //这样则表示需要插入的位置找到了,就在 temp 和 temp.next 的中间
            if (temp.next.id > heroNode.id) {
                break;
            } else if (temp.next.id == heroNode.id) { //表示插入的数据id在链表中已经存在了
                flag = true;
                break;
            }
            //如果上面都没有实现则 temp向后移动一位
            temp = temp.next;
        }
        // 将节点插入到指定位置 temp 和 temp.next之间
        if (flag) {
            System.out.println("你要添加节点的id已经存在 id = " + heroNode.id + " 无法添加");
        } else {
            // 则 2.next = 1.next;  1.next = 2;  这样 2 就被插入到 1 和 3的中间了
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }
    /**
     * 根据id 对节点的姓名和绰号进行更新
     *
     * @param newHeroNode
     */
    public void update(HeroNode newHeroNode) {
        //需要使用一个辅助变量 temp
        HeroNode temp = head;
        //首先判断是否为空,如果为空直接退出
        if (temp.next == null) {
            System.out.println("链表为空,请加入节点后在进行更新!");
            return;
        }
        //使用循环找出id为 newHeroNode.id 的节点
        Boolean flag = false;
        while (true) {
            if (temp == null) {
                break;
            }
            //如果找到了 那么直接跳出循环,因为当前temp 就是需要修改的节点
            if (temp.id == newHeroNode.id) {
                flag = true;
                break;
            }
            //没有找到则向后移动一位
            temp = temp.next;
        }
        //判断 如果flag为true 则表示当前temp是需要修改的
        if (flag) {
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        } else {
            System.out.println("没有找到对应id的节点,请输入正确的数据");
        }
    }
    //删除节点
    public void delete(int id) {
        //辅助变量
        HeroNode temp = head;
        // 标记
        boolean flag = false;
        //思路:找到要删除的节点的前面那个 节点  temp
        //然后 temp.next = temp.next.next  这样中间那个节点的指向就没了,垃圾回收器就回收了
        while (true) {
            if (temp.next == null) {
                return;
            }
            if (temp.next.id == id) {
                flag = true;
                break;
            }
            // 如果都没有 则向后移动一位
            temp = temp.next;
        }
        if (flag) {
            temp.next = temp.next.next;
        } else {
            System.out.println("没有找到对应的节点id : " + id);
        }
    }
    //用来展示链表中的数据
    public void list() {
        //使用一个temp辅助变量来帮助我们进行循环遍历
        HeroNode temp = head;
        //首先判断一下这个链表是否为空链表
        //如果头节点的next域 为空则表示没有下一个节点,为空链表
        if (temp.next == null) {
            return;
        }
        // 因为上面做了判断了,所以直接输出就行
        //然后把temp往后移动一位,如果temp.next为空,则表示到头了
        while (true) {
            System.out.println(temp.next);
            temp = temp.next;
            if (temp.next == null) {
                break;
            }
        }
    }
    //用来查看链表中有效数据的个数
    //传过来一个头节点,即可得到他的长度
    public static int getLength(HeroNode head){
        //判断如果这个链表是空链表的话 直接return 0 就行
        if (head.next == null){
            return 0;
        }
        //因为不算上 头节点,所以是 head.next
        HeroNode temp = head.next;
        int length = 0;
        while ( temp != null){
            length ++;
            temp = temp.next;
        }
        return length;
    }
    //用来查看链表中倒数第K个节点
    public static HeroNode getLast(HeroNode head, int index){
        //1.首先根据头节点 得到链表的长度
        //判断如果这个链表是空链表的话 直接return 0 就行
        if (head.next == null){
            return null;
        }
        //因为不算上 头节点,所以是 head.next
        HeroNode temp = head.next;
        int length = 0;
        while ( temp != null){
            length ++;
            temp = temp.next;
        }
        //2. 做一个数据的校验,判断index是否合理
        if(index <= 0 || index > length){
            return null;
        }
        //3. 进行遍历,倒数第几个 : 长度 - 倒数第几个 = 要找的那个
        HeroNode temp2 = head;  //辅助变量,帮我们遍历
        for(int i = 0; i <= length - index; i++){
            if (temp2.next == null){
                break;
            }
            temp2 = temp2.next;
        }
        return temp2;
    }
    //传入一个头节点,对其进行链表反转
    public static void reverse(HeroNode head){
        //如果链表为空 或者 链表中就一个元素,那么直接返回这个链表就行,不用反转
        if (head.next == null || head.next.next == null){
            return;
        }
        //定义一个辅助变量,帮助我们遍历链表
        HeroNode cur = head.next;
        //此变量永远指向 cur的下一个节点
        HeroNode next = null;
        //新的头节点
        HeroNode reverseHead = new HeroNode(0,"","");
        while (cur != null){
            //保存cur的下一个节点,防止丢失
            next = cur.next;
            //将cur的下一个节点指向新的链表的最前端 右边的那条链子
            cur.next = reverseHead.next;
            //头插法,将每一个遍历的节点插入到链表的最前端  左边的那条链子
            reverseHead.next = cur;
            //向后移动一位
            cur = next;
        }
        //将head.next 执行reverserHead.next 实现单链表的反转
        head.next = reverseHead.next;
    }
    //从头到尾打印单链表,使用栈数据结构
    public static void reversePrint(HeroNode head){
        //先判断 链表是否为空。
        if(head.next == null){
            return;
        }
        Stack<HeroNode> stack = new Stack<>();
        HeroNode temp = head.next;
        while (temp != null){
            //把节点压入栈中【先进后出】
            stack.push(temp);
            //向后移动一位
            temp = temp.next;
        }
        //将栈中的节点进行打印,因为是先进后出,所以就完成了倒序打印
        while (stack.size() > 0){
            System.out.println(stack.pop());
        }
    }
}
//创建一个节点类
class HeroNode{
    public int id;
    public String name;
    public String nickname;
    public HeroNode next;  //表示单向链表中的next区域,存放下一个节点
    //构造器,
    public HeroNode(int id, String name, String nickname) {
        this.id = id;
        this.name = name;
        this.nickname = nickname;
    }
    //重写toString的时候 next不需要重写
    @Override
    public String toString() {
        return "HeroNode{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", nickname='" + nickname + '\'' +
                '}';
    }
}

三、单项链表面试题

1、求单项链表中有效节点的个数

(如果是带头节点的,不统计头节点)

//用来查看链表中有效数据的个数
//传过来一个头节点,即可得到他的长度
public static int getLength(HeroNode head){
    //判断如果这个链表是空链表的话 直接return 0 就行
    if (head.next == null){
        return 0;
    }
    //因为不算上 头节点,所以是 head.next
    HeroNode temp = head.next;
    int length = 0;
    while ( temp != null){
        length ++;
        temp = temp.next;
    }
    return length;
}
调用:
System.out.println(HeadLinkedList.getLength(headLinkedList.getHead()));

2、查找单链表中的单数第K个节点【新浪面试题】

2.1 思路:

1. 编写一个方法,接收 head节点,同时接收一个index
2. index 是表示倒数第几个节点
3. 先把链表从头到尾遍历一遍,得到长度
4. 得到长度之后,我们从链表的第一个开始遍历(size - index)个,就可以得到了

2.2 代码实现

//用来查看链表中倒数第K个节点
public static HeroNode getLast(HeroNode head, int index){
    //1.首先根据头节点 得到链表的长度
    //判断如果这个链表是空链表的话 直接return 0 就行
    if (head.next == null){
        return null;
    }
    //因为不算上 头节点,所以是 head.next
    HeroNode temp = head.next;
    int length = 0;
    while ( temp != null){
        length ++;
        temp = temp.next;
    }
    //2. 做一个数据的校验,判断index是否合理
    if(index <= 0 || index > length){
        return null;
    }
    //3. 进行遍历,倒数第几个 : 长度 - 倒数第几个 = 要找的那个
    HeroNode temp2 = head;  //辅助变量,帮我们遍历
    for(int i = 0; i <= length - index; i++){
        if (temp2.next == null){
            break;
        }
        temp2 = temp2.next;
    }
    return temp2;
}

3、单链表的反转【腾讯面试题】

3.1 思路:

1. 先定义一个新的节点,HeroNode reverseHead = new HeroNode();
2. 从头到尾遍历节点,将每一个遍历的节点放到头的后面 【头插法】
• 这样就是 1 2 3         先插入1, 然后 在往1之前插入2 在往2 之前插入3
3. 原来链表head.next =       reverseHead.next;

3.2 代码实现【头插法】

//传入一个头节点,对其进行链表反转
public static void reverse(HeroNode head){
    //如果链表为空 或者 链表中就一个元素,那么直接返回这个链表就行,不用反转
    if (head.next == null || head.next.next == null){
        return;
    }
    //定义一个辅助变量,帮助我们遍历链表
    HeroNode cur = head.next;
    //此变量永远指向 cur的下一个节点
    HeroNode next = null;
    //新的头节点
    HeroNode reverseHead = new HeroNode(0,"","");
    while (cur != null){
        //保存cur的下一个节点,防止丢失
        next = cur.next;
        //将cur的下一个节点指向新的链表的最前端 右边的那条链子
        cur.next = reverseHead.next;
        //头插法,将每一个遍历的节点插入到链表的最前端  左边的那条链子
        reverseHead.next = cur;
        //向后移动一位
        cur = next;
    }
    //将head.next 执行reverserHead.next 实现单链表的反转
    head.next = reverseHead.next;
}

4、从尾到头打印单链表【百度面试题,要求:1.反向遍历,2。Stack栈】

4.1 思路

• 思路1:将单链表反转过来,然后再打印【问题:会破坏原来链表的结构】
• 思路2:利用栈数据结构,将各个节点压入栈中,利用栈的先进后出的特点 就实现了逆序打印的效果

4.2 代码实现:

//从头到尾打印单链表,使用栈数据结构
public static void reversePrint(HeroNode head){
    //先判断 链表是否为空。
    if(head.next == null){
        return;
    }
    Stack<HeroNode> stack = new Stack<>();
    HeroNode temp = head.next;
    while (temp != null){
        //把节点压入栈中【先进后出】
        stack.push(temp);
        //向后移动一位
        temp = temp.next;
    }
    //将栈中的节点进行打印,因为是先进后出,所以就完成了倒序打印
    while (stack.size() > 0){
        System.out.println(stack.pop());
    }
}

5、合并两个有序的单链表,合并之后的链表依然有序

四、双向链表

1、双向链表和单向链表有什么不一样的地方

• 除了有data域,next域,还有一个pre域 用来保存前一个节点。
• head头部也有pre域,可以向头部的前面加节点

2、分析双向链表的遍历,增加,删除,修改。

2.1遍历思路:

• 遍历跟单链表一样,只是可以向前,也可以向后查找

2.2 添加思路:(添加到链表的最后)

• 先找到双向链表的最后那个节点
• 先让 temp.next = 新节点  
• 然后 新节点.pre = temp

2.3 修改思路:

• 和原来的单项链表一致,先找到节点再修改

2.4 删除思路:

• 因为是双向链表,所以不需要找到 删除的前一个节点,直接找到要删除的节点就行
• 然后 要删除的节点.pre.next = 要删除的节点.next;
• 再然后 要删除的节点.next.pre = 要删除的节点.pre
• 需要注意的是如果是删除最后一个节点的话,就不需要最后一句了,所以加一个if判断

五、双向链表代码实现

package com.yixuexi.linkedList;
/**
* 2020/10/11   20:57
*/
public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        //进行测试
        //首先创建几个人物数据
        DoubleHeroNode n1 = new DoubleHeroNode(1,"松江","及时雨");
        DoubleHeroNode n2 = new DoubleHeroNode(2,"李逵","黑旋风");
        DoubleHeroNode n3 = new DoubleHeroNode(3,"吴用","智多星");
        DoubleHeroNode n4 = new DoubleHeroNode(4,"卢俊义","玉麒麟");
        DoubleHeroNode n5 = new DoubleHeroNode(5,"武大郎","烧饼哥");
        //创建链表
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        doubleLinkedList.add(n1);
        doubleLinkedList.add(n2);
        doubleLinkedList.add(n3);
        doubleLinkedList.add(n4);
        doubleLinkedList.add(n5);
        //查看链表中的数据
        doubleLinkedList.list();
        //删除表中的数据
        doubleLinkedList.delete(1);
        doubleLinkedList.delete(5);
        System.out.println("-------------------------------->");
        //查看链表中的数据
        doubleLinkedList.list();
        DoubleHeroNode n6 = new DoubleHeroNode(5,"潘金莲","烧饼哥老婆");
        doubleLinkedList.update(n6);
        System.out.println("-------------------------------->");
        //查看链表中的数据
        doubleLinkedList.list();
    }
}
//
class DoubleLinkedList{
    //在这个单项链表类中有一个 头节点,这个头节点不能动
    private DoubleHeroNode head = new DoubleHeroNode(0, "", "");
    public DoubleHeroNode getHead() {
        return head;
    }
    //遍历的方法
    //用来展示链表中的数据
    public void list() {
        //使用一个temp辅助变量来帮助我们进行循环遍历
        DoubleHeroNode temp = head;
        //首先判断一下这个链表是否为空链表
        //如果头节点的next域 为空则表示没有下一个节点,为空链表
        if (temp.next == null) {
            return;
        }
        // 因为上面做了判断了,所以直接输出就行
        //然后把temp往后移动一位,如果temp.next为空,则表示到头了
        while (true) {
            System.out.println(temp.next);
            temp = temp.next;
            if (temp.next == null) {
                break;
            }
        }
    }
    //用来添加节点的方法【从链表的尾部添加】
    public void add(DoubleHeroNode doubleHeroNode) {
        //由于每次添加节点都是往链表的最后去添加,所以先要遍历一遍链表,找出最后那个元素
        //这里遍历我们使用一个 temp临时变量 辅助我们遍历
        //这里这个temp就是头节点,使用循环,如果temp.next == null,则表示它是最后一个节点
        //如果 temp.next != null 则表示它不是最后一个节点,则往后移一位, temp = temp.next;
        DoubleHeroNode temp = head;
        while (true) {
            if (temp.next == null) {
                //说明此时 temp是最后一个节点 直接跳出循环
                break;
            }
            //能执行到这里,则表示不是最后一个节点,往后移动一位,继续循环
            temp = temp.next;
        }
        //执行到这里,说明此时的temp一定为最后一个节点,直接将节点添加到 temp.next 然后把添加的元素.pre = temp
        temp.next = doubleHeroNode;
        doubleHeroNode.pre = temp;
    }
    //删除元素的方法
    public void delete(int index){
        DoubleHeroNode temp = head;
        boolean flag = false;
        while (true){
            if (temp == null){
                return;
            }
            if (temp.id == index){
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag){
            temp.pre.next = temp.next;
            //这里代码有问题,如果是删除的最后一个节点的话会报出空指针异常,所以需要加上if判断
            if (temp.next != null){
                temp.next.pre = temp.pre;
            }
        }else{
            System.out.println("没有找到你要删除的节点");
        }
    }
    public void update(DoubleHeroNode doubleHeroNode){
        //需要使用一个辅助变量 temp
        DoubleHeroNode temp = head;
        //首先判断是否为空,如果为空直接退出
        if (temp.next == null) {
            System.out.println("链表为空,请加入节点后在进行更新!");
            return;
        }
        //使用循环找出id为 newHeroNode.id 的节点
        Boolean flag = false;
        while (true) {
            if (temp == null) {
                break;
            }
            //如果找到了 那么直接跳出循环,因为当前temp 就是需要修改的节点
            if (temp.id == doubleHeroNode.id) {
                flag = true;
                break;
            }
            //没有找到则向后移动一位
            temp = temp.next;
        }
        //判断 如果flag为true 则表示当前temp是需要修改的
        if (flag) {
            temp.name = doubleHeroNode.name;
            temp.nickname = doubleHeroNode.nickname;
        } else {
            System.out.println("没有找到对应id的节点,请输入正确的数据");
        }
    }
}
//创建一个有着双向节点的类
class DoubleHeroNode {
    public int id;
    public String name;
    public String nickname;
    public DoubleHeroNode next;  //表示双向链表中的next区域,存放下一个节点
    public DoubleHeroNode pre;   //表示双向链表中的pre区域,存放上一个节点
    //构造器,
    public DoubleHeroNode(int id, String name, String nickname) {
        this.id = id;
        this.name = name;
        this.nickname = nickname;
    }
    @Override
    public String toString() {
        return "DoubleHeroNode{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", nickname='" + nickname + '\'' +
                '}';
    }
}

六、单向环形链表

1、单向环形链表应用场景

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

假设:
n = 5 表示有几个人
k = 1 表示从第1个人 开始报数
m = 2 表示数两下【自身是1下,第二下那个人出列】然后第三个人数
最后出队列的顺序是 2->4->1->5->3
解决方案:使用单向环形链表,没有头节点,最后一个节点指向第一个节点

2、创建环形链表的思路

• 先创建第一个节点,让first节点指向该节点,这样就形成了环状
• 后面当我们没创建一个新的节点,就把该节点加入到以后的环形链表中即可。

3、遍历环形链表的思路

• 让一个辅助变量 curboy,指向first节点,
• 然后通过一个while循环进行遍历
• 再循环中 如果curboy.next = first 则可以break了,因为 前遍历的下一个节点是 第一个节点 则表示遍历完了

4、约瑟夫问题思路

约瑟夫问题代码实现

package com.yixuexi.linkedList;
/**
* 2020/10/12   19:20
*/
/*解决约瑟夫问题,模拟环形链表*/
public class Josepfu {
    public static void main(String[] args) {
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        circleSingleLinkedList.showBoy(); 
        circleSingleLinkedList.countBoy(1,2,5);
    }
}
class CircleSingleLinkedList{
    //创建一个 first 指针,这个指针永远指向 第一个节点。
    public Boy first = null;
    //创建环形链表
    public void addBoy(int nums){
        //首先判断传进来的值合不合法,如果说构建一个小于1 个环形链表 那完全没必要
        if (nums < 1){
            System.out.println("构建链表节点数量太少,没必要!");
            return;
        }
        //创建一个辅助指针,帮助我们创建环形链表
        Boy curBoy = null;
        //进行构建
        for (int i = 1; i <= nums; i ++){
            //每循环一次,创建一个小孩
            Boy boy = new Boy(i);
            //如果是创建第一个小孩,那么这时第一个节点,让first指向这个第一个节点
            if ( i == 1 ){
                first = boy;
                //再将first的下一个节点指向boy,形成一个环
                first.setNext(boy);
                //让辅助节点指向first
                curBoy = first;
            }else{
                //倒数第二个节点的next 指向倒数第一个节点
                curBoy.setNext(boy);
                //将最后一个节点指向第一个节点,形成一个环
                boy.setNext(first);
                //向后一位移动
                curBoy = boy;
            }
        }
    }
    //遍历当前的单项环形链表
    public void showBoy(){
        if (first == null){
            System.out.println("空链表,没有必要做遍历");
            return;
        }
        Boy curBoy = first;
        while (true){
            System.out.println("小孩的编号为:" + curBoy.getNo());
            //因为是环形链表,所以说 如果当前遍历的下一个节点是 第一个节点 则表示遍历完了
            if (curBoy.getNext() == first){
                break;
            }
            //向后移动一位
            curBoy = curBoy.getNext();
        }
    }
    // 根据用户的输入,计算出小孩出圈的顺序
    /**
     *
     * @param starNo 表示从第几个小孩开始数
     * @param countNum 表示数几下
     * @param nums 表示最初有几个小孩在圈中
     */
    public void countBoy(int starNo,int countNum, int nums){
        //先对数据进行一波校验
        if (first == null || starNo < 1 || starNo > nums){
            System.out.println("输入的参数有误,请重新输入");
            return;
        }
        //创建辅助节点,帮助完成小孩出圈
        Boy helper = first;
        //将helper 移动到这个链表的最后一个节点
        while (true){
            if (helper.getNext() == first){
                break;
            }
            //向后移动一位
            helper = helper.getNext();
        }
        //在小孩报数前,先让first 和 helper移动 k-1 次
        for (int j = 0; j < starNo - 1; j ++){
            first = first.getNext();
            helper = helper.getNext();
        }
        //当小孩报数时,让first和helper指针同时移动 m -1次,然后出圈
        //通过循环,让这个链表中 到最后只剩下一个节点
        while (true){
            if (helper == first){
                break;
            }
            //让first和helper同时的移动 countNum - 1
            for (int i = 0; i < countNum -1 ; i++){
                first = first.getNext();
                helper = helper.getNext();
            }
            //此时的first 指向的就是要出圈小孩的节点,
            System.out.println(first.getNo());
            //将小孩出圈
            first = first.getNext();
            helper.setNext(first);
        }
        System.out.println("最后留在圈中的小孩的编号为 " + first.getNo());
    }
}
//小孩节点
class Boy{
    private int no; //小孩id
    private Boy next;
    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;
    }
}
Contents
  1. 1. 链表
    1. 1.1. 一、单向链表
      1. 1.1.1. 1、基本介绍
      2. 1.1.2. 2、带头节点的链表
        1. 1.1.2.1. 1. head节点不存放任何具体数据,作用就是表示单链表的头。
        2. 1.1.2.2. 2. 最后一个节点的next域 存放的是null,表示这是尾部
        3. 1.1.2.3. 3. 从单项链表中添加一个节点的思路
      3. 1.1.3. 4. 根据id更新一个节点的数据,不更新 id 和 next ,其余都可以更新 的思路
      4. 1.1.4. 5. 删除节点思路
    2. 1.2. 二、单项链表代码实现
    3. 1.3. 三、单项链表面试题
      1. 1.3.1. 1、求单项链表中有效节点的个数
      2. 1.3.2. 2、查找单链表中的单数第K个节点【新浪面试题】
        1. 1.3.2.1. 2.1 思路:
        2. 1.3.2.2. 2.2 代码实现
      3. 1.3.3. 3、单链表的反转【腾讯面试题】
        1. 1.3.3.1. 3.1 思路:
      4. 1.3.4. 4、从尾到头打印单链表【百度面试题,要求:1.反向遍历,2。Stack栈】
        1. 1.3.4.1. 4.1 思路
        2. 1.3.4.2. 4.2 代码实现:
      5. 1.3.5. 5、合并两个有序的单链表,合并之后的链表依然有序
    4. 1.4. 四、双向链表
      1. 1.4.1. 1、双向链表和单向链表有什么不一样的地方
      2. 1.4.2. 2、分析双向链表的遍历,增加,删除,修改。
        1. 1.4.2.1. 2.1遍历思路:
        2. 1.4.2.2. 2.2 添加思路:(添加到链表的最后)
        3. 1.4.2.3. 2.3 修改思路:
        4. 1.4.2.4. 2.4 删除思路:
    5. 1.5. 五、双向链表代码实现
    6. 1.6. 六、单向环形链表
      1. 1.6.1. 1、单向环形链表应用场景
      2. 1.6.2. 2、创建环形链表的思路
      3. 1.6.3. 3、遍历环形链表的思路
      4. 1.6.4. 4、约瑟夫问题思路
      5. 1.6.5. 约瑟夫问题代码实现
|