• 看不懂的数据结构-链表深度刨析


    欧克!欧克!小刘今天带大家来学习一下链表 ,你要是学不会,你来捶我

    img

    img

    1、链表(Linked List)介绍

    1.1、内存结构

    • 内存上来看:链表存储空间 不连续(不像数组)

    1.2、逻辑结构

    • 逻辑上来看:链表属于 线性结构

    1.3、链表特点

    • 链表是以节点的方式来存储,是 链式存储
    • data 域存放数据,next 域 指向下一个节点
    • 链表分 带头节点的链表和 没有头节点的链表, 根据实际的需求来确定

    2、链表应用场景

    2.1、水浒英雄榜

    • 使用带 head 头的 单向链表实现【水浒英雄排行榜管理】

    2.2、链表节点定义

    • no :英雄编号
    • name :英雄名字
    • nickName :英雄昵称
    • next :指向下一个 HeroNode 节点
    
    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 + "]";
    	}
    
    }
    

    2.3、链表定义

    • DummyHead : 头结点不存放数据,仅仅作为当前链表的入口
    • head 字段的值不能改变,一旦改变,就 丢失了整个链表的入口,我们也就无法通过 head 找到链表了
    
    class SingleLinkedList {
    
    	private HeroNode head = new HeroNode(0, "", "");
    
    	public HeroNode getHead() {
    		return head;
    	}
    
    

    2.4、遍历链表

    2.4.1、代码思路

    • 定义辅助变量 temp ,相当于一个指针,指向 当前节点
    • 何时遍历完成? temp == null 表明当前节点为 null ,即表示已到链表末尾
    • 如何遍历? temp = temp.next ,每次输出当前节点信息之后,temp 指针后移

    2.4.2、代码实现

    • 遍历链表
    
    public void list() {
    
    	if (head.next == null) {
    		System.out.println("链表为空");
    		return;
    	}
    
    	HeroNode temp = head.next;
    	while (true) {
    
    		if (temp == null) {
    			break;
    		}
    
    		System.out.println(temp);
    
    		temp = temp.next;
    	}
    }
    

    2.5、尾部插入

    2.5.1、代码思路

    • 定义辅助变量 temp ,相当于一个指针,指向 当前节点
    • 如何在链表末尾插入节点?
      • 首先需要遍历链表,找到链表最后一个节点,当 temp.next == null时,temp 节点指向链表最后一个节点
      • 然后在 temp 节点之后插入节点即可: *temp.next = heroNode

    2.5.2、代码实现

    • 在链表尾部插入节点
    
    public void add(HeroNode heroNode) {
    
        HeroNode temp = head;
    
        while (true) {
    
            if (temp.next == null) {
                break;
            }
    
            temp = temp.next;
        }
    
        temp.next = heroNode;
    }
    
    • 测试代码
    	public static void main(String[] args) {
    
    		HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
    		HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
    		HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
    		HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
    
    		SingleLinkedList singleLinkedList = new SingleLinkedList();
    
    		singleLinkedList.add(hero1);
    		singleLinkedList.add(hero2);
    		singleLinkedList.add(hero3);
    		singleLinkedList.add(hero4);
    
    		singleLinkedList.list();
    	}
    
    • 程序运行结果
    HeroNode [no=1, name=宋江, nickName=及时雨]
    HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
    HeroNode [no=3, name=吴用, nickName=智多星]
    HeroNode [no=4, name=林冲, nickName=豹子头]
    

    2.6、按顺序插入

    2.6.1、代码思路

    • 定义辅助变量 temp ,相当于一个指针,指向 当前节点
    • 应该如何执行插入?(待插入节点为 heroNode)
      • 首先需要遍历链表,找到链表中编号值比 heroNode.no 大的节点,暂且叫它 biggerNode ,然后把 heroNode 插入到 biggerNode 之前即可
      • 怎么找 biggerNode ?当 temp.next.no > heroNode.no 时,这时 temp.next 节点就是 biggerNode 节点。
      • 为什么是 temp.next 节点?只有找到 temp 节点和 temp.next(biggerNode )节点,才能在 temp 节点和 temp.next 节点之间插入 heroNode 节点
      • 怎么插入?
        • heroNode .next = temp.next;
        • temp.next = heroNode;

    2.6.2、代码实现

    • 按照英雄排名的顺序进行插入
    
    public void addByOrder(HeroNode heroNode) {
    
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            if (temp.next == null) {
                break;
            }
            if (temp.next.no > heroNode.no) {
                break;
            } else if (temp.next.no == heroNode.no) {
    
                flag = true;
                break;
            }
            temp = temp.next;
        }
    
        if (flag) {
            System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入
    ", heroNode.no);
        } else {
    
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }
    
    • 测试代码
    public static void main(String[] args) {
    
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
    
        SingleLinkedList singleLinkedList = new SingleLinkedList();
    
        singleLinkedList.addByOrder(hero1);
        singleLinkedList.addByOrder(hero4);
        singleLinkedList.addByOrder(hero2);
        singleLinkedList.addByOrder(hero3);
    
        singleLinkedList.list();
    }
    
    • 程序运行结果
    HeroNode [no=1, name=宋江, nickName=及时雨]
    HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
    HeroNode [no=3, name=吴用, nickName=智多星]
    HeroNode [no=4, name=林冲, nickName=豹子头]
    

    2.7、修改节点信息

    2.7.1、代码思路

    • 定义辅助变量 temp ,相当于一个指针,指向 当前节点
    • 如何找到指定节点? *temp.no = newHeroNode.no

    2.7.2、代码实现

    • 修改指定节点信息
    
    public void update(HeroNode newHeroNode) {
    
        if (head.next == null) {
            System.out.println("链表为空~");
            return;
        }
    
        HeroNode temp = head.next;
        boolean flag = false;
        while (true) {
            if (temp == null) {
                break;
            }
            if (temp.no == newHeroNode.no) {
    
                flag = true;
                break;
            }
            temp = temp.next;
        }
    
        if (flag) {
            temp.name = newHeroNode.name;
            temp.nickName = newHeroNode.nickName;
        } else {
            System.out.printf("没有找到 编号 %d 的节点,不能修改
    ", newHeroNode.no);
        }
    }
    
    • 测试代码
    public static void main(String[] args) {
    
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
    
        SingleLinkedList singleLinkedList = new SingleLinkedList();
    
        singleLinkedList.addByOrder(hero1);
        singleLinkedList.addByOrder(hero4);
        singleLinkedList.addByOrder(hero2);
        singleLinkedList.addByOrder(hero3);
    
        HeroNode newHeroNode = new HeroNode(2, "小卢", "玉麒麟~~");
        singleLinkedList.update(newHeroNode);
    
        singleLinkedList.list();
    }
    
    • 程序运行结果
    HeroNode [no=1, name=宋江, nickName=及时雨]
    HeroNode [no=2, name=小卢, nickName=玉麒麟~~]
    HeroNode [no=3, name=吴用, nickName=智多星]
    HeroNode [no=4, name=林冲, nickName=豹子头]
    

    2.8、删除节点

    2.8.1、代码思路

    • 定义辅助变量 temp ,相当于一个指针,指向 当前节点
    • 如何找到待删除的节点?遍历链表,当 temp.next == no 时,temp.next 节点就是待删除的节点
    • 如何删除? temp = temp.next.next 即可删除 temp.next 节点,该节点没有引用指向它,会被垃圾回收机制回收

    2.8.2、代码实现

    • 删除指定节点
    
    public void del(int no) {
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            if (temp.next == null) {
                break;
            }
            if (temp.next.no == no) {
    
                flag = true;
                break;
            }
            temp = temp.next;
        }
    
        if (flag) {
    
            temp.next = temp.next.next;
        } else {
            System.out.printf("要删除的 %d 节点不存在
    ", no);
        }
    }
    
    • 测试代码
    public static void main(String[] args) {
    
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
    
        SingleLinkedList singleLinkedList = new SingleLinkedList();
    
        singleLinkedList.add(hero1);
        singleLinkedList.add(hero2);
        singleLinkedList.add(hero3);
        singleLinkedList.add(hero4);
    
        singleLinkedList.del(1);
        singleLinkedList.del(4);
    
        singleLinkedList.list();
    }
    
    • 程序运行结果
    HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
    HeroNode [no=3, name=吴用, nickName=智多星]
    

    2.9、总结

    • 遍历链表,执行操作时,判断条件有时候是 temp ,有时候是 temp.next ,Why?
      • 对于插入、删除节点来说,需要知道 当前待操作的节点(heroNode)前一个节点的地址(指针),如果直接定位至当前待操作的节点 heroNode ,那没得玩。。。因为不知道heroNode 前一个节点的地址,无法进行插入、删除操作,所以 while 循环中的条件使用 temp.next 进行判断
      • 对于更新、遍历操作来说,我需要的仅仅就只是当前节点的信息,所以 while 循环中的条件使用 temp进行判断
    • 头结点与首节点
      • 参考资料:https://blog.csdn.net/WYpersist/article/details/80288056
      • 头结点是为了操作的统一与方便而设立的,放在第一个元素结点之前,其数据域一般无意义(当然有些情况下也可存放链表的长度、用做监视哨等等)。
      • 首元结点也就是第一个元素的结点,它是头结点后边的第一个结点。

    3、单链表面试题

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

    3.1.1、代码思路

    • 求单链表中有效节点的个数:遍历即可

    3.1.2、代码实现

    • 求单链表中有效节点的个数
    
    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;
    }
    
    • 测试代码
    public static void main(String[] args) {
    
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
    
        SingleLinkedList singleLinkedList = new SingleLinkedList();
    
        singleLinkedList.add(hero1);
        singleLinkedList.add(hero2);
        singleLinkedList.add(hero3);
        singleLinkedList.add(hero4);
    
        singleLinkedList.list();
    
        System.out.println("有效的节点个数=" + getLength(singleLinkedList.getHead()));
    }
    
    • 程序运行结果
    HeroNode [no=1, name=宋江, nickName=及时雨]
    HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
    HeroNode [no=3, name=吴用, nickName=智多星]
    HeroNode [no=4, name=林冲, nickName=豹子头]
    有效的节点个数=4
    

    3.2、查找单链表中的倒数第 k 个结点

    3.2.1、代码思路

    • 查找单链表中的倒数第k个结点 【新浪面试题】
      • 首先,获取整个链表中元素的个数 size
      • 在使用 for 循环定位至倒数第 index(形参) 个节点,返回即可
      • for 循环的条件应如何确定?for (int i = 0; i < x; i++) 中 x 的值应是多少?我们需要定位至倒数第 index 个节点,在 for 循环之前,我们已经定位置首节点,还需再走 (size - index ) 步,定位至倒数第 index 个节点
      • 举例说明:链表中一共有 4 个元素,想要定位至倒数第 2 个节点,那么需要在首节点之后走两步,到达倒数第 2 个节点

    3.2.2、代码实现

    • 查找单链表中的倒数第k个结点
    
    public static HeroNode findLastIndexNode(HeroNode head, int index) {
    
        if (head.next == null) {
            return null;
        }
    
        int size = getLength(head);
    
        if (index  0 || index > size) {
            return null;
        }
    
        HeroNode cur = head.next;
        for (int i = 0; i < size - index; i++) {
            cur = cur.next;
        }
        return cur;
    }
    
    • 测试代码
    public static void main(String[] args) {
    
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
    
        SingleLinkedList singleLinkedList = new SingleLinkedList();
    
        singleLinkedList.add(hero1);
        singleLinkedList.add(hero2);
        singleLinkedList.add(hero3);
        singleLinkedList.add(hero4);
    
        singleLinkedList.list();
    
        HeroNode res = findLastIndexNode(singleLinkedList.getHead(), 2);
        System.out.println("res=" + res);
    
    }
    
    • 程序运行结果
    HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]
    HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
    HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
    HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
    res=HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
    

    3.3、单链表的反转

    3.3.1、代码思路

    • 单链表的反转【腾讯面试题,有点难度】
      • 定义一个新的头结点 reverseHead ,一点一点将链表反转后,再串起来
      • 怎么个串法?
        • 在原链表中每读取一个节点(cur),先保存其下一个节点的地址(next),然后将 cur 节点放在新链表的最前面
        • 然后执行遍历: cur = next ,即指针后移
        • 遍历完成后,新链表即是反转后的链表
      • 如何将 cur 节点插入在新链表的最前面
        • cur.next = reverseHead.next;
        • reverseHead.next = cur;
      • while 循环终止条件? cur == null :已遍历至链表尾部
    • 单链表的翻转可以参考我的这篇博文:https://blog.csdn.net/oneby1314/article/details/107577923

    3.3.2、代码实现

    • 单链表的反转
    
    public static void reversetList(HeroNode head) {
    
        if (head.next == null || head.next.next == null) {
            return;
        }
    
        HeroNode cur = head.next;
        HeroNode next = null;
        HeroNode reverseHead = new HeroNode(0, "", "");
    
        while (cur != null) {
            next = cur.next;
            cur.next = reverseHead.next;
            reverseHead.next = cur;
            cur = next;
        }
    
        head.next = reverseHead.next;
    }
    
    • 测试代码
    public static void main(String[] args) {
    
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
    
        SingleLinkedList singleLinkedList = new SingleLinkedList();
    
        singleLinkedList.add(hero1);
        singleLinkedList.add(hero2);
        singleLinkedList.add(hero3);
        singleLinkedList.add(hero4);
    
        System.out.println("原来链表的情况~~");
        singleLinkedList.list();
    
        System.out.println("反转单链表~~");
        reversetList(singleLinkedList.getHead());
        singleLinkedList.list();
    }
    
    • 程序运行结果
    &#x539F;&#x6765;&#x94FE;&#x8868;&#x7684;&#x60C5;&#x51B5;~~
    HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]
    HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
    HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
    HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
    &#x53CD;&#x8F6C;&#x5355;&#x94FE;&#x8868;~~
    HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
    HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
    HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
    HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]
    

    3.4、单链表的反转(我的代码)

    3.4.1、代码思路

    • 单链表的反转【腾讯面试题,有点难度】
      • 原链表为 cur 指向 next ,反转链表不就是把 next 指向 cur 吗?
      • 由于 next 指向 cur 时,next 将 丢失其下一节点的地址,所以需要先将 nnext 保存起来
      • next ==null 时链表已经反转完毕,最后将头结点指向 cur 节点即可

    3.4.2、代码实现

    • 单链表的反转
    
    public static void myReversetList(HeroNode head) {
    
        if (head.next == null || head.next.next == null) {
            return;
        }
    
        HeroNode cur = head.next;
    
        HeroNode next = cur.next;
    
        cur.next = null;
    
        while (next != null) {
    
            HeroNode nnext = next.next;
    
            next.next = cur;
    
            cur = next;
            next = nnext;
        }
    
        head.next = cur;
    }
    
    • 测试代码
    public static void main(String[] args) {
    
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
    
        SingleLinkedList singleLinkedList = new SingleLinkedList();
    
        singleLinkedList.add(hero1);
        singleLinkedList.add(hero2);
        singleLinkedList.add(hero3);
        singleLinkedList.add(hero4);
    
        System.out.println("原来链表的情况~~");
        singleLinkedList.list();
    
        System.out.println("反转单链表~~");
        reversetList(singleLinkedList.getHead());
        singleLinkedList.list();
    }
    
    • 程序运行结果
    &#x539F;&#x6765;&#x94FE;&#x8868;&#x7684;&#x60C5;&#x51B5;~~
    HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]
    HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
    HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
    HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
    &#x53CD;&#x8F6C;&#x5355;&#x94FE;&#x8868;~~
    HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
    HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
    HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
    HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]
    

    3.5、从尾到头打印单链表

    3.5.1、栈的基本使用

    • 测试代码
    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());
        }
    }
    
    • 程序运行结果
    smith
    tom
    jack
    

    3.5.2、代码思路

    • 从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
      • 方式一:先将单链表进行反转操作,然后再遍历输出,问题: 破坏原链表结构,不可取
      • 方式二:遍历链表,去除节点压入栈中,利用栈 先进后出的特点,实现逆序打印

    3.5.3、代码实现

    • 从尾到头打印单链表
    
    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;
        }
    
        while (stack.size() > 0) {
            System.out.println(stack.pop());
        }
    }
    
    • 测试代码
    public static void main(String[] args) {
    
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
    
        SingleLinkedList singleLinkedList = new SingleLinkedList();
    
        singleLinkedList.add(hero1);
        singleLinkedList.add(hero2);
        singleLinkedList.add(hero3);
        singleLinkedList.add(hero4);
    
        System.out.println("原来链表的情况~~");
        singleLinkedList.list();
    
        System.out.println("测试逆序打印单链表, 没有改变链表的结构~~");
        reversePrint(singleLinkedList.getHead());
    }
    
    • 程序运行结果
    &#x539F;&#x6765;&#x94FE;&#x8868;&#x7684;&#x60C5;&#x51B5;~~
    HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]
    HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
    HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
    HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
    &#x6D4B;&#x8BD5;&#x9006;&#x5E8F;&#x6253;&#x5370;&#x5355;&#x94FE;&#x8868;, &#x6CA1;&#x6709;&#x6539;&#x53D8;&#x94FE;&#x8868;&#x7684;&#x7ED3;&#x6784;~~
    HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
    HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
    HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
    HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]
    

    3.6、合并两个有序的单链表

    3.6.1、代码思路

    • 合并两个有序的单链表,合并之后的链表依然有序【课后练习】

    3.6.2、代码实现

    3.7、单向链表所有代码

    public class SingleLinkedListDemo {
    
    	public static void main(String[] args) {
    
    		HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
    		HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
    		HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
    		HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
    
    		SingleLinkedList singleLinkedList = new SingleLinkedList();
    
    		singleLinkedList.add(hero1);
    		singleLinkedList.add(hero4);
    		singleLinkedList.add(hero2);
    		singleLinkedList.add(hero3);
    
    		System.out.println("原来链表的情况~~");
    		singleLinkedList.list();
    
    		System.out.println("反转单链表~~");
    		reversetList(singleLinkedList.getHead());
    		singleLinkedList.list();
    
    		System.out.println("测试逆序打印单链表, 没有改变链表的结构~~");
    		reversePrint(singleLinkedList.getHead());
    
    		singleLinkedList.addByOrder(hero1);
    		singleLinkedList.addByOrder(hero4);
    		singleLinkedList.addByOrder(hero2);
    		singleLinkedList.addByOrder(hero3);
    
    		singleLinkedList.list();
    
    		HeroNode newHeroNode = new HeroNode(2, "小卢", "玉麒麟~~");
    		singleLinkedList.update(newHeroNode);
    
    		System.out.println("修改后的链表情况~~");
    		singleLinkedList.list();
    
    		singleLinkedList.del(1);
    		singleLinkedList.del(4);
    		System.out.println("删除后的链表情况~~");
    		singleLinkedList.list();
    
    		System.out.println("有效的节点个数=" + getLength(singleLinkedList.getHead()));
    
    		HeroNode res = findLastIndexNode(singleLinkedList.getHead(), 3);
    		System.out.println("res=" + res);
    
    	}
    
    	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;
    		}
    
    		while (stack.size() > 0) {
    			System.out.println(stack.pop());
    		}
    	}
    
    	public static void reversetList(HeroNode head) {
    
    		if (head.next == null || head.next.next == null) {
    			return;
    		}
    
    		HeroNode cur = head.next;
    		HeroNode next = null;
    		HeroNode reverseHead = new HeroNode(0, "", "");
    
    		while (cur != null) {
    			next = cur.next;
    			cur.next = reverseHead.next;
    			reverseHead.next = cur;
    			cur = next;
    		}
    
    		head.next = reverseHead.next;
    	}
    
    	public static void myReversetList(HeroNode head) {
    
    		if (head.next == null || head.next.next == null) {
    			return;
    		}
    
    		HeroNode cur = head.next;
    
    		HeroNode next = cur.next;
    
    		cur.next = null;
    
    		while (next != null) {
    
    			HeroNode nnext = next.next;
    
    			next.next = cur;
    
    			cur = next;
    			next = nnext;
    		}
    
    		head.next = cur;
    	}
    
    	public static HeroNode findLastIndexNode(HeroNode head, int index) {
    
    		if (head.next == null) {
    			return null;
    		}
    
    		int size = getLength(head);
    
    		if (index  0 || index > size) {
    			return null;
    		}
    
    		HeroNode cur = head.next;
    		for (int i = 0; i < size - index; i++) {
    			cur = cur.next;
    		}
    		return cur;
    
    	}
    
    	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;
    	}
    
    }
    
    class SingleLinkedList {
    
    	private HeroNode head = new HeroNode(0, "", "");
    
    	public HeroNode getHead() {
    		return head;
    	}
    
    	public void add(HeroNode heroNode) {
    
    		HeroNode temp = head;
    
    		while (true) {
    
    			if (temp.next == null) {
    				break;
    			}
    
    			temp = temp.next;
    		}
    
    		temp.next = heroNode;
    	}
    
    	public void addByOrder(HeroNode heroNode) {
    
    		HeroNode temp = head;
    		boolean flag = false;
    		while (true) {
    			if (temp.next == null) {
    				break;
    			}
    			if (temp.next.no > heroNode.no) {
    				break;
    			} else if (temp.next.no == heroNode.no) {
    
    				flag = true;
    				break;
    			}
    			temp = temp.next;
    		}
    
    		if (flag) {
    			System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入
    ", heroNode.no);
    		} else {
    
    			heroNode.next = temp.next;
    			temp.next = heroNode;
    		}
    	}
    
    	public void update(HeroNode newHeroNode) {
    
    		if (head.next == null) {
    			System.out.println("链表为空~");
    			return;
    		}
    
    		HeroNode temp = head.next;
    		boolean flag = false;
    		while (true) {
    			if (temp == null) {
    				break;
    			}
    			if (temp.no == newHeroNode.no) {
    
    				flag = true;
    				break;
    			}
    			temp = temp.next;
    		}
    
    		if (flag) {
    			temp.name = newHeroNode.name;
    			temp.nickName = newHeroNode.nickName;
    		} else {
    			System.out.printf("没有找到 编号 %d 的节点,不能修改
    ", newHeroNode.no);
    		}
    	}
    
    	public void del(int no) {
    		HeroNode temp = head;
    		boolean flag = false;
    		while (true) {
    			if (temp.next == null) {
    				break;
    			}
    			if (temp.next.no == no) {
    
    				flag = true;
    				break;
    			}
    			temp = temp.next;
    		}
    
    		if (flag) {
    
    			temp.next = temp.next.next;
    		} else {
    			System.out.printf("要删除的 %d 节点不存在
    ", no);
    		}
    	}
    
    	public void list() {
    
    		if (head.next == null) {
    			System.out.println("链表为空");
    			return;
    		}
    
    		HeroNode temp = head.next;
    		while (true) {
    
    			if (temp == null) {
    				break;
    			}
    
    			System.out.println(temp);
    
    			temp = temp.next;
    		}
    	}
    }
    
    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 + "]";
    	}
    
    }
    

    4、双向链表

    4.1、与单向链表的比较

    • 单向链表, 查找的方向只能是一个方向, 而双向链表可以向前或者向后查找
    • 单向链表不能自我删除, 需要靠辅助节点 , 而双向链表, 则可以 自我删除, 所以前面我们单链表删除时节点, 总是找到 temp ,temp 是待删除节点的 前一个节点(认真体会)

    4.2、链表节点定义

    • 在单向链表节点的基础上,增加 pre ,用于指向前一个节点
    
    class HeroNode {
    	public int no;
    	public String name;
    	public String nickname;
    	public HeroNode next;
    	public HeroNode pre;
    
    	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 + "]";
    	}
    
    }
    
    

    4.3、链表定义

    • 定义整个链表的头结点,作为链表的入口
    
    class DoubleLinkedList {
    
    	private HeroNode head = new HeroNode(0, "", "");
    
    	public HeroNode getHead() {
    		return head;
    	}
    
    

    4.4、链表遍历

    4.4.1、代码思路

    • 定义辅助变量 temp ,相当于一个指针,指向 当前节点 ,用于遍历链表
    • 何时停止 while 循环? temp == null :已经遍历至链表尾部

    4.4.2、代码实现

    
    public void list() {
    
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
    
        HeroNode temp = head.next;
        while (true) {
    
            if (temp == null) {
                break;
            }
    
            System.out.println(temp);
    
            temp = temp.next;
        }
    }
    

    4.5、尾部插入

    4.5.1、代码思路

    • 定义辅助变量 temp ,相当于一个指针,指向 当前节点
    • 何时停止 while 循环? temp.next == null :temp 节点已经是链表最后一个节点,在 temp 节点之后插入 heroNode 节点即可
    • 如何插入?
      • temp.next 指向新的尾节点 heroNode : temp.next = heroNode;
      • heroNode .pre 指向旧的尾节点 temp : *heroNode.pre = temp;

    4.5.2、代码实现

    • 在链表尾部插入节点
    
    public void add(HeroNode heroNode) {
    
        HeroNode temp = head;
    
        while (true) {
    
            if (temp.next == null) {
                break;
            }
    
            temp = temp.next;
        }
    
        temp.next = heroNode;
        heroNode.pre = temp;
    }
    

    4.6、按顺序插入

    4.6.1、代码思路

    • 定义辅助变量 temp ,相当于一个指针,指向 当前节点
    • 我们将 heroNode 节点插入到 temp 节点之后还是 temp 节点之前?
      • 如果插入到 temp 节点之后:
        • 判断条件: temp.next.no > heroNode.no ,即 temp 的下一个节点的值比 heroNode 节点的值大,所以需要将 heroNode 插入到 temp 节点之后
      • while 循环终止条件:
        • temp.next == null :temp 节点已经是链表的尾节点
        • temp.next.no > heroNode.no :heroNode 节点的值介于 temp 节点的值和 temp 下一个节点的值之间
        • temp.next.no == heroNode.no :heroNode 节点的值等于 temp 下一个节点的值,不能进行插入
      • 如果插入到 temp 节点之前:
        • 判断条件: temp.no > heroNode.no ,即 temp 节点的值比 heroNode 节点的值大,所以需要将 heroNode 插入到 temp 节点之前
        • 存在的问题:如果需要在链表尾部插入 heroNode 节点,即需要在 null 节点之前插入 heroNode 节点, 定位至 null 节点将丢失其前一个节点的信息(除非使用一个变量保存起来),所以跳出循环的判断条件为:temp.next == null
        • 所以我们选取:【插入到 temp 节点之后】方案

    4.6.2、代码实现

    • 代码
    
    public void addByOrder(HeroNode heroNode) {
    
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            if (temp.next == null) {
                break;
            }
            if (temp.next.no > heroNode.no) {
                break;
            } else if (temp.next.no == heroNode.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
    
        if (flag) {
            System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入
    ", heroNode.no);
        } else {
    
            heroNode.next = temp.next;
            if(temp.next != null) {
                temp.next.pre = heroNode;
            }
    
            temp.next = heroNode;
            heroNode.pre = temp;
        }
    }
    

    4.7、修改节点信息

    4.7.1、代码思路

    • 定义辅助变量 temp ,相当于一个指针,指向 当前节点
    • 如何找到指定节点? *temp.no == no

    4.7.2、代码实现

    • 修改指定节点的信息
    
    public void update(HeroNode newHeroNode) {
    
        if (head.next == null) {
            System.out.println("链表为空~");
            return;
        }
    
        HeroNode temp = head.next;
        boolean flag = false;
        while (true) {
            if (temp == null) {
                break;
            }
            if (temp.no == newHeroNode.no) {
    
                flag = true;
                break;
            }
            temp = temp.next;
        }
    
        if (flag) {
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        } else {
            System.out.printf("没有找到 编号 %d 的节点,不能修改
    ", newHeroNode.no);
        }
    }
    

    4.8、删除节点

    4.8.1、代码思路

    • 定义辅助变量 temp ,相当于一个指针,指向 当前节点
    • while 循环的终止条件?由于 temp 节点就是待删除节点,所以终止条件是: temp == null
    • 为何双向链表,可以实现 自我删除?定位至待删除的节点 temp ,由于temp 节点有其前一个节点和后一个节点的信息,所以可实现自我删除
    • 如何删除?
      • temp 的前一个节点的 next 域指向 temp 的后一个节点: temp.pre.next = temp.next;
      • temp 的后一个节点的 pre 域指向 temp 的前一个节点: temp.next.pre = temp.pre;
        • 有个地方需要注意,如果 temp 已经是链表尾节点,temp 已经没有下一个节点
        • 这时只需要将 temp 的前一个节点的 next 指向 null 即可
        • 所以 temp.next.pre = temp.pre; 执行的前提条件是 *temp.next != null

    4.8.2、代码实现

    • 删除指定节点
    
    public void del(int no) {
    
        if (head.next == null) {
            System.out.println("链表为空,无法删除");
            return;
        }
    
        HeroNode temp = head.next;
        boolean flag = false;
        while (true) {
            if (temp == null) {
                break;
            }
            if (temp.no == no) {
    
                flag = true;
                break;
            }
            temp = temp.next;
        }
    
        if (flag) {
    
            temp.pre.next = temp.next;
    
            if (temp.next != null) {
                temp.next.pre = temp.pre;
            }
        } else {
            System.out.printf("要删除的 %d 节点不存在
    ", no);
        }
    }
    

    4.9、双向链表测试

    4.9.1、测试代码

    public static void main(String[] args) {
    
        System.out.println("双向链表的测试");
    
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(5, "林冲", "豹子头");
    
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        doubleLinkedList.add(hero1);
        doubleLinkedList.add(hero2);
        doubleLinkedList.add(hero3);
        doubleLinkedList.add(hero4);
    
        doubleLinkedList.list();
    
        doubleLinkedList.addByOrder(new HeroNode(4, "Heygo", "Heygogo"));
        doubleLinkedList.addByOrder(new HeroNode(6, "Oneby", "Onebyone"));
        System.out.println("按顺序插入后的情况");
        doubleLinkedList.list();
    
        HeroNode newHeroNode = new HeroNode(5, "公孙胜", "入云龙");
        doubleLinkedList.update(newHeroNode);
        System.out.println("修改后的链表情况");
        doubleLinkedList.list();
    
        doubleLinkedList.del(3);
        System.out.println("删除后的链表情况~~");
        doubleLinkedList.list();
    }
    

    4.9.2、程序运行结果

    &#x53CC;&#x5411;&#x94FE;&#x8868;&#x7684;&#x6D4B;&#x8BD5;
    HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickname=&#x53CA;&#x65F6;&#x96E8;]
    HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickname=&#x7389;&#x9E92;&#x9E9F;]
    HeroNode [no=3, name=&#x5434;&#x7528;, nickname=&#x667A;&#x591A;&#x661F;]
    HeroNode [no=5, name=&#x6797;&#x51B2;, nickname=&#x8C79;&#x5B50;&#x5934;]
    &#x6309;&#x987A;&#x5E8F;&#x63D2;&#x5165;&#x540E;&#x7684;&#x60C5;&#x51B5;
    HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickname=&#x53CA;&#x65F6;&#x96E8;]
    HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickname=&#x7389;&#x9E92;&#x9E9F;]
    HeroNode [no=3, name=&#x5434;&#x7528;, nickname=&#x667A;&#x591A;&#x661F;]
    HeroNode [no=4, name=Heygo, nickname=Heygogo]
    HeroNode [no=5, name=&#x6797;&#x51B2;, nickname=&#x8C79;&#x5B50;&#x5934;]
    HeroNode [no=6, name=Oneby, nickname=Onebyone]
    &#x4FEE;&#x6539;&#x540E;&#x7684;&#x94FE;&#x8868;&#x60C5;&#x51B5;
    HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickname=&#x53CA;&#x65F6;&#x96E8;]
    HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickname=&#x7389;&#x9E92;&#x9E9F;]
    HeroNode [no=3, name=&#x5434;&#x7528;, nickname=&#x667A;&#x591A;&#x661F;]
    HeroNode [no=4, name=Heygo, nickname=Heygogo]
    HeroNode [no=5, name=&#x516C;&#x5B59;&#x80DC;, nickname=&#x5165;&#x4E91;&#x9F99;]
    HeroNode [no=6, name=Oneby, nickname=Onebyone]
    &#x5220;&#x9664;&#x540E;&#x7684;&#x94FE;&#x8868;&#x60C5;&#x51B5;~~
    HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickname=&#x53CA;&#x65F6;&#x96E8;]
    HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickname=&#x7389;&#x9E92;&#x9E9F;]
    HeroNode [no=4, name=Heygo, nickname=Heygogo]
    HeroNode [no=5, name=&#x516C;&#x5B59;&#x80DC;, nickname=&#x5165;&#x4E91;&#x9F99;]
    HeroNode [no=6, name=Oneby, nickname=Onebyone]
    

    4.10、双向链表所有代码

    public class DoubleLinkedListDemo {
    
    	public static void main(String[] args) {
    
    		System.out.println("双向链表的测试");
    
    		HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
    		HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
    		HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
    		HeroNode hero4 = new HeroNode(5, "林冲", "豹子头");
    
    		DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
    		doubleLinkedList.add(hero1);
    		doubleLinkedList.add(hero2);
    		doubleLinkedList.add(hero3);
    		doubleLinkedList.add(hero4);
    
    		doubleLinkedList.list();
    
    		doubleLinkedList.addByOrder(new HeroNode(0, "Kobe", "BlackMamba"));
    		doubleLinkedList.addByOrder(new HeroNode(4, "Heygo", "Heygogo"));
    		doubleLinkedList.addByOrder(new HeroNode(6, "Oneby", "Onebyone"));
    		System.out.println("按顺序插入后的情况");
    		doubleLinkedList.list();
    
    		HeroNode newHeroNode = new HeroNode(5, "公孙胜", "入云龙");
    		doubleLinkedList.update(newHeroNode);
    		System.out.println("修改后的链表情况");
    		doubleLinkedList.list();
    
    		doubleLinkedList.del(3);
    		System.out.println("删除后的链表情况~~");
    		doubleLinkedList.list();
    	}
    
    }
    
    class DoubleLinkedList {
    
    	private HeroNode head = new HeroNode(0, "", "");
    
    	public HeroNode getHead() {
    		return head;
    	}
    
    	public void list() {
    
    		if (head.next == null) {
    			System.out.println("链表为空");
    			return;
    		}
    
    		HeroNode temp = head.next;
    		while (true) {
    
    			if (temp == null) {
    				break;
    			}
    
    			System.out.println(temp);
    
    			temp = temp.next;
    		}
    	}
    
    	public void add(HeroNode heroNode) {
    
    		HeroNode temp = head;
    
    		while (true) {
    
    			if (temp.next == null) {
    				break;
    			}
    
    			temp = temp.next;
    		}
    
    		temp.next = heroNode;
    		heroNode.pre = temp;
    	}
    
    	public void addByOrder(HeroNode heroNode) {
    
    		HeroNode temp = head;
    		boolean flag = false;
    		while (true) {
    			if (temp.next == null) {
    				break;
    			}
    			if (temp.next.no > heroNode.no) {
    				break;
    			} else if (temp.next.no == heroNode.no) {
    				flag = true;
    				break;
    			}
    			temp = temp.next;
    		}
    
    		if (flag) {
    			System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入
    ", heroNode.no);
    		} else {
    
    		 	heroNode.next = temp.next;
    		 	if(temp.next != null) {
    		 		temp.next.pre = heroNode;
    		 	}
    
    		 	temp.next = heroNode;
    		 	heroNode.pre = temp;
    		}
    	}
    
    	public void update(HeroNode newHeroNode) {
    
    		if (head.next == null) {
    			System.out.println("链表为空~");
    			return;
    		}
    
    		HeroNode temp = head.next;
    		boolean flag = false;
    		while (true) {
    			if (temp == null) {
    				break;
    			}
    			if (temp.no == newHeroNode.no) {
    
    				flag = true;
    				break;
    			}
    			temp = temp.next;
    		}
    
    		if (flag) {
    			temp.name = newHeroNode.name;
    			temp.nickname = newHeroNode.nickname;
    		} else {
    			System.out.printf("没有找到 编号 %d 的节点,不能修改
    ", newHeroNode.no);
    		}
    	}
    
    	public void del(int no) {
    
    		if (head.next == null) {
    			System.out.println("链表为空,无法删除");
    			return;
    		}
    
    		HeroNode temp = head.next;
    		boolean flag = false;
    		while (true) {
    			if (temp == null) {
    				break;
    			}
    			if (temp.no == no) {
    
    				flag = true;
    				break;
    			}
    			temp = temp.next;
    		}
    
    		if (flag) {
    
    			temp.pre.next = temp.next;
    
    			if (temp.next != null) {
    				temp.next.pre = temp.pre;
    			}
    		} else {
    			System.out.printf("要删除的 %d 节点不存在
    ", no);
    		}
    	}
    
    }
    
    class HeroNode {
    	public int no;
    	public String name;
    	public String nickname;
    	public HeroNode next;
    	public HeroNode pre;
    
    	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 + "]";
    	}
    
    }
    

    4.11、总结

    • 辅助变量 temp ,相当于一个指针,指向 当前节点
    • 如果定位至当前节点会丢失前一个节点的信息,那么我们只能定位至待操作节点的前一个节点:使用 temp.next 进行条件判断

    5、单向环形链表

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

    • Josephu 问题为: 设编号为 1, 2, ... n 的 n 个人围坐一圈, 约定编号为 k(1

    5.2、单向环形链表图解

    5.3、Josephu 问题

    • 用一个不带头结点的循环链表来处理 Josephu 问题: 先构成一个有 n 个结点的 单循环链表, 然后由 k 结点起从 1 开始计数, 计到 m 时, 对应结点从链表中删除, 然后再从被删除结点的下一个结点又从 1 开始计数, 直到最后一个结点从链表中删除算法结束。

    5.4、环形链表的构建与遍历

    5.4.1、Boy 节点的定义

    • Boy 节点就是个普普通通的单向链表节点
    
    class Boy {
    	private int no;
    	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;
    	}
    }
    

    5.4.2、单向循环链表的定义

    • first 节点为单向循环链表的 首节点,是真实 存放数据的节点,不是头结点
    
    class CircleSingleLinkedList {
    
    	private Boy first = null;
    
    

    5.4.3、构建单向循环链表

    1、代码思路
    • 长度为 1 的情况:
      • 新创建的 boy 节点即是首节点: first = boy;
      • 自封闭(自己构成环形链表): first.setNext(first);
      • 此时 first 节点既是首节点,也是尾节点,辅助指针也指向 first : curBoy = first;
    • 长度不为 1 的情况:
      • 将 boy 节点添加至环形链表的最后: curBoy.setNext(boy); ,curBoy 节点永远是环形链表的尾节点
      • 构成环形链表(最): boy.setNext(first);
      • 辅助指针后移,指向环形链表的尾节点: *curBoy = boy;

    2、代码实现
    
    public void addBoy(int nums) {
    
        if (nums < 1) {
            System.out.println("nums的值不正确");
            return;
        }
        Boy curBoy = null;
    
        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;
            }
        }
    }
    

    5.4.4、遍历单向循环链表

    1、代码思路
    • 定义辅助变量 curBoy ,相当于一个指针,指向 当前节点
    • 何时退出 while 循环?当 curBoy 已经指向环形链表的尾节点: *curBoy.getNext() == first
    2、代码实现
    
    public void showBoy() {
    
        if (first == null) {
            System.out.println("没有任何小孩~~");
            return;
        }
    
        Boy curBoy = first;
        while (true) {
            System.out.printf("小孩的编号 %d 
    ", curBoy.getNo());
            if (curBoy.getNext() == first) {
                break;
            }
            curBoy = curBoy.getNext();
        }
    }
    

    5.5、解决 Josephu 问题

    5.5.1、代码思路

    • 辅助变量 helper :helper 永都指向 环形链表的尾节点,环形链表的尾节点永远都指向首节点,可得出: helper.getNext() == first
    • 如何将 helper 定位至环形链表的尾节点?
      • 初始化时,让 helper = first ,此时 helper 指向环形链表的首节点
      • while 循环终止条件? helper.getNext() == first :此时 helper 已经移动至环形链表的尾节点
    • 如何定位至第 startNo 个节点?如果想要定位至第 2 个节点,那么则需要让 first 和 helper 都移动 1 步,所以让 first 和 helper 都移动 (startNo - 1)步即可
    • 如何数 nums 下?让 first 和 helper 都移动 (nums - 1)步即可
    • 如何实现出圈?
      • 我们需要将 first 指向的节点出圈,first 前一个节点的地址在 helper 中存着(环形链表)
      • 先让 first 后移一步: first = first.getNext;
      • 出圈: helper.setNext(first); ,原来的 first 节点由于没有任何引用,便会被垃圾回收机制回收
    • while 循环终止条件?圈中只剩一人: *helper == first

    5.5.2、代码实现

    
    public void countBoy(int startNo, int countNum, int nums) {
    
    	if (first == null || startNo < 1 || startNo > nums) {
    		System.out.println("参数输入有误, 请重新输入");
    		return;
    	}
    
    	Boy helper = first;
    
    	while (true) {
    		if (helper.getNext() == first) {
    			break;
    		}
    		helper = helper.getNext();
    	}
    
    	for (int j = 0; j < startNo - 1; j++) {
    		first = first.getNext();
    		helper = helper.getNext();
    	}
    
    	while (true) {
    		if (helper == first) {
    			break;
    		}
    
    		for (int j = 0; j < countNum - 1; j++) {
    			first = first.getNext();
    			helper = helper.getNext();
    		}
    
    		System.out.printf("小孩%d出圈
    ", first.getNo());
    
    		first = first.getNext();
    		helper.setNext(first);
    
    	}
    	System.out.printf("最后留在圈中的小孩编号%d 
    ", first.getNo());
    
    }
    

    5.6、Josephu 问题测试

    5.6.1、测试代码

    public static void main(String[] args) {
    
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        circleSingleLinkedList.showBoy();
    
        circleSingleLinkedList.countBoy(1, 2, 3);
    }
    

    5.6.2、程序运行结果

    &#x5C0F;&#x5B69;&#x7684;&#x7F16;&#x53F7; 1
    &#x5C0F;&#x5B69;&#x7684;&#x7F16;&#x53F7; 2
    &#x5C0F;&#x5B69;&#x7684;&#x7F16;&#x53F7; 3
    &#x5C0F;&#x5B69;&#x7684;&#x7F16;&#x53F7; 4
    &#x5C0F;&#x5B69;&#x7684;&#x7F16;&#x53F7; 5
    &#x5C0F;&#x5B69;2&#x51FA;&#x5708;
    &#x5C0F;&#x5B69;4&#x51FA;&#x5708;
    &#x5C0F;&#x5B69;1&#x51FA;&#x5708;
    &#x5C0F;&#x5B69;5&#x51FA;&#x5708;
    &#x6700;&#x540E;&#x7559;&#x5728;&#x5708;&#x4E2D;&#x7684;&#x5C0F;&#x5B69;&#x7F16;&#x53F7;3
    

    5.7、Josephu 问题所有代码

    public class Josepfu {
    
    	public static void main(String[] args) {
    
    		CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
    		circleSingleLinkedList.addBoy(5);
    		circleSingleLinkedList.showBoy();
    
    		circleSingleLinkedList.countBoy(1, 2, 3);
    	}
    
    }
    
    class CircleSingleLinkedList {
    
    	private Boy first = null;
    
    	public void addBoy(int nums) {
    
    		if (nums < 1) {
    			System.out.println("nums的值不正确");
    			return;
    		}
    		Boy curBoy = null;
    
    		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 showBoy() {
    
    		if (first == null) {
    			System.out.println("没有任何小孩~~");
    			return;
    		}
    
    		Boy curBoy = first;
    		while (true) {
    			System.out.printf("小孩的编号 %d 
    ", curBoy.getNo());
    			if (curBoy.getNext() == first) {
    				break;
    			}
    			curBoy = curBoy.getNext();
    		}
    	}
    
    	public void countBoy(int startNo, int countNum, int nums) {
    
    		if (first == null || startNo < 1 || startNo > nums) {
    			System.out.println("参数输入有误, 请重新输入");
    			return;
    		}
    
    		Boy helper = first;
    
    		while (true) {
    			if (helper.getNext() == first) {
    				break;
    			}
    			helper = helper.getNext();
    		}
    
    		for (int j = 0; j < startNo - 1; j++) {
    			first = first.getNext();
    			helper = helper.getNext();
    		}
    
    		while (true) {
    			if (helper == first) {
    				break;
    			}
    
    			for (int j = 0; j < countNum - 1; j++) {
    				first = first.getNext();
    				helper = helper.getNext();
    			}
    
    			System.out.printf("小孩%d出圈
    ", first.getNo());
    
    			first = first.getNext();
    			helper.setNext(first);
    
    		}
    		System.out.printf("最后留在圈中的小孩编号%d 
    ", first.getNo());
    
    	}
    }
    
    class Boy {
    	private int no;
    	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;
    	}
    
    }
    

    5.8、总结

    • 操作单向链表:对于插入、删除操作,只能定位至待操作节点的前一个节点,如果定位至当前节点,那么其上一个节点的信息便会丢失
  • 相关阅读:
    Spring boot 梳理
    Spring boot 梳理
    Spring boot 梳理
    观察者模式
    设计模式原则
    Spring MVC上传文件
    Spring MVC视图解析器
    Spring MVC中Action使用总结
    Spring MVC控制器
    Java并发 两个线程交替执行和死锁
  • 原文地址:https://www.cnblogs.com/spiritmark/p/14166314.html
Copyright © 2020-2023  润新知