链表(Linked List) 介绍

链表是有序的列表, 但是它在内存中是存入如下
image.png
说明:

  1. 链表是以节点的方式来存储,是链式存储
  2. 每个节点包含data域:用于存放值, next域:指向下一个节点
  3. 如图: 发现链表的各个节点不一定是连续存储的
  4. 链表分带头节点的链表没有带头节点的链表, 根据实际的需求来确定
  5. 单链表(带头节点)逻辑结构示意图如下

image.png

单链表的应用实例

需求
使用带head头的单向链表实现 - 水浒英雄排行榜管理,完成对英雄人物的增删改查操作, 注: 删除和修改,查找可以考虑独立完成

顺序添加节点

需求

  1. 第一种方法在添加英雄时, 直接添加到链表的尾部

    思路分析示意图

    image.png

    代码实现

    ```java package com.dance.linkedlist;

/**

  • 单向链表(带头节点) */ public class SingleLinkedListDemo {

    public static void main(String[] args) {

    1. SingleLinkedList singleLinkedList = new SingleLinkedList();
    2. // 顺序添加
    3. singleLinkedList.addHeroNode(new HeroNode(1, "宋江", "及时雨"));
    4. singleLinkedList.addHeroNode(new HeroNode(2, "卢俊义", "玉麒麟"));
    5. singleLinkedList.addHeroNode(new HeroNode(3, "吴用", "智多星"));
    6. singleLinkedList.addHeroNode(new HeroNode(4, "林冲", "豹子头"));
    7. singleLinkedList.showLinkedList();

    }

}

/**

  • 定义链表管理节点 */ class SingleLinkedList {

    /**

    • 定义头节点, 代表第一个节点,不要动 */ private final HeroNode head = new HeroNode(0, “”, “”);

      /**

    • 添加节点到链表, 不考虑编号顺序 *
    • @param next 要添加的节点 */ public void addHeroNode(HeroNode next) { HeroNode tailNode = getTailNode(head); // 在尾部的节点上添加一个节点 tailNode.next = next; }

      public void showLinkedList() { showLinkedList(head); }

      public void showLinkedList(HeroNode head) { System.out.println(head); if (head.next != null) {

       showLinkedList(head.next);
      

      } }

      /**

    • 获取尾部节点 *
    • @param headNode 头节点
    • @return 尾部节点 */ public HeroNode getTailNode(HeroNode headNode) { return headNode.next == null ? headNode : getTailNode(headNode.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 + '\'' +
           '}';
      

      } }

      <a name="ZZqKf"></a>
      ### 测试
      ```java
      HeroNode{no=0, name='', nickName=''}
      HeroNode{no=1, name='宋江', nickName='及时雨'}
      HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
      HeroNode{no=3, name='吴用', nickName='智多星'}
      HeroNode{no=4, name='林冲', nickName='豹子头'}
      

      带排序的添加节点(插入)

      需求

  1. 第二种方式在添加英雄时, 根据排名将英雄插入到指定位置(如果有这个排名, 则添加失败, 并给出提示)

    思路分析示意图

    image.png

    代码实现

    ```java package com.dance.linkedlist;

/**

  • 单向链表(带头节点) */ public class SingleLinkedListDemo {

    public static void main(String[] args) {

     SingleLinkedList singleLinkedList = new SingleLinkedList();
    
     ......
    
     // 带排序的添加
     singleLinkedList.addHeroNodeByNo(new HeroNode(2, "卢俊义", "玉麒麟"));
     singleLinkedList.addHeroNodeByNo(new HeroNode(1, "宋江", "及时雨"));
     singleLinkedList.addHeroNodeByNo(new HeroNode(4, "林冲", "豹子头"));
     singleLinkedList.addHeroNodeByNo(new HeroNode(3, "吴用", "智多星"));
    
     singleLinkedList.showLinkedList();
    

    }

}

/**

  • 定义链表管理节点 */ class SingleLinkedList {

    ……

    /**

    • 根据编号添加, 需要考虑编号排序问题
    • 因为头节点不能动, 因此我们需要一个辅助指针来帮助我们寻找添加的位置
    • 因为是单向链表,所以我们的辅助指针需要找到位于要添加节点的上一个节点, 否则没办法插入 *
    • @param heroNode 要添加的节点 */ public void addHeroNodeByNo(HeroNode heroNode) { try {

       HeroNode addBeforeNode = getAddBeforeNode(head, heroNode);
       heroNode.next = addBeforeNode.next;
       addBeforeNode.next = heroNode;
      

      } catch (Exception e) {

       System.out.println(e.getMessage());
      

      } }

      ……

      public HeroNode getAddBeforeNode(HeroNode heroNode, HeroNode newVal) { if (heroNode.next == null || heroNode.next.no > newVal.no) {

       return heroNode;
      

      } if (heroNode.next.no == newVal.no) {

       throw new RuntimeException("插入数据失败, 编号已存在: " + heroNode.next);
      

      } return getAddBeforeNode(heroNode.next, newVal); }

}

/**

  • 节点 */ class HeroNode { …… }
<a name="mJ8uQ"></a>
### 测试
```java
HeroNode{no=0, name='', nickName=''}
HeroNode{no=1, name='宋江', nickName='及时雨'}
HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{no=3, name='吴用', nickName='智多星'}
HeroNode{no=4, name='林冲', nickName='豹子头'}

节点信息修改

需求

  1. 修改节点功能

    思路:

  • 先通过遍历找到该节点
  • temp.name = newHeroNode.name ;temp.nickName = newHeroNode.nickName

    代码实现

    ```java package com.dance.linkedlist;

/**

  • 单向链表(带头节点) */ public class SingleLinkedListDemo {

    public static void main(String[] args) {

     SingleLinkedList singleLinkedList = new SingleLinkedList();
     ......
    
     // 带排序的添加
     singleLinkedList.addHeroNodeByNo(new HeroNode(2, "卢俊义", "玉麒麟"));
     singleLinkedList.addHeroNodeByNo(new HeroNode(1, "宋江", "及时雨"));
     singleLinkedList.addHeroNodeByNo(new HeroNode(4, "林冲", "豹子头"));
     singleLinkedList.addHeroNodeByNo(new HeroNode(3, "吴用", "智多星"));
     // 修改节点信息
     singleLinkedList.updateHeroNodeByNo(new HeroNode(2, "小卢", "倒茶"));
     singleLinkedList.showLinkedList();
    

    }

}

/**

  • 定义链表管理节点 */ class SingleLinkedList {

    ……

    /**

    • 修改节点信息 *
    • @param source 源信息 */ public void updateHeroNodeByNo(HeroNode source){ try {

       HeroNode target = getHeroNodeByNo(source.no);
       // 只允许修改 名称和昵称
       target.name = source.name;
       target.nickName = source.nickName;
      

      } catch (Exception e) {

       System.out.println(e.getMessage());
      

      }

      }

      ……

      public HeroNode getHeroNodeByNo(int no) { return getHeroNodeByNo(head, no); }

      public HeroNode getHeroNodeByNo(HeroNode heroNode, int no){ if (heroNode == null){

       throw new RuntimeException("没有找到对应的节点");
      

      } if(heroNode.no == no){

       return heroNode;
      

      } return getHeroNodeByNo(heroNode.next, no); }

}

/**

  • 节点 */ class HeroNode { …… }
<a name="CL6zi"></a>
### 测试
```java
HeroNode{no=0, name='', nickName=''}
HeroNode{no=1, name='宋江', nickName='及时雨'}
HeroNode{no=2, name='小卢', nickName='倒茶'}
HeroNode{no=3, name='吴用', nickName='智多星'}
HeroNode{no=4, name='林冲', nickName='豹子头'}

移除节点

需求

  1. 删除节点

    思路分析示意图

    image.png

    代码实现

    ```java package com.dance.linkedlist;

/**

  • 单向链表(带头节点) */ public class SingleLinkedListDemo {

    public static void main(String[] args) {

     SingleLinkedList singleLinkedList = new SingleLinkedList();
     ......
     // 带排序的添加
     singleLinkedList.addHeroNodeByNo(new HeroNode(2, "卢俊义", "玉麒麟"));
     singleLinkedList.addHeroNodeByNo(new HeroNode(1, "宋江", "及时雨"));
     singleLinkedList.addHeroNodeByNo(new HeroNode(4, "林冲", "豹子头"));
     singleLinkedList.addHeroNodeByNo(new HeroNode(3, "吴用", "智多星"));
     // 修改节点信息
     singleLinkedList.updateHeroNodeByNo(new HeroNode(2, "小卢", "倒茶"));
     // 移除节点2,4
     singleLinkedList.removeHeroNodeByNo(2);
     singleLinkedList.removeHeroNodeByNo(4);
     singleLinkedList.showLinkedList();
    

    }

}

/**

  • 定义链表管理节点 */ class SingleLinkedList {

    ……

    /**

    • 根据编号移除节点
    • @param no 编号 */ public void removeHeroNodeByNo(int no){ HeroNode removeBeforeHeroNode = getRemoveBeforeHeroNode(no); removeBeforeHeroNode.next = removeBeforeHeroNode.next.next; }

      ……

      public HeroNode getRemoveBeforeHeroNode(int no){ return getRemoveBeforeHeroNode(head, no); }

      public HeroNode getRemoveBeforeHeroNode(HeroNode heroNode, int no){ if(heroNode == null){

       throw new RuntimeException("没有找到要移除的节点");
      

      } if(heroNode.next.no == no){

       return heroNode;
      

      } return getRemoveBeforeHeroNode(heroNode.next, no); }

}

/**

  • 节点 */ class HeroNode { …… }
<a name="BaSG2"></a>
### 测试
```java
HeroNode{no=0, name='', nickName=''}
HeroNode{no=1, name='宋江', nickName='及时雨'}
HeroNode{no=3, name='吴用', nickName='智多星'}

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

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

package com.dance.linkedlist;

/**
 * 单向链表(带头节点)
 */
public class SingleLinkedListDemo {

    public static void main(String[] args) {
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        ......

        // 带排序的添加
        singleLinkedList.addHeroNodeByNo(new HeroNode(2, "卢俊义", "玉麒麟"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(1, "宋江", "及时雨"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(4, "林冲", "豹子头"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(3, "吴用", "智多星"));
         ......

        int nodeSize = singleLinkedList.getNodeSize();
        System.out.println("链表中有效节点个数为"+ nodeSize);

    }

}

/**
 * 定义链表管理节点
 */
class SingleLinkedList {

    ......

    public int getNodeSize(){
        return getNodeSize(head);
    }

    /**
     * 获取单链表的有效节点个数, (如果是带头节点,需要不统计头节点)
     * @param heroNode 头节点
     * @return 节点数据有效个数
     */
    public int getNodeSize(HeroNode heroNode){
        int count = 0;
        if (heroNode.next == null){
            return count;
        }
        HeroNode cursor = heroNode.next;
        while (cursor != null){
            count++;
            cursor = cursor.next;
        }
        return count;
    }

}

/**
 * 节点
 */
class HeroNode {
    ......
}

执行结果:

链表中有效节点个数为4

查询单链表中的倒数第k个节点[新浪面试题]

package com.dance.linkedlist;

/**
 * 单向链表(带头节点)
 */
public class SingleLinkedListDemo {

    public static void main(String[] args) {
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        ......

        // 带排序的添加
        singleLinkedList.addHeroNodeByNo(new HeroNode(2, "卢俊义", "玉麒麟"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(1, "宋江", "及时雨"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(4, "林冲", "豹子头"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(3, "吴用", "智多星"));
        ......

        int nodeSize = singleLinkedList.getNodeSize();
        System.out.println("链表中有效节点个数为"+ nodeSize);
        System.out.println("倒数第一个节点是:" + singleLinkedList.lastIndexOf(1));
        System.out.println("倒数第二个节点是:" + singleLinkedList.lastIndexOf(2));
        System.out.println("倒数第三个节点是:" + singleLinkedList.lastIndexOf(3));
        System.out.println("倒数第四个节点是:" + singleLinkedList.lastIndexOf(4));
        System.out.println("倒数第五个节点是:" + singleLinkedList.lastIndexOf(5));

    }

}

/**
 * 定义链表管理节点
 */
class SingleLinkedList {

    ......

    /**
     * 倒序查找
     * @param k 倒数第k个
     * @return k的元素
     */
    public HeroNode lastIndexOf(int k){
        // 如果k为负数或者头结点直接返回null
        if(k <= 0){
            return null;
        }
        int nodeSize = getNodeSize();
        // 如果k大于有效节点数直接返回null
        if(nodeSize < k){
            return null;
        }
        // 通过有效节点数 - k 取到正向节点数
        int index = nodeSize - k;
        int start = 1;
        HeroNode cursor = head.next;
        // 循环正向节点数 获取k
        while (start <= index){
            start++;
            cursor = cursor.next;
        }
        return cursor;
    }

    ......

}

/**
 * 节点
 */
class HeroNode {
    ......
}

执行结果:

链表中有效节点个数为4
倒数第一个节点是:HeroNode{no=4, name='林冲', nickName='豹子头'}
倒数第二个节点是:HeroNode{no=3, name='吴用', nickName='智多星'}
倒数第三个节点是:HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
倒数第四个节点是:HeroNode{no=1, name='宋江', nickName='及时雨'}
倒数第五个节点是:null

单链表的反转[腾讯面试题, 有点难度]

image.png
image.png

package com.dance.linkedlist;

/**
 * 单向链表(带头节点)
 */
public class SingleLinkedListDemo {

    public static void main(String[] args) {
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        ......

        // 带排序的添加
        singleLinkedList.addHeroNodeByNo(new HeroNode(2, "卢俊义", "玉麒麟"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(1, "宋江", "及时雨"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(4, "林冲", "豹子头"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(3, "吴用", "智多星"));
        ......
        singleLinkedList.showLinkedList();
        System.out.println("反转单链表");
        singleLinkedList.reverse();
        singleLinkedList.showLinkedList();

    }

}

/**
 * 定义链表管理节点
 */
class SingleLinkedList {

    /**
     * 定义头节点, 代表第一个节点,不要动
     */
    public final HeroNode head = createHeadNode();

    private HeroNode createHeadNode(){
        return new HeroNode(0, "", "");
    }

    ......

    public void reverse(){
        reverse(head);
    }

    /**
     * 反转单向链表
     * @param heroNode 头节点
     */
    public void reverse(HeroNode heroNode){
        // 如果当前链表为空 或者 有效节点数 为1 就不需要反转
        if(heroNode.next == null || heroNode.next.next == null){
            return;
        }
        // 0 -> 1 -> 2 -> 3 -> 4
        // 0 -> 4 -> 3 -> 2 -> 1
        // 定义辅助指针
        HeroNode current = heroNode.next; // 1 -> 2 -> 3 -> 4
        // 指向当前节点的下一个节点
        HeroNode next = null; // null
        // 定义新的头节点
        HeroNode reverseHead = createHeadNode(); // 0
        // 遍历原来的链表
        // 每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
        while (current != null) {
            // 保存当前节点的下一个节点
            // 1:  next = 2 -> 3 -> 4 -> null
            // 5:  next = 3 -> 4
            // 9:  next = 4
            // 13: null
            next = current.next;
            // 将当前节点的下一个节点放到reverseHead的最前面
            // 2:  current = 1 -> null
            // 6:  current = 2 -> 1 -> null
            // 10: current = 3 -> 2 -> 1 -> null
            // 14: current = 4 -> 3 -> 2 -> 1 -> null
            current.next = reverseHead.next;
            // 将reverseHead 的下一个节点 设置为当前节点
            // 3:  reverseHead = 0 -> 1 -> null
            // 7:  reverseHead = 0 -> 2 -> 1 -> null
            // 11: reverseHead = 0 -> 3 -> 2 -> 1 -> null
            // 15: reverseHead = 0 -> 4 -> 3 -> 2 -> 1 -> null
            reverseHead.next = current;
            // 当前节点向后移动
            // 4:  current = 2 -> 3 -> 4
            // 8:  current = 3 -> 4
            // 12: current = 4
            // 16: current = null
            current = next;
        }
        // 将head.next 指向 reverseHead.next ,实现单链表的反转
        head.next = reverseHead.next;
    }

}

/**
 * 节点
 */
class HeroNode {
    ......
}

执行结果

HeroNode{no=0, name='', nickName=''}
HeroNode{no=1, name='宋江', nickName='及时雨'}
HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{no=3, name='吴用', nickName='智多星'}
HeroNode{no=4, name='林冲', nickName='豹子头'}
反转单链表
HeroNode{no=0, name='', nickName=''}
HeroNode{no=4, name='林冲', nickName='豹子头'}
HeroNode{no=3, name='吴用', nickName='智多星'}
HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{no=1, name='宋江', nickName='及时雨'}

分析

逆向遍历单链表

思路分析图解
image.png
思路

  1. 上面的题的要求就是逆向打印单链表
  2. 方式1: 先将单链表进行反转操作,然后在遍历即可, 这样做的问题是会破坏原来的单链表结构,不建议
  3. 方式2: 可以;利用栈(stack)数据结构,将各个节点压入栈中,然后利用栈的先入后出的特点,就实现了逆向打印的效果
  4. 其实我还想到了一种方法,就是获取链表的有效节点数,然后通过有效节点数创建一个数组, 遍历链表将有效节点顺序放入数组,然后逆向遍历数组

代码实现(采用栈实现,上面已经实现了反转和打印了)

栈的简单使用(后面详细写)

package com.dance.linkedlist;

import java.util.Stack;

public class TestStack {
    public static void main(String[] args) {
        Stack<String> strings = new Stack<>();
        // 入栈
        strings.push("1");
        strings.push("2");
        strings.push("3");
        while (strings.size()>0) {
            // 出栈 从栈顶 取出
            String pop = strings.pop();
            System.out.println(pop);
        }
    }
}

执行结果

3
2
1

使用栈实现逆向遍历

package com.dance.linkedlist;

import java.util.Stack;

/**
 * 单向链表(带头节点)
 */
public class SingleLinkedListDemo {

    public static void main(String[] args) {
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        ......

        // 带排序的添加
        singleLinkedList.addHeroNodeByNo(new HeroNode(2, "卢俊义", "玉麒麟"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(1, "宋江", "及时雨"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(4, "林冲", "豹子头"));
        singleLinkedList.addHeroNodeByNo(new HeroNode(3, "吴用", "智多星"));
        ......

        singleLinkedList.showLinkedList();
        ......
        System.out.println("逆向打印单链表");
        singleLinkedList.showReverseLinkedList();
    }

}

/**
 * 定义链表管理节点
 */
class SingleLinkedList {

    ......

    /**
     * 逆向打印链表
     */
    public void showReverseLinkedList(){
        showReverseLinkedList(head.next);
    }

    /**
     * 逆向打印链表
     * @param node 头节点
     */
    public void showReverseLinkedList(HeroNode node){
        Stack<HeroNode> stacks = new Stack<>();
        heroNodeToStack(node, stacks);
        showStack(stacks);
    }

    /**
     * 打印栈
     * @param stacks 栈
     */
    public void showStack(Stack<HeroNode> stacks){
        while (!stacks.isEmpty()){
            System.out.println(stacks.pop());
        }
    }

    /**
     * 链表顺序压栈
     * @param heroNode 头节点
     * @param stack 栈
     */
    public void heroNodeToStack(HeroNode heroNode, Stack<HeroNode> stack){
        if(heroNode == null){
            return;
        }
        stack.push(heroNode);
        heroNodeToStack(heroNode.next,stack);
    }

}

/**
 * 节点
 */
class HeroNode {
    ......
}

执行结果

HeroNode{no=0, name='', nickName=''}
HeroNode{no=1, name='宋江', nickName='及时雨'}
HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{no=3, name='吴用', nickName='智多星'}
HeroNode{no=4, name='林冲', nickName='豹子头'}
逆向打印单链表
HeroNode{no=4, name='林冲', nickName='豹子头'}
HeroNode{no=3, name='吴用', nickName='智多星'}
HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{no=1, name='宋江', nickName='及时雨'}

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

这个我就只写思路了
思路:

  1. 创建两个带头的单向链表a,b
  2. 将b顺序遍历,然后通过排序添加到a中,实现合并

    双向链表应用实例

    双向链表的操作分析与实现

    需求

    使用带Head头的双向链表实现 - 水浒英雄排行榜

    单向链表和双向链表的区别

  3. 单向链表,查找的方向只能是一个方向,而双向链表可以向前和向后查找

  4. 单向链表,不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除节点时,总是需要临时节点,临时节点是待删除节点的前一个节点

    分析双向链表如何完成遍历,添加,修改和删除的思路

    03-数据结构与算法 链表 - 图9
    对上图的说明

    Head头结点

    不存放具体的数据,作用就是代表单链表的头节点

    属性

    pre : 用于存放当前节点的上一个节点的坐标
    next : 用于存放当前节点的下一个节点的坐标
    value: 值

    添加, 遍历所有节点

    和单向链表一样,只是多了可以向前查找的节点而已, 直接遍历到next属性为null的节点,添加到他的后面即可
    03-数据结构与算法 链表 - 图10
    也就是value:2{下面用current表示}
    current.next = 新节点
    新节点.pre = current

    代码实现

    我直接拿上面的单链表进行扩展了,就不重新写了, 可以直接集成单链表和HeroNode对象,重写自己需要重写的方法,然后只要处理好类型的上下转型就可以 ```java package com.dance.linkedlist;

import java.util.Stack;

/**

  • 双向链表(带头节点) */ public class DoubleLinkedListDemo {

    public static void main(String[] args) {

     DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
    
     // 添加节点
     doubleLinkedList.addHeroNode(new HeroNodeExt(2, "卢俊义", "玉麒麟"));
     doubleLinkedList.addHeroNode(new HeroNodeExt(1, "宋江", "及时雨"));
     doubleLinkedList.addHeroNode(new HeroNodeExt(4, "林冲", "豹子头"));
     doubleLinkedList.addHeroNode(new HeroNodeExt(3, "吴用", "智多星"));
    
     doubleLinkedList.showLinkedList();
    

    }

}

/**

  • 定义链表管理节点 */ class DoubleLinkedList extends SingleLinkedList {

    @Override protected HeroNode createHeadNode() {

     return new HeroNodeExt(0,"","");
    

    }

    @Override public void addHeroNode(HeroNode next) {

     HeroNode tailNode = getTailNode(head);
     if(next instanceof HeroNodeExt){
         HeroNodeExt current = (HeroNodeExt) next;
         tailNode.next = current;
         current.pre = tailNode;
     }else{
         throw new RuntimeException("参数类型错误,不是 HeroNodeExt.class");
     }
    

    } }

/**

  • 节点 */ class HeroNodeExt extends HeroNode {

    /**

    • 当前节点的上一个节点 */ public HeroNode pre;

      public HeroNodeExt(int no, String name, String nickName) { super(no, name, nickName); } }

      执行结果
      ```java
      HeroNode{no=0, name='', nickName=''}
      HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
      HeroNode{no=1, name='宋江', nickName='及时雨'}
      HeroNode{no=4, name='林冲', nickName='豹子头'}
      HeroNode{no=3, name='吴用', nickName='智多星'}
      

      修改节点信息

      代码实现

      修改节点信息和单链表的一样,直接调用父类的方法即可 ```java package com.dance.linkedlist;

import java.util.Stack;

/**

  • 双向链表(带头节点) */ public class DoubleLinkedListDemo {

    public static void main(String[] args) {

     DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
    
     // 添加节点
     doubleLinkedList.addHeroNode(new HeroNodeExt(2, "卢俊义", "玉麒麟"));
     doubleLinkedList.addHeroNode(new HeroNodeExt(1, "宋江", "及时雨"));
     doubleLinkedList.addHeroNode(new HeroNodeExt(4, "林冲", "豹子头"));
     doubleLinkedList.addHeroNode(new HeroNodeExt(3, "吴用", "智多星"));
    
     doubleLinkedList.showLinkedList();
     // 修改节点内容
     doubleLinkedList.updateHeroNodeByNo(new HeroNodeExt(2,"小卢","小玉"));
     doubleLinkedList.showLinkedList();
    

    }

}

……

执行结果
```java
HeroNode{no=0, name='', nickName=''}
HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{no=1, name='宋江', nickName='及时雨'}
HeroNode{no=4, name='林冲', nickName='豹子头'}
HeroNode{no=3, name='吴用', nickName='智多星'}
HeroNode{no=0, name='', nickName=''}
HeroNode{no=2, name='小卢', nickName='小玉'}
HeroNode{no=1, name='宋江', nickName='及时雨'}
HeroNode{no=4, name='林冲', nickName='豹子头'}
HeroNode{no=3, name='吴用', nickName='智多星'}

删除指定节点

代码实现

根据ID删除指定节点,重写父类的删除方法,根据NO查询指定节点可以调用父类的方法,然后通过转型移除节点

package com.dance.linkedlist;

import java.util.Stack;

/**
 * 双向链表(带头节点)
 */
public class DoubleLinkedListDemo {

    public static void main(String[] args) {
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();

        // 添加节点
        doubleLinkedList.addHeroNode(new HeroNodeExt(2, "卢俊义", "玉麒麟"));
        doubleLinkedList.addHeroNode(new HeroNodeExt(1, "宋江", "及时雨"));
        doubleLinkedList.addHeroNode(new HeroNodeExt(4, "林冲", "豹子头"));
        doubleLinkedList.addHeroNode(new HeroNodeExt(3, "吴用", "智多星"));

        doubleLinkedList.showLinkedList();
        // 修改节点内容
        doubleLinkedList.updateHeroNodeByNo(new HeroNodeExt(2,"小卢","小玉"));
        doubleLinkedList.showLinkedList();
        // 删除指定节点
        doubleLinkedList.removeHeroNodeByNo(2);
        doubleLinkedList.showLinkedList();

    }

}

/**
 * 定义链表管理节点
 */
class DoubleLinkedList extends SingleLinkedList {

    ......

    @Override
    public void removeHeroNodeByNo(int no) {
        HeroNodeExt current = (HeroNodeExt) getHeroNodeByNo(no);
        current.pre.next = current.next;
        ((HeroNodeExt)current.next).pre = current.pre;
    }
}

......

执行结果

HeroNode{no=0, name='', nickName=''}
HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{no=1, name='宋江', nickName='及时雨'}
HeroNode{no=4, name='林冲', nickName='豹子头'}
HeroNode{no=3, name='吴用', nickName='智多星'}
HeroNode{no=0, name='', nickName=''}
HeroNode{no=2, name='小卢', nickName='小玉'}
HeroNode{no=1, name='宋江', nickName='及时雨'}
HeroNode{no=4, name='林冲', nickName='豹子头'}
HeroNode{no=3, name='吴用', nickName='智多星'}
HeroNode{no=0, name='', nickName=''}
HeroNode{no=1, name='宋江', nickName='及时雨'}
HeroNode{no=4, name='林冲', nickName='豹子头'}
HeroNode{no=3, name='吴用', nickName='智多星'}

双向链表按照编号顺序添加节点

直接写代码吧,其实就是需要处理一下pre和下一个节点为null,不设置Pre的问题

package com.dance.linkedlist;

import java.util.Stack;

/**
 * 双向链表(带头节点)
 */
public class DoubleLinkedListDemo {

    public static void main(String[] args) {
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();

        // 添加节点
        doubleLinkedList.addHeroNodeByNo(new HeroNodeExt(2, "卢俊义", "玉麒麟"));
        doubleLinkedList.addHeroNodeByNo(new HeroNodeExt(1, "宋江", "及时雨"));
        doubleLinkedList.addHeroNodeByNo(new HeroNodeExt(4, "林冲", "豹子头"));
        doubleLinkedList.addHeroNodeByNo(new HeroNodeExt(3, "吴用", "智多星"));

        doubleLinkedList.showLinkedList();
        ......

    }

}

/**
 * 定义链表管理节点
 */
class DoubleLinkedList extends SingleLinkedList {

    ......

    @Override
    public void addHeroNodeByNo(HeroNode heroNode) {
        HeroNodeExt beforeNode = (HeroNodeExt)getHeroNodePosition(head, heroNode);
        HeroNodeExt current = (HeroNodeExt) heroNode;
        HeroNodeExt afterNode = (HeroNodeExt) beforeNode.next;
        beforeNode.next = current;
        current.pre = beforeNode;
        current.next = afterNode;
        if(afterNode != null) {
            afterNode.pre = current;
        }
    }

    ......
}

......

执行结果

HeroNode{no=0, name='', nickName=''}
HeroNode{no=1, name='宋江', nickName='及时雨'}
HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{no=3, name='吴用', nickName='智多星'}
HeroNode{no=4, name='林冲', nickName='豹子头'}

单向环形链表应用场景

Josephu(约瑟夫, 约瑟夫环)问题

Josephu问题为: 设编号为1, 2,……n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,他的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列
提示: 用一个不带头节点的单向环形列表来处理Josephu问题: 先构成一个有n个节点的单循环链表, 然后由k节点起从1开始计数, 记到m时,对应节点从 链表中删除,然后再从被删除节点的下一个节点又从1开始计数,直到最后一个节点从链表中删除,算法结束
image.png

单向环形链表介绍

image.png
其实就是不带头的单向链表,然后将最后节点的next指向第一个节点

Josephu问题思路图解

image.png

创建环形链表思路图解

image.png
构建一个单向的环形链表思路

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

遍历环形链表

  1. 先让一个辅助指针(curBoy)指向first节点
  2. 然后通过循环遍历该链表,当curBoy.next = first时结束

    小孩出圈的思路分析图

    image.png
    根据用户的输入生成一个小孩出圈的顺序
    n = 5 , 即有5个人
    k = 1 , 从第一个人开始报数
    m = 2 , 数2下
    需要创建一个辅助指针helper, 事先应该指向环形链表的最后这个节点
    补充:

  3. 小孩报数前,先让first和helper移动k-1次

  4. 当小孩报数时,让first和helper指针同时的移动m-1次
  5. 这时就可以将first指向的小孩节点出圈
    1. first = first.next
    2. helper.next = first
  6. 原来first指向的节点就没有任何引用了, 就会被下一次的GC回收
  7. 出圈的顺序为 : 2->4->1->5->3

    Josephu问题代码实现

    ```java package com.dance.linkedlist;

public class JosephuDemo { public static void main(String[] args) { CircleSingleLinkedList singleLinkedList = new CircleSingleLinkedList(); // 添加5个小孩 singleLinkedList.addBoy(20); // singleLinkedList.show(); singleLinkedList.killBoy(3,2,20); } }

class CircleSingleLinkedList {

/**
 * 第一个节点
 */
private Boy first = new Boy(-1);

public void addBoy(int nums) {
    if (nums < 1) {
        System.out.println("数量错误");
        return;
    }
    // 辅助指针帮助创建循环链表
    Boy curBoy = null;
    // 使用for创建环形链表
    for (int i = 1; i <= nums; i++) {
        Boy boy = new Boy(i);
        if (first.no == -1) {
            // 将头节点设置为第一个小孩
            first = boy;
            // 将第一个小孩的下一个节点设置为自己 构成环形
            first.next = first;
            // 将当前节点设置为第一个小孩方便下一次添加
            curBoy = first;
        } else {
            curBoy.next = boy;
            boy.next = first;
            curBoy = boy;
        }
    }
}

public void show() {
    Boy current = first;
    if (current.no == -1) {
        System.out.println("数据为空");
    } else {
        // 打印第一个节点
        System.out.println(current);
        // 循环打印后续节点
        while (current.next != first) {
            System.out.println(current.next);
            current = current.next;
        }
    }
}

/**
 * 根据用户的输入, 计算出小孩出圈的顺序
 *
 * @param startNo  表示从第几个小孩开始数数
 * @param countNum 表示数几下(包含自己)
 * @param nums     表示最初有几个小孩在圈
 */
public void killBoy(int startNo, int countNum, int nums) {
    if (checkParams(startNo, countNum, nums)) {
        // 创建辅助指针,帮助完成小孩出圈
        Boy helper = first;
        // 需要创建一个辅助指针(变量)helper 事先应该指向环形链表的最后这个节点
        helper = pointAfter(helper);
        // 小孩报数之前先移动K - 1 次, 因为不一定是从第一个小孩开始数
        for (int i = 0; i < startNo - 1; i++) {
            // 头节点后移
            first = first.next;
            // 尾节点后移
            helper = helper.next;
        }
        // 开始循环, 一直到圈中只有一个小孩
        while (true) {
            // 当头和尾都指向一个节点时,就只有一个节点了
            if (helper == first) {
                // 打印最后一个小孩
                System.out.println(first);
                break;
            }
            // 当小孩报数时, 让first和helper指针同时移动M - 1次,然后出圈
            for (int i = 0; i < countNum - 1; i++) {
                first = first.next;
                helper= helper.next;
            }
            // 这时first指向的节点就要出圈的小孩
            System.out.println(first);
            // 设置helper的指针指向出圈小孩的下一个节点
            helper.next = first.next;
            first = first.next;
        }

    }
}

public boolean checkParams(int startNo, int countNum, int nums) {
    if (first.no == -1) {
        System.out.println("链表为空");
        return false;
    } else if (startNo < 1) {
        System.out.println("不能从第0位开始数");
        return false;
    } else if (startNo > nums) {
        System.out.println("开始位置不能大于总数");
        return false;
    }
    return true;
}

public Boy pointAfter(Boy point) {
    while (true) {
        // 找到最后一个节点
        if (point.next == first) {
            return point;
        }
        point = point.next;
    }
}

}

class Boy { /**

 * 编号
 */
public int no;
/**
 * 下一个节点
 */
public Boy next;

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

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

}

执行结果
```java
Boy{no=4}
Boy{no=6}
Boy{no=8}
Boy{no=10}
Boy{no=12}
Boy{no=14}
Boy{no=16}
Boy{no=18}
Boy{no=20}
Boy{no=2}
Boy{no=5}
Boy{no=9}
Boy{no=13}
Boy{no=17}
Boy{no=1}
Boy{no=7}
Boy{no=15}
Boy{no=3}
Boy{no=19}
Boy{no=11}