栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

带头单链表Java实现

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

带头单链表Java实现

目录
    • 带头单链表
      • 一、概念
      • 二、要点总结
      • 三、代码实现
        • 1. 类结构
        • 2. 带头单链表的遍历
        • 3. 插入结点
          • 3. 1. 头插法
          • 3.2. 指定索引处插入结点
          • 3.3 尾插法
        • 4. 删除结点
          • 4.1. 删除指定索引处的结点
          • 4.2 删除所有值为val的结点

带头单链表 一、概念

带头单链表中默认带有一个虚拟头结点dummyHead,不存储有效元素。

二、要点总结
  • 链表中仅创建了虚拟头结点的Node对象 private Node dummyHead = new Node();

    虚拟头结点的属性值val默认为0(不存储有效元素),next引用默认为null

  • 带有虚拟头结点后,头结点也具有了前驱结点,带头单链表中所有的结点都有了前驱结点

    所有结点的插入、删除方式均相同,无需再对头结点进行单独处理,无需单独考虑头结点

  • 插入、删除操作的核心即寻找前驱结点,带头单链表中第一个前驱结点即为虚拟头结点

  • 遍历单链表中的每个元素

    • 遍历带头单链表的每个元素:从第一个有效元素开始(越过虚拟头结点) Node x = dummyHead.next;

    • 遍历不带头单链表的每个元素:从头结点开始 Node x = head;

  • 在带头单链表中根据index寻找结点

    ​ 从虚拟头结点向后走 Node x = dummyHead;

    • 寻找index的前驱结点:从虚拟头结点开始向后走 index 步即到达index的前驱结点

      for(int i = 0; i < index; i++) —— 循环执行index次

    • 寻找index结点:从虚拟头结点开始向后走 (index+1) 步即到达index结点

      for(int i = 0; i < index+1; i++) —— 循环执行(index+1)次

三、代码实现 1. 类结构
//结点类
class Node {
    int val;  //当前结点保存的值
    Node next;  //下一个结点的地址

    public Node(int val) {
        this.val = val;
    }
    public Node(int val, Node next) {
        this.val = val;
        this.next = next;
    }
    public Node() {};
}
//带头单链表
public class SingleLinkedListWithDummyHead {

    private int size;  //链表中有效元素的个数(不包含虚拟头结点),第一个有效元素的索引为0
    private Node dummyHead = new Node();  //虚拟头结点
    ...(增删改查等方法)
}
2. 带头单链表的遍历

【遍历单链表中的每个元素】

  • 遍历带头单链表的每个元素:从第一个有效元素开始(越过虚拟头结点) Node x = dummyHead.next; √

  • 遍历不带头单链表的每个元素:从头结点开始 Node x = head;

public String toString() {
    String ret = "";
    Node x = dummyHead.next;  //从第一个有效元素开始遍历
    while (x != null) {
        ret += x.val + "->";
        x = x.next;
    }
    ret += "NULL";
    return ret;
}
3. 插入结点 3. 1. 头插法

【不带头、带头单链表的头插法区别】

  • 不带头单链表:对于原头结点存在(链表非空)、原头结点不存在(链表为空)的两种情况,头插操作部分不同

  • 带头单链表:无论链表是否为空,原头结点均可视为存在,只是空链表的原头结点为空结点,而非空链表的原头结点为有效结点,两种情况的头插操作完全相同

public void addFirst(int val) {
    
    dummyHead.next = new Node(val, dummyHead.next);
    //有效元素增1
    size++;
}
3.2. 指定索引处插入结点

在index处插入结点需找到index的前驱结点。

对于带头单链表,所有的结点包括头结点都有前驱结点,故所有结点的插入操作都相同,无需单独考虑头结点。

【在带头单链表中根据index寻找结点】

从虚拟头结点向后走 Node x = dummyHead;

  • 寻找index的前驱结点:从虚拟头结点开始向后走 index 步即到达index的前驱结点

    for(int i = 0; i < index; i++) —— 循环执行index次

  • 寻找index结点:从虚拟头结点开始向后走 (index+1) 步即到达index结点

    for(int i = 0; i < index+1; i++) —— 循环执行(index+1)次

public void add(int index, int val) {
    //【传参涉及index就要判断其合法性】允许(index=0)头插、(index=size)尾插
    if(index < 0 || index > size) {
        System.err.println("add index illegal!");
        return;
    }
    //从虚拟头结点开始向后寻找index的前驱结点
    Node prev = dummyHead;
    //走index步即找到prev结点 ==> 循环index次
    for(int i = 0; i < index; i++) {
        prev = prev.next;  //prev向后移动一步
    }
    
    prev.next = new Node(val, prev.next);
    //有效元素增1
    size++;
}
3.3 尾插法

可直接复用指定索引插入结点的方法add(int index, int val)。尾结点有前驱结点,同其他所有结点的插入操作相同。

public void addLast(int val) {
    add(size, val);
}
4. 删除结点 4.1. 删除指定索引处的结点

删除index处的结点需先找到index的前驱结点。

对于带头单链表,所有的结点包括头结点都有前驱结点,故所有结点的删除操作都相同,无需单独考虑头结点。

private boolean rangeCheck(int index) {
    if(index < 0 || index >= size) {  //必须在有效元素的范围内,不允许(index=size)
        System.err.println("index illegal!");
        return false;
    }
    return true;
}


public int remove(int index) {
    //【传参涉及index就要判断其合法性】
    if(!rangeCheck(index)) {
        return -1;
    }
    //从虚拟头结点开始向后寻找index的前驱结点
    Node prev = dummyHead;
    //向后走index步即找到前驱结点
    for(int i = 0; i < index; i++) {
        prev = prev.next;
    }
    //找到index的前驱结点,暂存index结点值用于返回
    Node node = prev.next;
    int temp = node.val;
    //删除index结点
    prev.next = node.next;  //前驱结点的next指针指向后驱结点
    node.next = node = null;  //指向后驱结点的next指针置空,将node自身也置空
    size--;
    return temp;
}
4.2 删除所有值为val的结点

1)考虑链表中出现多个连续相邻的val要删除的情况:

设置循环,每次都只删除一个结点,删除后即检查此结点的后继结点是否还是val,若还是val则继续删除此结点,若不是val则向后移动前驱结点指针prev,寻找下一个要删除的val

2)由于带头单链表的所有结点(包括头结点)都有前驱结点,故无需单独考虑头结点

public void removeByValueAll(int val) {
    //寻找val结点的前驱结点
    Node prev = dummyHead;  //虚拟头结点是链表中的第一个前驱结点
    while (prev.next != null) {  //后方还有结点
        if (prev.next.val == val) {  //循环删除所有值为val的结点
            //删除结点
            Node node = prev.next;
            prev.next = node.next;
            node.next = node = null;
            size--;
        } else {  //prev.next不是待删除结点
            prev = prev.next;  //移动prev指针
        }
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/848351.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号