循环双向链表的概念

循环双向链表是一种链表数据结构,其中每个节点包含指向前驱节点和后继节点的指针。与普通双向链表不同的是,循环双向链表的头节点的前驱指针指向尾节点,而尾节点的后继指针指向头节点,形成一个闭环。这种结构使得从任意节点出发都可以遍历整个链表,无论是向前还是向后。

节点结构体定义

首先,让我们定义循环双向链表的节点结构:

// 循环双向链表的节点结构体
struct Node {
    int data;        // 节点存储的数据
    Node* prev;      // 指向前驱节点的指针
    Node* next;      // 指向后继节点的指针
    
    // 构造函数,方便创建节点时初始化数据
    Node(int value) : data(value), prev(nullptr), next(nullptr) {}
};

循环双向链表的基本操作

下面是实现循环双向链表的完整代码,包含了创建、插入、删除和遍历等基本操作:

#include <iostream>

// 循环双向链表的节点结构体
struct Node {
    int data;        // 节点存储的数据
    Node* prev;      // 指向前驱节点的指针
    Node* next;      // 指向后继节点的指针
    
    // 构造函数,方便创建节点时初始化数据
    Node(int value) : data(value), prev(nullptr), next(nullptr) {}
};

// 循环双向链表类
class CircularDoublyLinkedList {
private:
    Node* head;      // 头节点指针

public:
    // 构造函数,初始化空链表
    CircularDoublyLinkedList() : head(nullptr) {}
    
    // 析构函数,释放链表内存
    ~CircularDoublyLinkedList() {
        clear();
    }
    
    // 判断链表是否为空
    bool isEmpty() const {
        return head == nullptr;
    }
    
    // 在链表头部插入新节点
    void insertAtHead(int value) {
        Node* newNode = new Node(value);
        
        if (isEmpty()) {
            // 空链表的情况,新节点既是头节点也是尾节点
            head = newNode;
            newNode->next = newNode;  // 自己指向自己形成环
            newNode->prev = newNode;
        } else {
            // 非空链表,插入到头部
            Node* tail = head->prev;  // 找到尾节点
            
            newNode->next = head;     // 新节点的后继指向原头节点
            newNode->prev = tail;     // 新节点的前驱指向尾节点
            
            tail->next = newNode;     // 尾节点的后继指向新节点
            head->prev = newNode;     // 原头节点的前驱指向新节点
            
            head = newNode;           // 更新头节点为新节点
        }
    }
    
    // 在链表尾部插入新节点
    void insertAtTail(int value) {
        if (isEmpty()) {
            insertAtHead(value);  // 空链表时,尾部插入等同于头部插入
            return;
        }
        
        // 找到尾节点(头节点的前驱)
        Node* tail = head->prev;
        Node* newNode = new Node(value);
        
        newNode->next = head;     // 新节点的后继指向头节点
        newNode->prev = tail;     // 新节点的前驱指向尾节点
        
        tail->next = newNode;     // 尾节点的后继指向新节点
        head->prev = newNode;     // 头节点的前驱指向新节点
    }
    
    // 删除链表头部节点
    void deleteAtHead() {
        if (isEmpty()) return;  // 空链表不做处理
        
        if (head->next == head) {
            // 只有一个节点的情况
            delete head;
            head = nullptr;
        } else {
            // 多个节点的情况
            Node* tail = head->prev;  // 找到尾节点
            Node* newHead = head->next;  // 新头节点
            
            tail->next = newHead;     // 尾节点的后继指向新头节点
            newHead->prev = tail;     // 新头节点的前驱指向尾节点
            
            delete head;              // 释放原头节点内存
            head = newHead;           // 更新头节点
        }
    }
    
    // 删除链表尾部节点
    void deleteAtTail() {
        if (isEmpty()) return;  // 空链表不做处理
        
        if (head->next == head) {
            // 只有一个节点的情况
            delete head;
            head = nullptr;
        } else {
            // 多个节点的情况
            Node* tail = head->prev;  // 找到尾节点
            Node* newTail = tail->prev;  // 新尾节点
            
            newTail->next = head;     // 新尾节点的后继指向头节点
            head->prev = newTail;     // 头节点的前驱指向新尾节点
            
            delete tail;              // 释放原尾节点内存
        }
    }
    
    // 遍历链表(正向)
    void traverseForward() const {
        if (isEmpty()) {
            std::cout << "链表为空" << std::endl;
            return;
        }
        
        Node* current = head;
        do {
            std::cout << current->data << " ";
            current = current->next;
        } while (current != head);  // 回到头节点时结束遍历
        
        std::cout << std::endl;
    }
    
    // 遍历链表(反向)
    void traverseBackward() const {
        if (isEmpty()) {
            std::cout << "链表为空" << std::endl;
            return;
        }
        
        Node* current = head->prev;  // 从尾节点开始
        do {
            std::cout << current->data << " ";
            current = current->prev;
        } while (current != head->prev);  // 回到尾节点时结束遍历
        
        std::cout << std::endl;
    }
    
    // 清空链表
    void clear() {
        if (isEmpty()) return;
        
        Node* current = head;
        Node* nextNode;
        
        // 遍历链表,逐个删除节点
        do {
            nextNode = current->next;
            delete current;
            current = nextNode;
        } while (current != head);
        
        head = nullptr;  // 链表清空后,头节点置空
    }
};

// 主函数示例
int main() {
    CircularDoublyLinkedList list;
    
    // 插入节点
    list.insertAtHead(10);
    list.insertAtTail(20);
    list.insertAtHead(5);
    list.insertAtTail(30);
    
    // 遍历链表
    std::cout << "正向遍历: ";
    list.traverseForward();  // 输出: 5 10 20 30
    
    std::cout << "反向遍历: ";
    list.traverseBackward();  // 输出: 30 20 10 5
    
    // 删除节点
    list.deleteAtHead();
    list.deleteAtTail();
    
    std::cout << "删除后的正向遍历: ";
    list.traverseForward();  // 输出: 10 20
    
    return 0;
}

代码解析

  1. 节点结构体Node 结构体包含三个成员:数据 data、指向前驱节点的指针 prev 和指向后继节点的指针 next

  2. 链表类CircularDoublyLinkedList 类封装了链表的操作,包括初始化、插入、删除和遍历等。

  3. 插入操作

    • insertAtHead:在链表头部插入新节点。
    • insertAtTail:在链表尾部插入新节点。
  4. 删除操作

    • deleteAtHead:删除链表头部节点。
    • deleteAtTail:删除链表尾部节点。
  5. 遍历操作

    • traverseForward:从头部开始正向遍历链表。
    • traverseBackward:从尾部开始反向遍历链表。
  6. 清空操作clear 方法释放链表中所有节点的内存,并将头节点置空。

通过这种方式,你可以实现一个完整的循环双向链表,并进行各种操作。循环双向链表的优势在于可以双向遍历,且在表头和表尾插入删除操作的时间复杂度都是 O(1)。

1 条评论

  • @ 2025-6-9 21:15:47

    通俗易懂的 C++ 循环双向链表教程,包含完整的代码示例、注释和结构体 + 指针的使用方式。适合初学者学习。


    📘 什么是循环双向链表?

    • 双向链表(Doubly Linked List):每个节点有两个指针,prev 指向前一个节点,next 指向后一个节点。
    • 循环链表(Circular Linked List):尾节点的 next 指向头节点,头节点的 prev 指向尾节点,形成一个闭环。

    所以,循环双向链表就是首尾相连的双向链表。


    🧱 定义节点结构体

    #include <iostream>
    using namespace std;
    
    // 定义节点结构体
    struct Node {
        int data;           // 数据域
        Node* prev;         // 指向前一个节点的指针
        Node* next;         // 指向下一个节点的指针
    };
    

    🔗 创建新节点函数

    // 创建一个新节点
    Node* createNode(int value) {
        Node* newNode = new Node();     // 动态分配内存
        newNode->data = value;
        newNode->prev = nullptr;
        newNode->next = nullptr;
        return newNode;
    }
    

    ➕ 插入节点操作

    在链表末尾插入节点:

    // 在链表末尾插入节点
    void insertAtEnd(Node*& head, int value) {
        Node* newNode = createNode(value);
    
        if (head == nullptr) { // 如果链表为空
            head = newNode;
            newNode->next = head;   // 自己指向自己
            newNode->prev = head;
        } else {
            Node* tail = head->prev; // 获取尾节点
    
            // 插入新节点到尾部
            tail->next = newNode;
            newNode->prev = tail;
            newNode->next = head;
            head->prev = newNode;
        }
    }
    

    📋 打印链表内容(正序)

    // 打印链表内容(正向)
    void printListForward(Node* head) {
        if (head == nullptr) {
            cout << "链表为空" << endl;
            return;
        }
    
        Node* current = head;
        do {
            cout << current->data << " <-> ";
            current = current->next;
        } while (current != head); // 当回到起点时结束循环
    
        cout << "(回到头部)" << endl;
    }
    

    📄 打印链表内容(逆序)

    // 打印链表内容(反向)
    void printListBackward(Node* head) {
        if (head == nullptr) {
            cout << "链表为空" << endl;
            return;
        }
    
        Node* tail = head->prev; // 获取尾节点
        Node* current = tail;
        do {
            cout << current->data << " <-> ";
            current = current->prev;
        } while (current != tail); // 当回到尾部时结束循环
    
        cout << "(回到尾部)" << endl;
    }
    

    🧹 删除指定值的节点

    // 删除指定值的节点
    void deleteNode(Node*& head, int value) {
        if (head == nullptr) {
            cout << "链表为空,无法删除" << endl;
            return;
        }
    
        Node* current = head;
        bool found = false;
    
        // 查找要删除的节点
        do {
            if (current->data == value) {
                found = true;
                break;
            }
            current = current->next;
        } while (current != head);
    
        if (!found) {
            cout << "未找到值为 " << value << " 的节点" << endl;
            return;
        }
    
        // 只有一个节点的情况
        if (current->next == head && current->prev == head) {
            delete current;
            head = nullptr;
            return;
        }
    
        // 更新前后节点的指针
        current->prev->next = current->next;
        current->next->prev = current->prev;
    
        // 如果删除的是头节点,更新 head
        if (current == head) {
            head = current->next;
        }
    
        delete current;
    }
    

    🧪 主函数测试代码

    int main() {
        Node* head = nullptr; // 初始化空链表
    
        insertAtEnd(head, 10);
        insertAtEnd(head, 20);
        insertAtEnd(head, 30);
    
        cout << "正向打印链表:" << endl;
        printListForward(head);
    
        cout << "\n反向打印链表:" << endl;
        printListBackward(head);
    
        cout << "\n删除值为 20 的节点:" << endl;
        deleteNode(head, 20);
        printListForward(head);
    
        return 0;
    }
    

    ✅ 输出结果示例

    正向打印链表:
    10 <-> 20 <-> 30 <-> (回到头部)
    
    反向打印链表:
    30 <-> 20 <-> 10 <-> (回到尾部)
    
    删除值为 20 的节点:
    10 <-> 30 <-> (回到头部)
    

    📝 小结

    操作 时间复杂度
    插入(尾部) O(1)
    删除 O(n)(查找)+ O(1)(连接)
    正向遍历 O(n)
    反向遍历

    🎯 学习建议

    • 先理解普通双向链表的实现。
    • 再掌握循环结构带来的变化(头尾相连)。
    • 熟悉指针的操作是关键,尤其是 prevnext 的维护。

    如果你还想添加更多功能(比如在头部插入、按位置删除等),我也可以帮你扩展!欢迎继续提问 😊

    • 1