图解链表 —— JAVA中的单链表基本操作

1. 什么是链表

链表是一种物理存储结构上非连续的存储结构,是线性表的一种。
使用链表的原因:链表和顺序表同属于一种线性表但是顺序表有扩容浪费空间,以及中间位置插入元素和删除元素需要移动大量的数据效率低的缺点,使用链表则可以解决这些问题。

2. 链表的种类

  • 单向,双向
  • 带头,不带头
  • 循环,非循环

重点掌握:无头单向非循环链表,一般是配合其他数据结构使用,如哈希桶,图等等;另一个就是无头双向链表。

3. 创建单链表

单链表由两个类组成,一个类用来存储单链表中节点的元素以及指向下一个节点的地址,相当于现实社会中的火车车厢,每节车厢通过铁链相连,铁链相当于指向下一个节点的地址,只有知道地址才能使两个节点相连。每节车厢存放不同的物品就是节点中的元素。有了车厢才能组成一辆火车,所以另一个类就是火车类,存储的是单链表中实际存储的元素和单链表的头节点。

3.1 创建 Node 类(车厢类)

/**
 * 车厢类
 */
public class Node {
    // 存储具体元素
    int data;
    // 存储下一个节点的地址
    Node next;

    public Node(int data) {
        this.data = data;
    }

    public Node(int data, Node next) {
        this.data = data;
        this.next = next;
    }
}

3.2 创建 MySingleList (火车类)

public class MySingleList {
    // 链表中的实际存储元素
    private int size;
    // 链表中的头节点
    private Node head;
}

4. 单链表的基本操作

4.1 增加链表元素

4.1.1 在链表头部添加元素

public void firstAdd(int data) {
        // 关注临界条件
        // 1.链表中有没有节点,判空
        // 2.链表中已经有节点了
        if (size == 0) {
            // 1. 先创建一个新节点存放 data 值
            Node node = new Node(data);
            // 2. 头节点变为新加入的节点
            head = node;
            size++;
        } else {
            // 1. 先创建一个新节点存放 data 值
            Node node = new Node(data);
            // 2. 将新节点与原来的第一个节点连接
            node.next = head;
            // 3. 将头节点变为新的节点
            head = node;
            size++;
        }
    }

图解:
在这里插入图片描述

4.1.2 在链表任意位置添加元素

在 index 位置插入新节点要先找到前驱节点,为什么找前驱节点,因为单链表特点是只能从前往后遍历,所以要在任意位置添加节点要先知道它的前驱节点。添加步骤:先要使新节点与原来该位置的节点相连,并且将原来的前驱节点与新节点相连,这样就添加成功了。

        public void indexAdd(int index, int data) {
        // 边界条件:1. 链表是否为空 2. 索引是否合法 3. index 是否是第一个节点
        // 1. 链表是否为空
        if (size == 0) {
            System.err.println("链表为空!");
        } else if (index < 0 || index > size) { //2. 索引是否合法
            System.err.println("索引非法!");
        } else if (index == 0) {  // 3. index 是否是第一个节点
            firstAdd(data);
        } else {
            Node node = new Node(data);
            // 前驱节点
            Node prev = head;
            // 遍历找到 index 的前驱节点
            for (int i = 0; i < index - 1; i++) {
                prev = prev.next;
            }
            // 先将 index 位置的节点与插入的节点连接,index 位置的节点就是 prev.next
            node.next = prev.next;
            // 再将插入的节点与前驱节点连接
            prev.next = node;
            size++;
        }
    }

图解:
在这里插入图片描述

4.1.3 在链表尾部添加元素

    public void lastAdd(int data) {
        indexAdd(size,data);
        size++;
    }

4.2 删除链表元素

在单链表中遇到 index 都需要判断索引是否合法,所以可以将判断索引是否合法这个方法提出来。

    // 索引是否合法
    private boolean rangeCheck(int index) {
        if (index < 0 || index >= size) {
            System.err.println("索引非法!");
            return false;
        }
        return true;
    }

4.2.1 删除第一个节点元素

    public void deleteFirst() {
        // 创建一个临时变量存放 head
        Node node = head;
        // 将头节点从第一个变为第二个
        head = head.next;
        // 将原来的第一个节点断开
        node.next = null;
        size--;
    }

图解:
在这里插入图片描述

4.2.2 删除任意位置 index 元素

    public void deleteIndex(int index) {
        // 边界条件:1. 链表为空 2. 索引合法 3. 删除的是第一个节点
        // 1. 链表为空
        if (size == 0) {
            System.out.println("链表为空!");
            // 2. 索引合法
        } else if (rangeCheck(index)) {
            // 3. 删除的是第一个节点
            if (index == 0) {
                deleteFirst();
            } else {
                // 暂存 head 节点
                Node prev = head;
                for (int i = 0; i < index; i++) {
                    prev = prev.next;
                }
                // prev 指向待删除节点的前驱节点
                // node 就是待删除节点
                Node node = prev.next;
                // 链接前驱节点和后继节点
                prev.next = node.next;
                // 将当前 node 节点的 next 置为空,断开待删除元素和链表的链接
                node.next = null;
                size--;
            }
        }
    }

图解:
\

4.2.3 删除链表中指定元素的第一个节点

    public void deleteDataOnce(int data) {
        if (size == 0) {
            System.out.println("链表为空!");
        } else {
            // 临界条件:指定元素在头节点
            if (head.data == data) {
                deleteFirst();
            } else {
                Node prev = head;
                while (prev.next != null) {
                    // 如果 prev.next 是指定元素
                    if (prev.next.data == data) {
                        // 创建 node 放指定元素的节点
                        Node node = prev.next;
                        // 将 prev.next 指向 prev.next.next,跳过 node
                        prev.next = node.next;
                        // 将 node 与链表断开
                        node.next = null;
                        size--;
                        break;

                    } else {
                        // 如果 prev.next 不是指定元素,继续判断下一个
                        prev = prev.next;
                    }
                }
            }
        }
    }

图解:
在这里插入图片描述

4.2.4 删除链表的所有指定元素

    public void deleteAll(int data) {
        // 临界条件:指定元素是第一个节点
        // head != null 链表中的元素都相同,如果一直删除会出现链表为空,不能继续往下判断
        while (head != null && head.data == data) {
            deleteFirst();
        }
        // 链表都是待删除的节点且已经被删除完了
        if (head == null) {
            return;
        } else {
            Node prev = head;
            while (prev.next != null) {
                if (prev.next.data == data) {
                    // node 就是待删除节点
                    Node node = prev.next;
                    prev.next = node.next;
                    node.next = null;
                    size--;
                } else {
                    prev = prev.next;
                }
            }
        }
    }

图解:
在这里插入图片描述

4.3 查找链表元素

4.3.1 判断链表中是否包含元素 data

    public boolean contains(int data) {
        Node node = head;
        while (node != null) {
            if(node.data == data) {
//                System.out.println("链表中包含该元素");
                return true;
            }
            node = node.next;
        }
//        System.out.println("链表中没有该元素");
        return false;
    }

4.3.2 得到链表中 index 位置的元素

public int get(int index) {
        if(rangeCheck(index)) {
            Node node = head;
            for (int i = 0; i < index; i++) {
                node = node.next;
            }
            // node.data 就是 index 位置的元素
            int data = node.data;
            return data;
        }else{
        // 索引不合法
            return -1;
        }
    }

4.4 修改链表元素

    public void set(int index, int data) {
        if(rangeCheck(index)) {
            Node node = head;
            for (int i = 0; i < index; i++) {
                node = node.next;
            }
            // 将原来 index 位置的元素修改成 data
            node.data = data;
        } else{
            System.out.println("索引非法");
        }
    }

4.5 打印链表

    public String toString() {
        String result = "";
        // 创建一个新节点存放头节点防止遍历完链表找不到链表的头
        Node node = head;
        while (node != null) {
            result += node.data + "->";
            node = node.next;
        }
        // 如果链表为空,引用类型的默认值为 null
        result += "null";
        return result;
    }

总结:1. 链表中无论是添加还是删除操作如果参数中有索引,都需要注意索引是不是链表中的头节点,因为头节点比较特殊,它没有前驱节点。2. 使用索引时还需要注意索引是否合法即 index < 0 || index >= size 索引不合法。这点与之前顺序表有所不同,因为顺序表可以在最后扩容所以 index 可以等于 size。

  • 12
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值