[Data structure]双链表 | 一文带你了解线性数据结构之一的双链表

本文介绍了双向链表的基本概念、常见操作,包括创建、遍历、插入、删除和修改节点,探讨了其时间复杂度,并提供了Java代码实现。文章还强调了双向链表在正向和反向遍历的效率优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

⭐作者介绍:大二本科网络工程专业在读,持续学习Java,努力输出优质文章
⭐作者主页:@逐梦苍穹
⭐所属专栏:数据结构

1、简介

  双向链表(Doubly linked list)是一种常见的线性数据结构,它与单链表类似,但每个节点不仅包含一个指向下一个节点的指针,还包含一个指向前一个节点的指针。
  这使得双向链表可以在需要正向和反向遍历的场景中提高效率。

2、常见操作

下面是双向链表的一些基本操作:

  1. 链表的创建:与单链表类似,在创建链表时需要创建头节点,并将其前驱指针和后继指针都设置为NULL。然后逐个创建后续节点,将前一个节点的后继指针指向当前节点,将当前节点的前驱指针指向前一个节点,直到创建完整个链表。
  2. 链表的遍历:与单链表类似,从头节点开始,逐个遍历链表中的节点,直到遍历到尾节点为止。
  3. 链表的插入:可以在链表的任意位置插入一个新节点。具体操作是先将新节点的指针指向其下一个节点,然后将前一个节点的后继指针指向新节点,将下一个节点的前驱指针指向新节点。
  4. 链表的删除:可以删除链表中的任意节点。具体操作是将待删除节点的前一个节点的后继指针指向待删除节点的下一个节点,将下一个节点的前驱指针指向待删除节点的前一个节点,然后释放待删除节点的内存空间。

3、时间复杂度

双向链表的时间复杂度如下:

  1. 链表的创建时间复杂度为O(n),其中n为链表中节点的个数。
  2. 链表的遍历时间复杂度为O(n),其中n为链表中节点的个数。
  3. 链表的插入时间复杂度为O(1)。
  4. 链表的删除时间复杂度为O(1)。

需要注意的是,与单链表相比,双向链表需要占用更多的内存空间,因为每个节点需要同时保存前驱指针和后继指针。此外,双向链表在需要正向和反向遍历的场景中表现更好,但在只需要正向遍历的场景中,其效率与单链表相当。

4、代码实现思路总览

双链表与单链表在实现过程中最大的不同是:在实现的方法中,不再需要遍历到 目标节点的前一个节点,而是直接遍历到目标节点。
回顾一下单链表:遍历到目标节点的前一个节点的原因:单链表是单向的,如果直接遍历到目标节点,则无法修改目标节点前一个节点的指针指向,会破坏链表结构。
而双链表则没有这个问题,因为多了一个前驱指针pre

实现过程简要分析如下:
在这里插入图片描述

  首先创建节点对象代码Node类,创建成员变量:唯一标识符id,节点的值value,节点Node类型的next、节点Node类型的pre(重点)以及重写toString方法。
  然后创建双链表类DoubleLinkedList,先初始化头节点,然后依次编写各种方法:获取头节点、添加节点(两种方式)、删除节点、修改节点、显示链表内容。[总体实现思路如下图所示]:
在这里插入图片描述

5、Node

重写的toString方法,next和pre不直接打印对象内容,而是打印十六进制的地址值:
在这里插入图片描述

这里还运用了三元表达式.

下面是Node的代码:

package linkedList.doubleLinkedList;

/**
 * @author 逐梦苍穹
 * @date 2023/5/12 10:00
 */

public class Node {
    public int id;
    private int value;
    private Node next;
    private Node pre;

    public Node(int id, int value) {
        this.id = id;
        this.value = value;
    }

    @Override
    public String toString() {
        return "Node{" +
                "id=" + id +
                ", value=" + value +
                ", next=" + (next == null ? null
                : ("0x" + Integer.toHexString(next.hashCode()))) +
                ", pre=0x" + Integer.toHexString(pre.hashCode()) +
                '}';
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public Node getPre() {
        return pre;
    }

    public void setPre(Node pre) {
        this.pre = pre;
    }
}

6、DoubleLinkedList

双链表的全部代码如下,后面对其详细拆分解析:

package linkedList.doubleLinkedList;

/**
 * @author 逐梦苍穹
 * @date 2023/5/12 10:00
 */
public class DoubleLinkedList {
    private final Node headNode = new Node(0, 0);

    /**
     * 获取头节点
     */
    public Node getHeadNode() {
        return headNode;
    }

    /**
     * 添加到尾部
     */
    public void addNode(Node node) {
        Node temp = this.headNode;
        while (true) {
            if (temp.getNext() == null) {
                break;
            } else {
                //辅助节点后移
                temp = temp.getNext();
            }
        }
        //跳出循环说明到达链表尾部,可以添加
        temp.setNext(node); //把最后一个节点的next指针置为下一个要添加的节点的地址值
        node.setPre(temp); //把添加到末尾的节点的pre前置指针置为上一个节点的地址值
    }

    /**
     * 根据id查找位置插入
     */
    public void addNodeByOrder(Node node) {
        Node temp = this.headNode;

        while (true) {
            if (temp.getNext() == null) { // 如果temp是尾节点
                temp.setNext(node);
                node.setPre(temp); // 设置新节点的前驱节点为temp
                break;
            }
            if (node.id < temp.getNext().id) { // 如果新节点应该插入到temp之后
                node.setNext(temp.getNext());
                node.setPre(temp); // 设置新节点的前驱节点为temp
                temp.getNext().setPre(node); // 设置新节点之后的节点的前驱节点为node
                temp.setNext(node);
                break;
            }
            temp = temp.getNext();
        }
    }

    /**
     * 删除节点
     */
    public void deleteNode(int id) {
        Node temp = this.headNode;
        if (temp.getNext() == null) {
            System.out.println("链表空无法删除");
            return;
        }
        while (true) {
            if (temp.getNext() == null) break;
            if (temp.id == id) {
                temp.getPre().setNext(temp.getNext());
                temp.getNext().setPre(temp.getPre());
                System.out.println("Node{id=" + id + "}:删除成功");
                return;
            }
            temp = temp.getNext();
        }
        System.out.println("无此节点");
    }

    /**
     * 修改节点
     */
    public void updateNode(int id, int newValue) {
        Node temp = this.headNode;
        if (temp.getNext() == null) {
            System.out.println("链表空");
        }
        while (true) {
            if (temp.getNext() == null) break;
            if (temp.id == id) {
                temp.setValue(newValue);
                System.out.println("Node{id=" + id + ", newValue=" + newValue + "}");
                return;
            }
            temp = temp.getNext();
        }
        System.out.println("无此节点");
    }

    /**
     * 打印整个双链表的内容
     */
    public void outDoubleLinkedList() {
        Node temp = this.headNode.getNext();
        if (temp == null) {
            System.out.println("链表空");
            return;
        }
        while (true) {
            System.out.println(temp);
            if (temp.getNext() == null) break;
            temp = temp.getNext();
        }
    }
}

6.1、添加节点

添加节点有两种方式:直接添加到链表尾部、按唯一标识id按顺序添加

6.1.1、addNode

在这里插入图片描述

直接添加到链表尾部:先判断当前节点的next域是不是null,如果是,说明到达链表尾部,可以添加;如果不是,则链表指针需要后移,直到抵达尾部:
在这里插入图片描述

6.1.2、addNodeByOrder

在这里插入图片描述

按唯一标识id按顺序添加:
  这里在while中有两个if判断,一开始是指向头节点的,这时候链表如果为空,则直接添加数据即可;
  当链表不为空的时候,进入的是第二个if分支,由于每一次插入的时候,都是保持有序的,所以在插入节点的时候只需要判断:想要插入节点的位置的下一个节点的id大于想要插入节点的id即可。
在这里插入图片描述

6.2、删除节点

在这里插入图片描述

  删除节点的操作:找到待删除节点,把 该节点的pre的next指针 指向 该节点的next,然后把 该节点的next指针的pre 指向 该节点的pre
  被删除的节点,是不需要手动释放资源的,JVM有自动回收机制。
在这里插入图片描述

6.3、修改节点

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

6.4、显示链表内容

  显示链表内容的思路,其实无非也就是遍历。这里采用的是while进行遍历,因为并不知道链表到底有多长,所以采用while遍历更合适。
  遍历终止条件:链表为空 或者 到达最后一个节点(即在判断不为空的情况下出现next为null)
在这里插入图片描述

写在最后
  ⭐如果觉得文章写的不错,欢迎点个关注一键三连。您的三连支持,是我创作的最大动力
  ⭐有写的不好的地方也欢迎指正,一同进步😁

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逐梦苍穹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值