zoukankan      html  css  js  c++  java
  • 看不懂的数据结构-链表深度刨析

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

    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、总结

    • 操作单向链表:对于插入、删除操作,只能定位至待操作节点的前一个节点,如果定位至当前节点,那么其上一个节点的信息便会丢失
  • 相关阅读:
    Codeforces 992C(数学)
    Codeforces 990C (思维)
    Codeforces 989C (构造)
    POJ 1511 Invitation Cards(链式前向星,dij,反向建边)
    Codeforces 1335E2 Three Blocks Palindrome (hard version)(暴力)
    POJ 3273 Monthly Expense(二分)
    POJ 2566 Bound Found(尺取前缀和)
    POJ 1321 棋盘问题(dfs)
    HDU 1506 Largest Rectangle in a Histogram(单调栈)
    POJ 2823 Sliding Window(单调队列)
  • 原文地址:https://www.cnblogs.com/spiritmark/p/14166314.html
Copyright © 2011-2022 走看看