数据结构系列(2)—链表

567 阅读9分钟

学 无 止 境 , 与 君 共 勉 。


相关系列

简介

链表是一种将数据存储在链节点中的数据结构。在链表中,每个数据项都包含在一个链节点中。每个链节点中除了数据项外,还包含了对其他链节点的引用。需要存储多少个数据,就生成多少个链节点,这些链节点点之间相互引用。

单链表

在单链表中,包含了对首个链表节点的引用(可以称为first),每个链表节点都包含了一个对下一个链节点引用的字段(可以称为next)。

单链表的建立有头插法和尾插法两种

头插法:

将链表的右端看成是固定的,插入时,将首个链节点的引用指向要插入的节点,将插入节点的对下个节点的引用next指向原来最左边的节点;

尾插法:

将链表的左端看成是固定的,每次插入时,将原先最右边节点的next指向新插入的节点;

双端链表

相对单链表,在双端链表中,还包含了对最后一个链表节点的引用(可以称为last),可以同时实现在头部和尾部插入新的节点。插入方式和单链表类似。需要注意的是,在尾部插入节点数据后,需要将对最后一个节点的引用last指向新插入的节点

双向链表

前面讲的那些链表都是只支持向前遍历,只能获取的下一个节点的信息,无法回过去获取上一个节点的信息。双向链表解决了这个问题,它的链表节点中除了包含对下一节点的引用next外,还包含了对上一节点的引用prev。但是相应的每次插入或者删除的时候需要处理4个链节点的引用,链节点的占用空间也变大了一点。双向链表可以不是双端链表,这里我们用双向双端展示。

插入

将要插入节点的prev指向前一个节点,next指向后一节点,将前一节点的next指向插入节点,将后一节点的prev指向插入节点;

  • 在头部插入
   /**
     * 在首位插入
     * 空的,首节点和末节点都指向新节点;
     * 新节点的next指向原来首节点,
     * 原来首节点存在的prev指向新节点
     *
     * @param data 数据
     */

    public void insertFirst(E data) {
        // 新节点 next 为原来第一个节点,prev 为 null
        Node<E> newNode = new Node<E>(null, data, this.first);
        // 空的,需要将最后的节点也指向新的节点
        if (this.first == null) {
            this.last = newNode;
        } else {
            this.first.setPrev(newNode);
        }
        this.first = newNode;
        this.size++;
    }
  • 在尾部插入
   /**
     * 在末尾插入
     * 空的,首节点和末节点都指向新节点;
     * 新节点的prev指向原来尾节点,
     * 原来尾节点存在的next指向新节点
     *
     * @param data 数据
     */

    public void insertLast(E data) {
        // 新节点 prev 为原来最后一个节点,next 为 null
        Node<E> newNode = new Node<E>(this.last, data, null);
        if (this.first == null) {
            this.first = newNode;
        } else {
            this.last.setNext(newNode);
        }
        this.last = newNode;
        this.size++;
    }
  • 在中间插入
   /**
     * 在目标节点前添加
     *
     * @param data    数据
     * @param tagNode 目标节点
     */

    public void insertBefore(E data, Node<E> tagNode) {
        // 前置节点
        Node<E> prevNode = tagNode.getPrev();
        // 新节点的前置节点为目标节点的前置节点,后置节点为目标节点
        Node<E> newNode = new Node<E>(prevNode, data, tagNode);
        // 重置next节点
        prevNode.setNext(newNode);
        // 重置prev节点
        tagNode.setPrev(newNode);
        this.size++;
    }

删除

将要删除节点的前一节点的next指向要删除节点的后一节点,将要删除节点的后一节点的prev指向要删除节点的前一节点;

  • 删除头部
   /**
     * 删除头部数据
     * 只有一个节点的删除后,将最后节点引用设为null
     * 首个节点的引用设为原来第二个节点
     * 原来第二个节点的前置节点设为null
     */

    public void delFirst() {
        Node<E> fNode = this.first;
        if (fNode == null) {
            System.out.println("删除失败,这是个空链表!!!");
            return;
        }
        Node<E> sNode = fNode.getNext();
        fNode.setData(null);
        // 去除引用,用于GC
        fNode.setNext(null);
        // 重置首个节点
        this.first = sNode;
        // 链表只有1个节点的,删除后最后一个节点指向null
        if (sNode == null) {
            this.last = null;
        } else {
            // 重置prev节点
            sNode.setPrev(null);
        }
        this.size--;
    }
  • 删除尾部
   /**
     * 删除尾部
     * 只有一个节点的删除后,将首个节点引用设为null;
     * 最后一个节点的引用设为原来倒数第二个节点
     * 原来倒数第二个节点的后置节点设为null
     */

      public void delLast() {
        Node<E> lNode = this.last;
        if (lNode == null) {
            System.out.println("删除失败,这是个空链表!!!");
            return;
        }
        Node<E> prevNode = lNode.getPrev();
        lNode.setData(null);
        lNode.setPrev(null);
        this.last = prevNode;
        if (prevNode == null) {
            this.first = null;
        } else {
            prevNode.setNext(null);
        }
        this.size--;
    }
  • 删除中间
   public void del(Node<E> delNode) {
        Node<E> prevNode = delNode.getPrev();
        Node<E> nextNode = delNode.getNext();
        // 先处理左边2个引用关系
        if (prevNode == null) {
            // 删除首个
            this.first = nextNode;
        } else {
            prevNode.setNext(nextNode);
            delNode.setPrev(null);
        }
        // 再处理右边2个引用关系
        if (nextNode == null) {
            // 删除尾部节点
            this.last = prevNode;
        } else {
            nextNode.setPrev(prevNode);
            delNode.setNext(null);
        }
        delNode.setData(null);
        this.size--;
    }

小结

数据存在于链表节点中;
节点中包含了对其他节点的引用;
插入、新增时调整前后节点的引用关系;
通过当前节点对下一节点或者上一节点的引用去遍历数据;

访问源码

本系列所有代码均上传至Github上,方便大家访问

>>>>>> 数据结构-链表 <<<<<<

日常求赞

创作不易,如果各位觉得有帮助,求点赞支持

求关注