4.双链表.md 16 KB

4. 双链表

概念

双链表(Doubly Linked List)是线性表的一种链式存储实现,每个结点除了数据域和指向后继的指针 next 外,还多了一个指向前驱的指针 prior。这样既能向后遍历,也能向前遍历。

  • 逻辑结构:线性结构(同顺序表、单链表)。
  • 存储结构:链式存储。结点含 dataprior(前驱指针)、next(后继指针)三部分。
  • 带头结点:同单链表,推荐使用头结点统一操作逻辑。头结点的 prior 指向 NULL。
  • 特点
    • 优点:既支持向后也支持向前遍历;给定结点时,前插 O(1)(单链表前插需要从头找前驱 O(n),双链表直接用 prior);删除给定结点也只需 O(1)。
    • 缺点:每个结点多一个指针,存储开销比单链表大;插入/删除时需要修改的指针更多(更易出错);仍不能随机存取。
  • 适用场景:需要双向遍历、频繁在某结点之前插入、需要高效删除已知结点的场景(如 LRU 缓存的双向链表实现)。

与单链表对比:单链表只能单向、前插需 O(n);双链表双向、前插 O(1),代价是每个结点多存一个指针。

核心操作

操作 说明
初始化 创建头结点,prior 与 next 均置 NULL
判空 头结点 next 是否为 NULL(动态分配,无判满)
按位查找 从头计数,返回第 i 个结点(O(n))
后插 在结点 p 之后插入新结点(O(1),仅改 4 个指针)
前插 在结点 p 之前插入新结点(O(1),利用 p->prior)
删除 删除给定结点 p(O(1),直接改前后指针并释放)
遍历打印 正向(next)与反向(prior)两种遍历

核心技巧:双链表的插入/删除关键都是「先改前驱的 next 和 后继的 prior」,再修改 p 自身的指针。注意断开顺序,防止丢失结点。

复杂度分析

操作 时间复杂度 空间复杂度 原因
初始化 O(1) O(1) 只创建头结点
判空 O(1) O(1) 比较头结点指针
按位查找 O(n) O(1) 不能随机存取,需从头遍历
给定结点 p 后插 O(1) O(1) 只需修改 p、p->next、新结点共 4 个指针
给定结点 p 前插 O(1) O(1) 用 p->prior 直接得到前驱,无需查找
给定结点 p 删除 O(1) O(1) 直接连接 p->prior 与 p->next
正向 / 反向遍历 O(n) O(1) 各扫描一遍

相比单链表:双链表把「前插」和「删除已知结点」从 O(n) 降到了 O(1),这是多存一个 prior 指针换来的时间收益。

语言实现

C

#include <stdio.h>
#include <stdlib.h>

typedef struct DNode {
    int data;
    struct DNode *prior;   // 前驱指针
    struct DNode *next;    // 后继指针
} DNode, *DLinkList;

// 初始化:创建带头结点的空双链表
DLinkList initList(void) {
    DLinkList L = (DNode *)malloc(sizeof(DNode));
    if (L == NULL) exit(1);
    L->prior = NULL;
    L->next = NULL;
    return L;
}

int isEmpty(DLinkList L) {
    return L->next == NULL;
}

// 后插:在结点 p 之后插入新结点(O(1))
void insertAfter(DNode *p, int x) {
    DNode *s = (DNode *)malloc(sizeof(DNode));
    if (s == NULL) exit(1);
    s->data = x;
    s->prior = p;
    s->next = p->next;
    if (p->next != NULL)
        p->next->prior = s;   // 让原后继的前驱指向 s
    p->next = s;
}

// 前插:在结点 p 之前插入新结点(O(1))
void insertBefore(DNode *p, int x) {
    DNode *s = (DNode *)malloc(sizeof(DNode));
    if (s == NULL) exit(1);
    s->data = x;
    s->prior = p->prior;      // 新结点的前驱 = p 的前驱
    s->next = p;
    if (p->prior != NULL)
        p->prior->next = s;   // 让 p 的原前驱的后继指向 s
    p->prior = s;
}

// 删除结点 p(O(1)),用 e 带回其值;头结点不可删
int deleteNode(DLinkList L, DNode *p, int *e) {
    if (p == L) return 0;            // 不能删头结点
    *e = p->data;
    p->prior->next = p->next;        // 前驱的后继跳过 p
    if (p->next != NULL)
        p->next->prior = p->prior;   // 后继的前驱跳过 p
    free(p);
    return 1;
}

// 按位查找:返回第 i 个结点(i 从 1 开始),无则 NULL
DNode *getNode(DLinkList L, int i) {
    if (i < 1) return NULL;
    DNode *p = L->next;
    int j = 1;
    while (p && j < i) { p = p->next; j++; }
    return p;
}

// 按值查找:返回第一个值为 x 的结点
DNode *locateNode(DLinkList L, int x) {
    DNode *p = L->next;
    while (p && p->data != x) p = p->next;
    return p;
}

// 正向遍历
void printForward(DLinkList L) {
    if (isEmpty(L)) { printf("空链表\n"); return; }
    DNode *p = L->next;
    while (p) {
        printf("%d", p->data);
        if (p->next) printf(" <-> ");
        p = p->next;
    }
    printf("\n");
}

// 反向遍历(利用 prior)
void printBackward(DLinkList L) {
    if (isEmpty(L)) { printf("空链表\n"); return; }
    DNode *p = L->next;
    while (p->next) p = p->next;   // 走到表尾
    while (p != L) {               // 直到头结点
        printf("%d", p->data);
        if (p->prior != L) printf(" <-> ");
        p = p->prior;
    }
    printf(" (反向)\n");
}

int main(void) {
    DLinkList L = initList();
    printf("初始状态: ");
    printForward(L);
    printf("判空=%d\n", isEmpty(L));

    // 后插建立 10 20 30(始终插在末尾之后)
    DNode *tail = L;
    int vals[] = {10, 20, 30};
    for (int i = 0; i < 3; i++) {
        insertAfter(tail, vals[i]);
        tail = tail->next;
    }
    insertAfter(getNode(L, 2), 15);   // 在 20 之后后插 15(后插演示)
    DNode *p = locateNode(L, 20);
    insertBefore(p, 18);              // 在 20 之前前插 18(前插演示)
    printForward(L);
    printBackward(L);

    p = getNode(L, 2);                // 按位查找
    printf("第2个结点值=%d\n", p->data);

    int e;
    p = locateNode(L, 15);            // 删除值为 15 的结点
    if (p && deleteNode(L, p, &e))
        printf("删除元素=%d\n", e);
    printForward(L);

    // 释放所有结点
    DNode *q = L;
    while (q) {
        DNode *t = q->next;
        free(q);
        q = t;
    }
    return 0;
}

C++

#include <iostream>
using namespace std;

struct DNode {
    int data;
    DNode *prior;
    DNode *next;
    DNode(int d) : data(d), prior(nullptr), next(nullptr) {}
};

class DLinkList {
private:
    DNode *head;   // 头结点

    // 私有:获取表尾结点(用于尾插建立)
    DNode *getTail() const {
        DNode *p = head;
        while (p->next) p = p->next;
        return p;
    }

public:
    DLinkList() { head = new DNode(0); }

    ~DLinkList() {
        DNode *p = head;
        while (p) {
            DNode *t = p->next;
            delete p;
            p = t;
        }
    }

    bool isEmpty() const { return head->next == nullptr; }

    // 尾插:在表尾之后插入(建立用)
    void insertTail(int x) {
        DNode *tail = getTail();
        DNode *s = new DNode(x);
        s->prior = tail;
        tail->next = s;
    }

    // 后插:在给定结点 p 之后插入(O(1))
    void insertAfter(DNode *p, int x) {
        DNode *s = new DNode(x);
        s->prior = p;
        s->next = p->next;
        if (p->next) p->next->prior = s;
        p->next = s;
    }

    // 前插:在给定结点 p 之前插入(O(1))
    void insertBefore(DNode *p, int x) {
        DNode *s = new DNode(x);
        s->prior = p->prior;
        s->next = p;
        if (p->prior) p->prior->next = s;
        p->prior = s;
    }

    // 删除给定结点 p(O(1)),返回其值
    int deleteNode(DNode *p) {
        int e = p->data;
        p->prior->next = p->next;
        if (p->next) p->next->prior = p->prior;
        delete p;
        return e;
    }

    DNode *getNode(int i) const {
        if (i < 1) return nullptr;
        DNode *p = head->next;
        int j = 1;
        while (p && j < i) { p = p->next; j++; }
        return p;
    }

    DNode *locateNode(int x) const {
        DNode *p = head->next;
        while (p && p->data != x) p = p->next;
        return p;
    }

    void printForward() const {
        if (isEmpty()) { cout << "空链表" << endl; return; }
        DNode *p = head->next;
        while (p) {
            cout << p->data;
            if (p->next) cout << " <-> ";
            p = p->next;
        }
        cout << endl;
    }

    void printBackward() const {
        if (isEmpty()) { cout << "空链表" << endl; return; }
        DNode *p = head->next;
        while (p->next) p = p->next;
        while (p != head) {
            cout << p->data;
            if (p->prior != head) cout << " <-> ";
            p = p->prior;
        }
        cout << " (反向)" << endl;
    }
};

int main() {
    DLinkList L;
    cout << "初始状态: ";
    L.printForward();
    cout << "判空=" << L.isEmpty() << endl;

    // 尾插建立 10 20 30
    L.insertTail(10);
    L.insertTail(20);
    L.insertTail(30);

    L.insertAfter(L.getNode(2), 15);  // 在 20 之后后插 15
    L.insertBefore(L.locateNode(20), 18);  // 在 20 之前前插 18
    L.printForward();
    L.printBackward();

    DNode *p = L.getNode(2);          // 按位查找
    cout << "第2个结点值=" << p->data << endl;

    p = L.locateNode(15);             // 按值查找并删除
    cout << "删除元素=" << L.deleteNode(p) << endl;
    L.printForward();
    return 0;
}

Java

public class DLinkListDemo {
    // 双链表结点
    static class DNode {
        int data;
        DNode prior;
        DNode next;
        DNode(int d) { data = d; }
    }

    static class DLinkList {
        private final DNode head;   // 头结点

        DLinkList() { head = new DNode(0); }

        boolean isEmpty() { return head.next == null; }

        // 尾插(建立用)
        void insertTail(int x) {
            DNode tail = head;
            while (tail.next != null) tail = tail.next;
            DNode s = new DNode(x);
            s.prior = tail;
            tail.next = s;
        }

        // 后插:在 p 之后插入(O(1))
        void insertAfter(DNode p, int x) {
            DNode s = new DNode(x);
            s.prior = p;
            s.next = p.next;
            if (p.next != null) p.next.prior = s;
            p.next = s;
        }

        // 前插:在 p 之前插入(O(1))
        void insertBefore(DNode p, int x) {
            DNode s = new DNode(x);
            s.prior = p.prior;
            s.next = p;
            if (p.prior != null) p.prior.next = s;
            p.prior = s;
        }

        // 删除给定结点 p(O(1))
        int deleteNode(DNode p) {
            int e = p.data;
            p.prior.next = p.next;
            if (p.next != null) p.next.prior = p.prior;
            return e;
        }

        DNode getNode(int i) {
            if (i < 1) return null;
            DNode p = head.next;
            int j = 1;
            while (p != null && j < i) { p = p.next; j++; }
            return p;
        }

        DNode locateNode(int x) {
            DNode p = head.next;
            while (p != null && p.data != x) p = p.next;
            return p;
        }

        void printForward() {
            if (isEmpty()) { System.out.println("空链表"); return; }
            DNode p = head.next;
            while (p != null) {
                System.out.print(p.data);
                if (p.next != null) System.out.print(" <-> ");
                p = p.next;
            }
            System.out.println();
        }

        void printBackward() {
            if (isEmpty()) { System.out.println("空链表"); return; }
            DNode p = head.next;
            while (p.next != null) p = p.next;
            while (p != head) {
                System.out.print(p.data);
                if (p.prior != head) System.out.print(" <-> ");
                p = p.prior;
            }
            System.out.println(" (反向)");
        }
    }

    public static void main(String[] args) {
        DLinkList L = new DLinkList();
        System.out.print("初始状态: ");
        L.printForward();
        System.out.println("判空=" + L.isEmpty());

        L.insertTail(10);
        L.insertTail(20);
        L.insertTail(30);

        L.insertAfter(L.getNode(2), 15);          // 后插演示
        L.insertBefore(L.locateNode(20), 18);     // 前插演示
        L.printForward();
        L.printBackward();

        DNode p = L.getNode(2);                   // 按位查找
        System.out.println("第2个结点值=" + p.data);

        p = L.locateNode(15);                     // 按值查找并删除
        System.out.println("删除元素=" + L.deleteNode(p));
        L.printForward();
    }
}

Python

class DNode:
    """双链表结点"""

    def __init__(self, data):
        self.data = data
        self.prior = None
        self.next = None


class DLinkList:
    """带头结点的双链表"""

    def __init__(self):
        # 初始化:创建头结点
        self._head = DNode(None)

    def is_empty(self) -> bool:
        return self._head.next is None

    def insert_tail(self, x) -> None:
        """尾插(建立链表用)"""
        tail = self._head
        while tail.next is not None:
            tail = tail.next
        s = DNode(x)
        s.prior = tail
        tail.next = s

    def insert_after(self, p: DNode, x) -> None:
        """后插:在结点 p 之后插入(O(1))"""
        s = DNode(x)
        s.prior = p
        s.next = p.next
        if p.next is not None:
            p.next.prior = s
        p.next = s

    def insert_before(self, p: DNode, x) -> None:
        """前插:在结点 p 之前插入(O(1))"""
        s = DNode(x)
        s.prior = p.prior
        s.next = p
        if p.prior is not None:
            p.prior.next = s
        p.prior = s

    def delete_node(self, p: DNode):
        """删除给定结点 p(O(1)),返回其值"""
        e = p.data
        p.prior.next = p.next
        if p.next is not None:
            p.next.prior = p.prior
        return e

    def get_node(self, i: int):
        """按位查找:返回第 i 个结点(i 从 1 开始)"""
        if i < 1:
            return None
        p = self._head.next
        j = 1
        while p is not None and j < i:
            p = p.next
            j += 1
        return p

    def locate_node(self, x):
        """按值查找:返回第一个值为 x 的结点"""
        p = self._head.next
        while p is not None and p.data != x:
            p = p.next
        return p

    def print_forward(self):
        if self.is_empty():
            print("空链表")
            return
        vals = []
        p = self._head.next
        while p is not None:
            vals.append(str(p.data))
            p = p.next
        print(" <-> ".join(vals))

    def print_backward(self):
        if self.is_empty():
            print("空链表")
            return
        vals = []
        p = self._head.next
        while p.next is not None:
            p = p.next          # 走到表尾
        while p is not self._head:
            vals.append(str(p.data))
            p = p.prior
        print(" <-> ".join(vals), "(反向)")


if __name__ == "__main__":
    L = DLinkList()
    print("初始状态:", end=" ")
    L.print_forward()
    print("判空 =", L.is_empty())

    L.insert_tail(10)
    L.insert_tail(20)
    L.insert_tail(30)

    L.insert_after(L.get_node(2), 15)          # 后插演示
    L.insert_before(L.locate_node(20), 18)     # 前插演示
    L.print_forward()
    L.print_backward()

    p = L.get_node(2)                          # 按位查找
    print("第2个结点值 =", p.data)

    p = L.locate_node(15)                      # 按值查找并删除
    print("删除元素 =", L.delete_node(p))
    L.print_forward()