C++ 数据结构 单链表、双链表、循环单向链表、循环双向链表

单链表和双链表是常用的线性数据结构,它们都有各自的优缺点和适用场景。以下是它们的基本概念、实现示例以及各自的特点。

单链表

概念

单链表是由一系列节点组成的线性数据结构,每个节点包含数据和一个指向下一个节点的指针。最后一个节点的指针指向nullptr。

特点
  • 插入和删除效率:在已知位置的情况下,插入和删除操作的时间复杂度为O(1),但查找时间复杂度为O(n)。
  • 内存占用:每个节点只存储一个指针,相对较省内存。
  • 单向性:只能向一个方向遍历。
操作示意

插入操作:

  • 在链表头插入 (插入1):

    cpp 复制代码
    [1] -> nullptr

    在尾部插入 (插入2):

cpp 复制代码
[1] -> [2] -> nullptr

在中间插入 (插入3,索引1):

cpp 复制代码
[1] -> [3] -> [2] -> nullptr

删除操作:

  • 删除索引1的元素 (删除3):

    cpp 复制代码
    [1] -> [2] -> nullptr
    cpp 复制代码
    #include <iostream>
    
    // 单链表节点类
    template <typename T>
    class Node {
    public:
        T data;        // 节点的数据部分
        Node* next;    // 指向下一个节点的指针
    
        Node(const T& value) : data(value), next(nullptr) {} // 构造函数
    };
    
    // 单链表类
    template <typename T>
    class SinglyLinkedList {
    public:
        SinglyLinkedList() : head(nullptr) {} // 构造函数,初始化头指针为空
    
        ~SinglyLinkedList() {
            while (head) {
                remove(0); // 删除所有节点
            }
        }
    
        // 在指定索引处插入元素
        void insert(int index, const T& value) {
            if (index < 0) return; // 索引无效
    
            Node<T>* newNode = new Node<T>(value); // 创建新节点
            if (index == 0) {
                // 插入到头部
                newNode->next = head; // 新节点指向当前头节点
                head = newNode; // 更新头指针为新节点
                return;
            }
    
            Node<T>* current = head;
            // 找到插入位置的前一个节点
            for (int i = 0; i < index - 1 && current != nullptr; ++i) {
                current = current->next; // 移动到下一个节点
            }
    
            if (current) {
                // 在中间插入
                newNode->next = current->next; // 新节点指向插入位置的下一个节点
                current->next = newNode; // 前一个节点指向新节点
            } else {
                delete newNode; // 超出范围,释放内存
            }
        }
    
        // 删除指定索引的元素
        bool remove(int index) {
            if (index < 0 || head == nullptr) return false; // 索引无效或链表为空
    
            Node<T>* temp = head; // 从头节点开始
    
            if (index == 0) {
                // 删除头节点
                head = temp->next; // 更新头指针为下一个节点
                delete temp; // 释放内存
                return true;
            }
    
            // 找到要删除节点的前一个节点
            for (int i = 0; i < index - 1 && temp != nullptr; ++i) {
                temp = temp->next; // 移动到下一个节点
            }
    
            if (temp == nullptr || temp->next == nullptr) return false; // 超出范围
    
            // 删除目标节点
            Node<T>* nodeToDelete = temp->next; // 保存要删除的节点
            temp->next = nodeToDelete->next; // 前一个节点指向目标节点的下一个节点
            delete nodeToDelete; // 释放内存
    
            return true; // 删除成功
        }
    
        // 打印链表中的所有元素
        void print() const {
            Node<T>* current = head; // 从头节点开始
            while (current) {
                std::cout << current->data << " "; // 输出节点数据
                current = current->next; // 移动到下一个节点
            }
            std::cout << std::endl; // 换行
        }
    
    private:
        Node<T>* head; // 链表的头指针
    };
    
    int main() {
        SinglyLinkedList<int> list; // 创建单链表实例
        list.insert(0, 1); // 在索引0插入1
        list.insert(1, 2); // 在索引1插入2
        list.insert(1, 3); // 在索引1插入3
        list.print(); // 输出: 1 3 2
    
        list.remove(1); // 删除索引1的元素
        list.print(); // 输出: 1 2
    
        return 0;
    }

    双链表

    概念

    双链表是由一系列节点组成的线性数据结构,每个节点包含数据、一个指向下一个节点的指针和一个指向前一个节点的指针。第一个节点的前指针和最后一个节点的后指针均指向nullptr。

    特点
  • 双向遍历:可以从前向后和从后向前遍历。

  • 插入和删除效率:与单链表相同,但双链表在删除时可以更方便地找到前驱节点。

  • 内存占用:每个节点需存储两个指针,因此占用更多内存。

操作示意

插入操作:

  • 在链表头插入 (插入1):
cpp 复制代码
[1] <-> nullptr

在尾部插入 (插入2):

cpp 复制代码
[1] <-> [2] <-> nullptr

在中间插入 (插入3,索引1):

cpp 复制代码
[1] <-> [3] <-> [2] <-> nullptr

删除操作:

  • 删除索引1的元素 (删除3):
cpp 复制代码
[1] <-> [2] <-> nullptr
cpp 复制代码
#include <iostream>

// 双链表节点类
template <typename T>
class DNode {
public:
    T data;        // 节点的数据部分
    DNode* next;   // 指向下一个节点的指针
    DNode* prev;   // 指向前一个节点的指针

    DNode(const T& value) : data(value), next(nullptr), prev(nullptr) {} // 构造函数
};

// 双链表类
template <typename T>
class DoublyLinkedList {
public:
    DoublyLinkedList() : head(nullptr), tail(nullptr) {} // 构造函数,初始化头和尾指针为空

    ~DoublyLinkedList() {
        while (head) {
            remove(0); // 删除所有节点
        }
    }

    // 在指定索引处插入元素
    void insert(int index, const T& value) {
        if (index < 0) return; // 索引无效

        DNode<T>* newNode = new DNode<T>(value); // 创建新节点
        if (index == 0) {
            // 插入到头部
            newNode->next = head; // 新节点指向当前头节点
            if (head) head->prev = newNode; // 旧头节点的前驱指向新节点
            head = newNode; // 更新头指针为新节点
            if (!tail) tail = newNode; // 如果是第一个节点,更新尾指针
            return;
        }

        DNode<T>* current = head;
        // 找到插入位置的前一个节点
        for (int i = 0; i < index - 1 && current != nullptr; ++i) {
            current = current->next; // 移动到下一个节点
        }

        if (current) {
            // 在中间插入
            newNode->next = current->next; // 新节点指向插入位置的下一个节点
            newNode->prev = current; // 新节点的前驱指向当前节点
            if (current->next) current->next->prev = newNode; // 旧下一个节点的前驱指向新节点
            current->next = newNode; // 当前节点指向新节点
            if (!newNode->next) tail = newNode; // 如果是最后一个节点,更新尾指针
        } else {
            delete newNode; // 超出范围,释放内存
        }
    }

    // 删除指定索引的元素
    bool remove(int index) {
        if (index < 0 || head == nullptr) return false; // 索引无效或链表为空

        DNode<T>* temp = head; // 从头节点开始

        if (index == 0) {
            // 删除头节点
            head = temp->next; // 更新头指针为下一个节点
            if (head) head->prev = nullptr; // 更新新头节点的前驱指针
            else tail = nullptr; // 如果链表空了,更新尾指针
            delete temp; // 释放内存
            return true;
        }

        // 找到要删除节点的前一个节点
        for (int i = 0; i < index && temp != nullptr; ++i) {
            temp = temp->next; // 移动到下一个节点
        }

        if (temp == nullptr) return false; // 超出范围

        // 删除目标节点
        if (temp->prev) temp->prev->next = temp->next; // 前一个节点指向目标节点的下一个节点
        if (temp->next) temp->next->prev = temp->prev; // 下一个节点的前驱指向前一个节点
        if (temp == tail) tail = temp->prev; // 如果删除的是尾节点,更新尾指针

        delete temp; // 释放内存
        return true; // 删除成功
    }

    // 打印链表中的所有元素
    void print() const {
        DNode<T>* current = head; // 从头节点开始
        while (current) {
            std::cout << current->data << " "; // 输出节点数据
            current = current->next; // 移动到下一个节点
        }
        std::cout << std::endl; // 换行
    }

private:
    DNode<T>* head; // 链表的头指针
    DNode<T>* tail; // 链表的尾指针
};

int main() {
    DoublyLinkedList<int> list; // 创建双链表实例
    list.insert(0, 1); // 在索引0插入1
    list.insert(1, 2); // 在索引1插入2
    list.insert(1, 3); // 在索引1插入3
    list.print(); // 输出: 1 3 2

    list.remove(1); // 删除索引1的元素
    list.print(); // 输出: 1 2

    return 0;
}

单循环链表 (Circular Singly Linked List)

特性:

  1. 循环性 :最后一个节点的 next 指针指向头节点,形成一个环形结构。
  2. 单向遍历:只能从头节点开始向前遍历,无法反向访问。
  3. 动态大小:可以动态增加或减少节点,内存利用灵活。
  4. 不易删除:无法直接访问前一个节点,因此删除操作需要遍历链表。

操作顺序:

  1. 插入 :
    • 创建新节点。
    • 如果链表为空,设置头节点为新节点,next 指向自身。
    • 如果不为空,找到尾节点,将新节点插入到头前。
    • 更新尾节点的 next 指针指向新节点。
  2. 删除 :
    • 遍历链表找到目标节点及其前驱节点。
    • 调整前驱节点的 next 指向目标节点的 next,以跳过目标节点。
    • 释放目标节点的内存。
操作示意
  1. 插入操作:

    • 在链表头插入 (插入3) :

      cpp 复制代码
      [3] <-> [3]

      在链表头插入 (插入2):

      cpp 复制代码
      [2] <-> [3] <-> [2]

      在链表头插入 (插入1):

      cpp 复制代码
      [1] <-> [2] <-> [3] <-> [1]

删除操作:

操作顺序:

  • 删除值为2的元素 :

    cpp 复制代码
    [1] <-> [3] <-> [1]
    cpp 复制代码
    #include <iostream>
    
    // 循环双链表节点类
    template <typename T>
    class DNode {
    public:
        T data;        // 节点的数据部分
        DNode* next;   // 指向下一个节点的指针
        DNode* prev;   // 指向前一个节点的指针
    
        DNode(const T& value) : data(value), next(nullptr), prev(nullptr) {} // 构造函数
    };
    
    // 循环双链表类
    template <typename T>
    class CircularDoublyLinkedList {
    public:
        CircularDoublyLinkedList() : head(nullptr) {} // 构造函数,初始化头指针为空
    
        ~CircularDoublyLinkedList() {
            if (head) {
                DNode<T>* current = head;
                DNode<T>* nextNode;
                do {
                    nextNode = current->next; // 保存下一个节点
                    delete current; // 删除当前节点
                    current = nextNode; // 移动到下一个节点
                } while (current != head); // 直到回到头节点
            }
        }
    
        // 在链表头插入元素
        void insertAtHead(const T& value) {
            DNode<T>* newNode = new DNode<T>(value); // 创建新节点
            if (!head) {
                head = newNode; // 如果链表为空,头指针指向新节点
                head->next = head; // 新节点指向自己
                head->prev = head; // 新节点的前驱指向自己
            } else {
                newNode->next = head; // 新节点指向当前头节点
                newNode->prev = head->prev; // 新节点的前驱指向尾节点
                head->prev->next = newNode; // 尾节点指向新节点
                head->prev = newNode; // 新节点成为新的头节点
                head = newNode; // 更新头指针为新节点
            }
        }
    
        // 删除指定值的元素
        bool remove(const T& value) {
            if (!head) return false; // 链表为空
    
            DNode<T>* current = head;
            do {
                if (current->data == value) {
                    if (current == head) {
                        // 删除头节点
                        if (current->next == head) {
                            delete current; // 只有一个节点
                            head = nullptr; // 更新头指针为空
                        } else {
                            head->prev->next = head->next; // 尾节点指向新头节点
                            head->next->prev = head->prev; // 新头节点的前驱指向尾节点
                            DNode<T>* temp = head; // 保存当前头节点
                            head = head->next; // 更新头指针
                            delete temp; // 删除旧头节点
                        }
                    } else {
                        // 删除中间或尾节点
                        current->prev->next = current->next; // 前一个节点跳过当前节点
                        current->next->prev = current->prev; // 后一个节点跳过当前节点
                        delete current; // 删除当前节点
                    }
                    return true; // 删除成功
                }
                current = current->next; // 移动到下一个节点
            } while (current != head); // 直到回到头节点
    
            return false; // 未找到元素
        }
    
        // 打印链表中的所有元素
        void print() const {
            if (!head) return; // 链表为空
    
            DNode<T>* current = head; // 从头节点开始
            do {
                std::cout << current->data << " "; // 输出节点数据
                current = current->next; // 移动到下一个节点
            } while (current != head); // 直到回到头节点
            std::cout << std::endl; // 换行
        }
    
    private:
        DNode<T>* head; // 链表的头指针
    };
    
    int main() {
        CircularDoublyLinkedList<int> list; // 创建循环双链表实例
        list.insertAtHead(1); // 在头插入1
        list.insertAtHead(2); // 在头插入2
        list.insertAtHead(3); // 在头插入3
        list.print(); // 输出: 3 2 1
    
        list.remove(2); // 删除值为2的节点
        list.print(); // 输出: 3 1
    
        return 0;
    }

    双循环链表 (Circular Doubly Linked List)

    特性:

  • 循环性 :尾节点的 next 指针指向头节点,头节点的 prev 指针指向尾节点,形成环形结构。

  • 双向遍历:可以从任意节点向前或向后遍历。

  • 插入 :

    • 创建新节点。
    • 如果链表为空,设置头节点为新节点,next 和 prev 均指向自身。
    • 如果不为空:
      • 更新新节点的 next 和 prev 指向相应的节点。
      • 更新相关节点的指针以保持循环结构。
  • 删除 :

    • 遍历链表找到目标节点。
    • 调整前驱节点的 next 和后继节点的 prev 指针,以跳过目标节点。
    • 释放目标节点的内存。
  • 动态大小:和单循环链表一样,动态增加或减少节点。

  • 插入和删除灵活:可以在任何位置方便地进行插入和删除操作,因为每个节点都有前驱和后继指针。

cpp 复制代码
#include <iostream>

// 循环双链表节点类
template <typename T>
class DNode {
public:
    T data;        // 节点的数据部分
    DNode* next;   // 指向下一个节点的指针
    DNode* prev;   // 指向前一个节点的指针

    DNode(const T& value) : data(value), next(nullptr), prev(nullptr) {} // 构造函数
};

// 循环双链表类
template <typename T>
class CircularDoublyLinkedList {
public:
    CircularDoublyLinkedList() : head(nullptr) {} // 构造函数,初始化头指针为空

    ~CircularDoublyLinkedList() {
        if (head) {
            DNode<T>* current = head;
            DNode<T>* nextNode;
            do {
                nextNode = current->next; // 保存下一个节点
                delete current; // 删除当前节点
                current = nextNode; // 移动到下一个节点
            } while (current != head); // 直到回到头节点
        }
    }

    // 在链表头插入元素
    void insertAtHead(const T& value) {
        DNode<T>* newNode = new DNode<T>(value); // 创建新节点
        if (!head) {
            head = newNode; // 如果链表为空,头指针指向新节点
            head->next = head; // 新节点指向自己
            head->prev = head; // 新节点的前驱指向自己
        } else {
            newNode->next = head; // 新节点指向当前头节点
            newNode->prev = head->prev; // 新节点的前驱指向尾节点
            head->prev->next = newNode; // 尾节点指向新节点
            head->prev = newNode; // 新节点成为新的头节点
            head = newNode; // 更新头指针为新节点
        }
    }

    // 删除指定值的元素
    bool remove(const T& value) {
        if (!head) return false; // 链表为空

        DNode<T>* current = head;
        do {
            if (current->data == value) {
                if (current == head) {
                    // 删除头节点
                    if (current->next == head) {
                        delete current; // 只有一个节点
                        head = nullptr; // 更新头指针为空
                    } else {
                        head->prev->next = head->next; // 尾节点指向新头节点
                        head->next->prev = head->prev; // 新头节点的前驱指向尾节点
                        DNode<T>* temp = head; // 保存当前头节点
                        head = head->next; // 更新头指针
                        delete temp; // 删除旧头节点
                    }
                } else {
                    // 删除中间或尾节点
                    current->prev->next = current->next; // 前一个节点跳过当前节点
                    current->next->prev = current->prev; // 后一个节点跳过当前节点
                    delete current; // 删除当前节点
                }
                return true; // 删除成功
            }
            current = current->next; // 移动到下一个节点
        } while (current != head); // 直到回到头节点

        return false; // 未找到元素
    }

    // 打印链表中的所有元素
    void print() const {
        if (!head) return; // 链表为空

        DNode<T>* current = head; // 从头节点开始
        do {
            std::cout << current->data << " "; // 输出节点数据
            current = current->next; // 移动到下一个节点
        } while (current != head); // 直到回到头节点
        std::cout << std::endl; // 换行
    }

private:
    DNode<T>* head; // 链表的头指针
};

int main() {
    CircularDoublyLinkedList<int> list; // 创建循环双链表实例
    list.insertAtHead(1); // 在头插入1
    list.insertAtHead(2); // 在头插入2
    list.insertAtHead(3); // 在头插入3
    list.print(); // 输出: 3 2 1

    list.remove(2); // 删除值为2的节点
    list.print(); // 输出: 3 1

    return 0;
}

总结

下面是关于单链表、双链表、单向循环链表和双向循环链表的操作顺序的详细总结。

1. 单链表 (Singly Linked List)

在头插入:

  1. 创建新节点,设置新节点的数据。
  2. 将新节点的 next 指向当前头节点。
  3. 更新头指针为新节点。

在尾插入:

  1. 创建新节点,设置新节点的数据。
  2. 如果链表为空,设置头指针为新节点,next 指向 nullptr。
  3. 如果链表不为空,遍历链表找到最后一个节点。
  4. 将最后一个节点的 next 指向新节点,并将新节点的 next 设置为 nullptr。

在中间插入:

  1. 创建新节点,设置新节点的数据。
  2. 遍历链表,找到插入位置的前一个节点。
  3. 将新节点的 next 指向前一个节点的 next。
  4. 更新前一个节点的 next 指向新节点。

删除操作:


2. 双链表 (Doubly Linked List)

删除操作:


3. 单向循环链表 (Circular Singly Linked List)

删除操作:


4. 双向循环链表 (Circular Doubly Linked List)

删除操作:

  • 删除头节点:

    1. 如果链表为空,返回失败。
    2. 保存当前头节点,更新头指针为头节点的 next。
    3. 删除保存的头节点。
  • 删除尾节点:

    1. 如果链表为空,返回失败。
    2. 如果链表只有一个节点,调用删除头节点。
    3. 遍历链表找到倒数第二个节点。
    4. 更新倒数第二个节点的 next 为 nullptr,删除尾节点。
  • 删除中间节点:

    1. 遍历链表找到目标节点及其前驱节点。
    2. 更新前驱节点的 next 指向目标节点的 next。
    3. 删除目标节点。
  • 在头插入:

    1. 创建新节点,设置新节点的数据。
    2. 如果链表为空,设置头指针为新节点,next 和 prev 均指向自身。
    3. 如果链表不为空,设置新节点的 next 指向当前头节点。
    4. 更新当前头节点的 prev 指向新节点。
    5. 新节点的 prev 指向尾节点,更新尾节点的 next 指向新节点。
    6. 更新头指针为新节点。
  • 在尾插入:

    1. 创建新节点,设置新节点的数据。
    2. 如果链表为空,调用在头插入。
    3. 如果链表不为空,遍历找到尾节点。
    4. 设置新节点的 prev 指向当前尾节点,更新尾节点的 next 指向新节点。
    5. 更新新节点的 next 指向头节点,头节点的 prev 指向新节点。
  • 在中间插入:

    1. 创建新节点,设置新节点的数据。
    2. 遍历找到插入位置的前一个节点。
    3. 更新新节点的 next 指向前一个节点的 next,并更新前一个节点的 next 指向新节点。
    4. 更新新节点的 prev 指向前一个节点。
    5. 更新新节点的 next 的 prev 指向新节点。
  • 删除头节点:

    1. 如果链表为空,返回失败。
    2. 保存当前头节点。
    3. 更新头指针为头节点的 next。
    4. 更新新头节点的 prev 指向尾节点。
    5. 删除保存的头节点。
  • 删除尾节点:

    1. 如果链表为空,返回失败。
    2. 如果链表只有一个节点,调用删除头节点。
    3. 找到倒数第二个节点,更新其 next 为 nullptr。
    4. 更新新尾节点的 next 指向头节点,头节点的 prev 指向新尾节点。
    5. 删除原尾节点。
  • 删除中间节点:

    1. 遍历链表找到目标节点。
    2. 更新目标节点的 prev 的 next 和 next 的 prev 指向相应的节点,跳过目标节点。
    3. 删除目标节点。
  • 在头插入:

    1. 创建新节点,设置数据。
    2. 如果链表为空,设置新节点的 next 指向自身,头指针指向新节点。
    3. 如果不为空,找到尾节点,更新尾节点的 next 为新节点,新节点的 next 为头节点。
    4. 更新头指针为新节点。
  • 在尾插入:

    1. 创建新节点,设置数据。
    2. 如果链表为空,调用在头插入。
    3. 如果不为空,找到尾节点,更新尾节点的 next 为新节点,新节点的 next 为头节点。
  • 在中间插入:

    1. 创建新节点,设置数据。
    2. 遍历找到插入位置的前一个节点。
    3. 更新新节点的 next 指向前一个节点的 next,并更新前一个节点的 next 指向新节点。
  • 删除头节点:

    1. 如果链表为空,返回失败。
    2. 保存当前头节点。
    3. 更新头指针为头节点的 next。
    4. 找到尾节点,更新尾节点的 next 为新头节点。
    5. 删除保存的头节点。
  • 删除尾节点:

    1. 如果链表为空,返回失败。
    2. 如果链表只有一个节点,调用删除头节点。
    3. 遍历找到倒数第二个节点,更新其 next 指向头节点,删除尾节点。
  • 删除中间节点:

    1. 遍历找到目标节点及其前驱节点。
    2. 更新前驱节点的 next 指向目标节点的 next。
    3. 删除目标节点。
  • 在头插入:

    1. 创建新节点,设置数据。
    2. 如果链表为空,设置新节点的 next 和 prev 均指向自身,头指针指向新节点。
    3. 如果不为空,更新新节点的 next 为头节点,prev 为尾节点。
    4. 更新头节点的 prev 为新节点,尾节点的 next 为新节点。
    5. 更新头指针为新节点。
  • 在尾插入:

    1. 创建新节点,设置数据。
    2. 如果链表为空,调用在头插入。
    3. 如果不为空,更新新节点的 prev 为当前尾节点,next 为头节点。
    4. 更新当前尾节点的 next 为新节点,头节点的 prev 为新节点。
  • 在中间插入:

    1. 创建新节点,设置数据。
    2. 遍历找到插入位置的前一个节点。
    3. 更新新节点的 next 和 prev 指针,并更新相关节点的指针以保持循环结构。
  • 删除头节点:

    1. 如果链表为空,返回失败。
    2. 保存当前头节点。
    3. 更新头指针为头节点的 next。
    4. 更新新头节点的 prev 指向尾节点,尾节点的 next 指向新头节点。
    5. 删除保存的头节点。
  • 删除尾节点:

    1. 如果链表为空,返回失败。
    2. 如果链表只有一个节点,调用删除头节点。
    3. 更新倒数第二个节点的 next 为头节点,头节点的 prev 指向新尾节点。
    4. 删除原尾节点。
  • 删除中间节点:

    1. 遍历链表找到目标节点。
    2. 更新目标节点的 prev 和 next 的指针以跳过目标节点。
    3. 删除目标节点。
相关推荐
程序喵大人5 小时前
【C++入门】编译链接模型 - 01 一个 C++ 程序是怎样变成可执行文件的
开发语言·c++·编译链接模型
by209997 小时前
vector 的模拟实现:扩容与边界的详细阐述(下)
c++·经验分享·vector·动态数组
余额瞒着我当琳9 小时前
红黑树的实现--红黑树的概念、规则、效率、结构、旋转、查找、验证
数据结构·c++·算法
程序员南飞9 小时前
win系统安装minio
java·数据结构·算法
青少儿编程课堂9 小时前
AC自动机多模式串匹配精讲:校园关键词巡检实战解析
c++·python·算法·bfs·信息学竞赛
longlongzihan10 小时前
LeetCode 5. 最长回文子串:从暴力匹配到中心扩展的思维演进
c++·算法·leetcode
多弗朗皮卡丘10 小时前
C++继承
c++·继承
汉克老师11 小时前
GESP2026年9月认证C++七级( 第三部分编程题(2、括号序列))精讲
c++·gesp·小学生·学c++编程
w_zero_one11 小时前
链表(4)
java·数据结构·算法
白山编程大哥13 小时前
C语言篇:语法进阶
c语言·数据结构·算法