双向链表:数据世界的 “双向小火车”

229 阅读5分钟

编程小白们集合!上次咱们聊了单项链表这个 “单向小火车”,今天再来认识一位它的 “兄弟”—— 双向链表。数据世界里的 “双向小火车”,它叫双向链表。听名字是不是感觉有点高大上?别担心,看完这篇文章,保准你能轻松拿捏它!

image.png 想象一下,你和一群小伙伴手拉手排成一列,每个人只能看到前面的人,要是想知道后面发生了什么,还得一个个传话,这就是单向链表。而双向链表就不一样啦,每个人不仅能拉住前面小伙伴的手,还能拉住后面小伙伴的手,前后信息交流超方便!这样不管从前面还是后面找起,都能快速找到你想要的小伙伴,双向链表处理数据的原理就和这差不多。

双向链表长啥样?

在双向链表中,每个数据元素都被封装在一个 “小房子” 里,这个 “小房子” 我们叫它节点。每个节点都有三个 “小房间”:一个用来存放数据,另外两个分别用来存放下一个节点和上一个节点的 “地址”(在 Java 里其实是引用)。就好比每个小伙伴身上都揣着前面和后面小伙伴家的门牌号,想去谁家串门都超方便!

用 Java 代码来定义一个双向链表的节点长这样:

class Node {
    int data;
    Node prev;
    Node next;
    Node(int data) {
        this.data = data;
        this.prev = null;
        this.next = null;
    }
}

在这段代码里,data 就是存放数据的 “房间”,prev 是指向前一个节点的 “门牌号”,next 则是指向后一个节点的 “门牌号” 。构造函数 Node(int data) 就像是给每个 “小房子” 分配数据,顺便把前后 “门牌号” 先设为空,等着后续 “入住” 其他节点。

双向链表的基本操作

1. 创建双向链表

创建双向链表就像是召集一群小伙伴手拉手站好。我们先创建几个节点,然后让它们前后 “握手” 连接起来。

class DoublyLinkedList {
    Node head;
    // 创建双向链表
    void createList() {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        // 连接节点
        node1.next = node2;
        node2.prev = node1;
        node2.next = node3;
        node3.prev = node2;
        head = node1;
    }
}

在 createList 方法里,我们先创建了三个节点,分别存着数字 1、2、3。接着让 node1 的 next 指向 node2,node2 的 prev 指向 node1,以此类推,最后把 head 设为 node1,这样一个简单的双向链表就创建好啦!

2. 插入节点

插入节点就好比在已经站好队的小伙伴中间,加入一个新的小伙伴。插入的位置不同,操作也有点不一样,咱们以在链表头部插入为例。

    // 在头部插入节点
    void insertAtHead(int data) {
        Node newNode = new Node(data);
        if (head == null) {
            head = newNode;
        } else {
            newNode.next = head;
            head.prev = newNode;
            head = newNode;
        }
    }

先创建一个新节点 newNode 存着要插入的数据。如果链表是空的(head == null),那这个新节点就是链表的头节点。要是链表已经有节点了,就让新节点的 next 指向原来的头节点 head,原来头节点的 prev 指向新节点,最后把 head 更新为新节点,这样新节点就成功 “插队” 到最前面啦!

3. 删除节点

删除节点就像把队伍里的某个小伙伴 “移除” 出去。我们以删除指定数据的节点为例。

    // 删除指定数据的节点
    void deleteNode(int data) {
        Node current = head;
        while (current != null) {
            if (current.data == data) {
                if (current.prev != null) {
                    current.prev.next = current.next;
                } else {
                    head = current.next;
                }
                if (current.next != null) {
                    current.next.prev = current.prev;
                }
                return;
            }
            current = current.next;
        }
    }

先从链表头节点开始,一个个找,看看哪个节点存的数据和我们要删除的数据一样。如果找到了,就分情况处理:要是这个节点前面还有其他节点,就让前面节点的 next 跳过当前节点,直接指向下一个节点;要是它就是头节点,那就把 head 更新为它后面的节点。同时,还要处理好它后面节点的 prev,让其指向前面的节点,这样就把这个节点从链表中 “踢出去” 啦!

4. 遍历双向链表

遍历双向链表就像是从队伍的一头走到另一头,挨个和小伙伴打招呼。因为双向链表可以从前往后,也能从后往前遍历,咱们试试从前往后遍历。

    // 遍历双向链表
    void traverse() {
        Node current = head;
        while (current != null) {
            System.out.print(current.data + " ");
            current = current.next;
        }
    }

从链表头节点 head 开始,只要当前节点不是空的,就打印出它存的数据,然后让 current 指向下一个节点,直到把整个链表的节点都 “看” 一遍,这样就完成了遍历操作。

双向链表的优缺点

双向链表这么灵活,是不是就完美无缺了呢?当然不是!它也有自己的优缺点。

  • 优点:可以双向遍历,不管从前往后还是从后往前找数据都很方便;在插入和删除节点时,操作相对灵活,因为能直接找到前后节点。
  • 缺点:每个节点都要多存一个指向前一个节点的引用,会占用更多的内存空间;插入和删除节点时,需要处理更多的指针关系,代码稍微复杂一些。

好啦,关于双向链表的知识就介绍到这里!现在你是不是觉得双向链表也没那么难理解啦?以后在编程中遇到需要频繁插入、删除数据,还得前后查找的场景,就可以考虑让双向链表这个 “双向小火车” 来帮忙啦!快去用 Java 代码实践一下吧,相信你很快就能玩转它!