欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > C++ 数据结构 单链表、双链表、循环单向链表、循环双向链表

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

2025/4/20 5:13:46 来源:https://blog.csdn.net/qq_50373827/article/details/143427254  浏览:    关键词:C++ 数据结构 单链表、双链表、循环单向链表、循环双向链表

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

单链表

概念

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

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

插入操作

  • 在链表头插入 (插入1):
    [1] -> nullptr
    

    在尾部插入 (插入2):

[1] -> [2] -> nullptr

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

[1] -> [3] -> [2] -> nullptr

删除操作

  • 删除索引1的元素 (删除3):
    [1] -> [2] -> nullptr
    
    #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插入1list.insert(1, 2); // 在索引1插入2list.insert(1, 3); // 在索引1插入3list.print(); // 输出: 1 3 2list.remove(1); // 删除索引1的元素list.print(); // 输出: 1 2return 0;
    }
    

    双链表

    概念

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

    特点
  • 双向遍历:可以从前向后和从后向前遍历。
  • 插入和删除效率:与单链表相同,但双链表在删除时可以更方便地找到前驱节点。
  • 内存占用:每个节点需存储两个指针,因此占用更多内存。
操作示意

插入操作

  • 在链表头插入 (插入1)
[1] <-> nullptr

在尾部插入 (插入2)

[1] <-> [2] <-> nullptr

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

[1] <-> [3] <-> [2] <-> nullptr

删除操作

  • 删除索引1的元素 (删除3)
[1] <-> [2] <-> nullptr
#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插入1list.insert(1, 2); // 在索引1插入2list.insert(1, 3); // 在索引1插入3list.print(); // 输出: 1 3 2list.remove(1); // 删除索引1的元素list.print(); // 输出: 1 2return 0;
}

单循环链表 (Circular Singly Linked List)

特性

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

操作顺序

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

    • 在链表头插入 (插入3)
      [3] <-> [3]
      

      在链表头插入 (插入2)

      [2] <-> [3] <-> [2]
      

      在链表头插入 (插入1)

      [1] <-> [2] <-> [3] <-> [1]
      

删除操作

操作顺序

  • 删除值为2的元素
    [1] <-> [3] <-> [1]
    
    #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); // 在头插入1list.insertAtHead(2); // 在头插入2list.insertAtHead(3); // 在头插入3list.print(); // 输出: 3 2 1list.remove(2); // 删除值为2的节点list.print(); // 输出: 3 1return 0;
    }
    

    双循环链表 (Circular Doubly Linked List)

    特性

  • 循环性:尾节点的 next 指针指向头节点,头节点的 prev 指针指向尾节点,形成环形结构。
  • 双向遍历:可以从任意节点向前或向后遍历。
  • 插入
    • 创建新节点。
    • 如果链表为空,设置头节点为新节点,nextprev 均指向自身。
    • 如果不为空:
      • 更新新节点的 nextprev 指向相应的节点。
      • 更新相关节点的指针以保持循环结构。
  • 删除
    • 遍历链表找到目标节点。
    • 调整前驱节点的 next 和后继节点的 prev 指针,以跳过目标节点。
    • 释放目标节点的内存。
  • 动态大小:和单循环链表一样,动态增加或减少节点。
  • 插入和删除灵活:可以在任何位置方便地进行插入和删除操作,因为每个节点都有前驱和后继指针。
#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); // 在头插入1list.insertAtHead(2); // 在头插入2list.insertAtHead(3); // 在头插入3list.print(); // 输出: 3 2 1list.remove(2); // 删除值为2的节点list.print(); // 输出: 3 1return 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. 更新倒数第二个节点的 nextnullptr,删除尾节点。
  • 删除中间节点

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

    1. 创建新节点,设置新节点的数据。
    2. 如果链表为空,设置头指针为新节点,nextprev 均指向自身。
    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. 更新新节点的 nextprev 指向新节点。
  • 删除头节点

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

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

    1. 遍历链表找到目标节点。
    2. 更新目标节点的 prevnextnextprev 指向相应的节点,跳过目标节点。
    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. 如果链表为空,设置新节点的 nextprev 均指向自身,头指针指向新节点。
    3. 如果不为空,更新新节点的 next 为头节点,prev 为尾节点。
    4. 更新头节点的 prev 为新节点,尾节点的 next 为新节点。
    5. 更新头指针为新节点。
  • 在尾插入

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

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

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

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

    1. 遍历链表找到目标节点。
    2. 更新目标节点的 prevnext 的指针以跳过目标节点。
    3. 删除目标节点。 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词