当前位置:   article > 正文

链表(linked_list)的理解以及实现

链表(linked_list)的理解以及实现

链表的概念:

链表是一种线性数据结构,其中的每个元素都是一个节点对象,各个节点通过“引用”相连接。引用记录了下一个节点的内存地址,通过它可以从当前节点访问到下一个节点。 

可以看出:链表物理结构不是连续的

链表与数组:

链表(带头双向循环链表)与数组(顺序表)在功能,结构上的不同:

数组链表
存储方式连续内存空间分散内存空间
容量扩展长度不可变可灵活扩展
内存效率元素占用内存少、但可能浪费空间元素占用内存多
访问元素O(1)O(n)
添加元素O(n)O(1)
删除元素O(n)O(1)

链表的分类:

类型分类:

  • 单链表(Singly Linked List):

   - 每个节点包含数据和指向下一个节点的指针。

   - 单向链表,只能从表头向表尾遍历。

  • 双链表(Doubly Linked List):

   - 每个节点包含数据和两个指针,分别指向前一个节点和后一个节点。

   - 双向链表,可以从任一节点开始向前或向后遍历。

  • 循环链表(Circular Linked List):

   - 单链表或双链表的变体,最后一个节点的指针指向表头或前一个节点。

   - 循环单链表:最后一个节点指向表头。

   - 循环双链表:最后一个节点的后指针指向表头,表头的前指针指向最后一个节点。

  • 双向循环链表(Doubly Circular Linked List):

   - 双链表的循环版本,第一个节点的前指针和最后一个节点的后指针都指向表头。

  • 栈式链表(Stack Implementation Using Linked List):

   - 使用链表实现的栈,遵循后进先出(LIFO)原则。

   - 通常使用单链表实现,新元素总是添加到链表的头部。

  • 队列式链表(Queue Implementation Using Linked List):

   - 使用链表实现的队列,遵循先进先出(FIFO)原则。

   - 通常使用双链表实现,新元素添加到链表的尾部,移除元素从头部。

  • 有序链表(Ordered Linked List):

   - 链表中的节点按照特定的顺序(如数值大小)进行排序。

  • 无序链表(Unordered Linked List):

   - 链表中的节点没有特定的顺序。

  • 动态链表(Dynamic Linked List):

   - 可以在运行时动态地添加或删除节点。

  • 静态链表(Static Linked List):

    - 节点的数量在创建时就固定了,不能动态地添加或删除节点。

  • 哈希链表(Hash Linked List):

    - 哈希表的实现方式之一,通过链表解决哈希冲突。

  • 索引链表(Indexed Linked List):

    - 链表中包含指向特定节点的索引,可以快速访问链表中的元素。

结构分类:

其中:较为常见的整体结构还是单向,环形,双向连表:

 

链表的结构⾮常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:

 

虽然有这么多的链表的结构,但是我们实际中最常⽤还是两种结构: 单链表 双向带头循环链表
  • ⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。另外这种结构在笔试⾯试中出现很多
  • 带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使⽤代码实现以后会发现结构会带来很多优势,实现反⽽简单了

实现图解:

在实现链表的过程中,主要还是还是数据得插入删除

插入:

删除: 

单链表的实现:

在此主要实现带头单向不循环链表

在这里,带头带的其实就是哨兵位,哨兵位是一种特殊的节点,通常被添加到链表的开始或末尾,以简化某些操作,比如避免对空链表的特殊情况处理。哨兵节点通常不存储有效的数据,或者存储一个默认值

  1. #include <iostream>
  2. #include <stdexcept> // 用于抛出异常
  3. // 定义节点模板结构体
  4. template <typename T>
  5. struct Node {
  6. T data; // 存储数据
  7. Node<T>* next; // 指向下一个节点的指针
  8. // 构造函数
  9. Node(T val = T())
  10. : data(val)
  11. , next(nullptr)
  12. {}
  13. };
  14. // 定义带头结点的单向不循环链表类
  15. template <typename T>
  16. class LinkedListWithHead {
  17. private:
  18. Node<T>* head; // 头结点,哨兵节点
  19. public:
  20. // 构造函数
  21. LinkedListWithHead()
  22. {
  23. head = new Node<T>(); // 创建哨兵节点
  24. }
  25. // 析构函数
  26. ~LinkedListWithHead()
  27. {
  28. Node<T>* current = head->next;
  29. while (current != nullptr)
  30. {
  31. Node<T>* next = current->next;
  32. delete current;
  33. current = next;
  34. }
  35. delete head; // 释放哨兵节点
  36. }
  37. // 在链表末尾添加新节点
  38. void append(const T& value)
  39. {
  40. Node<T>* newNode = new Node<T>(value);
  41. Node<T>* last = head;
  42. while (last->next != nullptr)
  43. {
  44. last = last->next;
  45. }
  46. last->next = newNode;
  47. }
  48. // 在链表头部添加新节点
  49. void prepend(const T& value)
  50. {
  51. Node<T>* newNode = new Node<T>(value);
  52. newNode->next = head->next;
  53. head->next = newNode;
  54. }
  55. // 根据值删除节点
  56. void remove(const T& value)
  57. {
  58. Node<T>* current = head;
  59. while (current->next != nullptr && current->next->data != value)
  60. {
  61. current = current->next;
  62. }
  63. if (current->next != nullptr)
  64. {
  65. Node<T>* toDelete = current->next;
  66. current->next = current->next->next;
  67. delete toDelete;
  68. }
  69. else
  70. {
  71. throw std::runtime_error("Value not found in the list.");
  72. }
  73. }
  74. // 查找节点是否存在
  75. bool contains(const T& value) const
  76. {
  77. Node<T>* current = head->next;
  78. while (current != nullptr)
  79. {
  80. if (current->data == value)
  81. {
  82. return true;
  83. }
  84. current = current->next;
  85. }
  86. return false;
  87. }
  88. // 打印链表中的所有元素
  89. void print() const
  90. {
  91. Node<T>* current = head->next;
  92. while (current != nullptr)
  93. {
  94. std::cout << current->data << " -> ";
  95. current = current->next;
  96. }
  97. std::cout << "null" << std::endl;
  98. }
  99. };
  • remove 方法:删除链表中第一个匹配值的节点。如果找不到该值,则抛出一个 std::runtime_error 异常。
  • contains 方法:检查链表中是否存在具有给定值的节点,并返回一个布尔值。

请注意,remove 方法中,我们首先遍历链表直到找到要删除的节点。如果找到,我们将其从链表中移除并释放内存。如果链表中没有该值,我们抛出一个异常。contains 方法则是遍历链表检查是否存在具有给定值的节点。

双向链表的实现:

在此主要实现带头双向循环链表:

实现一个带头结点的双向循环链表涉及到创建一个链表,其中每个节点除了包含数据外,还有两个指针分别指向前一个节点和后一个节点。头结点是一个哨兵节点,它的 nextprev 指针都指向链表的第一个实际数据节点,而链表的最后一个数据节点的 next 指向头结点,prev 指向最后一个数据节点自身。

下面是C++模板实现带头结点的双向循环链表的示例代码:

  1. #pragma once
  2. #include <iostream>
  3. #include <stdexcept> // 用于抛出异常
  4. // 定义节点模板结构体
  5. template <typename T>
  6. struct Node {
  7. T data;
  8. Node<T>* prev;
  9. Node<T>* next;
  10. // 构造函数,为data提供默认参数值
  11. Node(T val = T()) : data(val), prev(nullptr), next(nullptr) {}
  12. };
  13. // 定义带头结点的双向循环链表类
  14. template <typename T>
  15. class CircularDoublyLinkedList {
  16. private:
  17. Node<T>* head; // 头结点,哨兵节点
  18. public:
  19. // 构造函数
  20. CircularDoublyLinkedList() {
  21. head = new Node<T>(); // 创建哨兵节点
  22. head->next = head->prev = head; // 哨兵节点形成循环
  23. }
  24. // 析构函数
  25. ~CircularDoublyLinkedList() {
  26. Node<T>* current = head->next;
  27. while (current != head) {
  28. Node<T>* temp = current;
  29. current = current->next;
  30. delete temp;
  31. }
  32. delete head; // 释放哨兵节点
  33. }
  34. // 在链表末尾添加新节点
  35. void append(const T& value) {
  36. Node<T>* newNode = new Node<T>(value);
  37. newNode->prev = head->prev;
  38. newNode->next = head;
  39. head->prev->next = newNode;
  40. head->prev = newNode;
  41. }
  42. // 删除链表中的指定节点
  43. void remove(Node<T>* node) {
  44. if (node == head) {
  45. head = head->next; // 哨兵节点指向下一个节点
  46. }
  47. node->prev->next = node->next;
  48. node->next->prev = node->prev;
  49. delete node;
  50. }
  51. // 打印链表中的所有元素
  52. void print() const {
  53. Node<T>* current = head->next;
  54. if (head->next == head) {
  55. std::cout << "List is empty." << std::endl;
  56. return;
  57. }
  58. do {
  59. std::cout << current->data << " ";
  60. current = current->next;
  61. } while (current != head->next);
  62. std::cout << std::endl;
  63. }
  64. // 根据值查找节点,如果找到则返回节点指针,否则返回nullptr
  65. Node<T>* find(const T& value) const {
  66. Node<T>* current = head->next;
  67. if (head->next == head) {
  68. return nullptr; // 链表为空
  69. }
  70. do {
  71. if (current->data == value) return current;
  72. current = current->next;
  73. } while (current != head->next);
  74. return nullptr;
  75. }
  76. // 根据值删除节点
  77. void removeByValue(const T& value) {
  78. Node<T>* nodeToDelete = find(value);
  79. if (nodeToDelete != nullptr) {
  80. remove(nodeToDelete);
  81. }
  82. }
  83. };

在这个实现中,定义了 Node 结构体和 CircularDoublyLinkedList 类。CircularDoublyLinkedList 类包括添加节点到头部和末尾、删除节点、打印链表、查找节点以及根据值删除节点的方法。链表使用哨兵节点简化了头部和末尾的操作。

请注意,remove 方法接受一个 Node 指针作为参数,它将该节点从链表中移除并释放内存。removeByValue 方法使用 find 方法来查找具有给定值的节点,如果找到了就调用 remove 方法来删除它。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/1003872
推荐阅读
相关标签
  

闽ICP备14008679号