当前位置:   article > 正文

4----双向链表

双向链表

//由于某些问题,代码可能无法运行,但是实现是全部正确的,大家冲!冲!冲!

考察知识点:

操作符重载;双向链表(文末有总结哦)

任务描述:

在不使用C++ STL中的list的情况下来完成以下的操作。

双向链表是一种链表结构,和普通的单向链表不同,它的每个链表节点不仅包含next指针,还包含一个back指针指向前一个节点,如下所示:(图片来自知乎)

//List Node.h

  1.   struct Element
  2. {
  3.     int num;
  4.     // 可以添加任何你需要的数据类型,但与本关测评无关
  5.     bool operator==(const Element e) const;//
  6. };
  7. bool operator==(const Element e) const
  8. {
  9. return num==e.num;
  10. }
  11. class ListNode
  12. {
  13.     Element e;
  14.     ListNode *back = nullptr;
  15.     ListNode *next = nullptr;
  16.     ListNode();
  17.     ListNode(const Element &e_);
  18.     friend class List; // 为便于测评,这里设置为友元
  19. };

这段代码首先定义了一个结构体 Element:

其中包含一个整型变量 num 和一个重载了 == 运算符的成员函数 operator==。

这段代码还定义了一个名为 ListNode 的类,其中包括以下成员:

1. Element e:数据成员,表示节点存储的元素。

2. ListNode *back:指向前一个节点的指针,默认值为 nullptr。

3. ListNode *next:指向后一个节点的指针,默认值为 nullptr。

4. ListNode():默认构造函数,不接收参数,用于创建一个空节点。

5. ListNode(const Element &e_):构造函数,接收一个元素 e_,用于创建一个存储该元素的节点。

6. friend class List:声明 List 类为友元类,以便 List 类可以访问 ListNode 类的私有成员。

而双向循环链表则是首尾相接的双向链表。

List是一个双向循环链表,它的定义如下:(具体实现如下)

//List.h

  1. #include <iostream>
  2. #include "ListNode.h"
  3. class List
  4. {
  5. ListNode head; // 加入默认头节点,使得代码逻辑更统一
  6. // 在进行链表操作时,你需要对head取地址
  7. size_t size_; // 可以自行使用,不做要求
  8. public:
  9. // 构造和析构
  10. List(); // 默认构造函数,head的next和back应该指向自己
  11. List(const List &l_); // 拷贝构造函数,内部深拷贝
  12. ~List(); // 析构函数,要求释放空间,析构后的head和size变量不做要求
  13. void operator=(const List &l_); // 赋值操作符,内部深拷贝
  14. size_t size() { return size_; };
  15. // 访问和更改,返回访问或更改是否成功
  16. bool push_front(const Element &e); // 在头节点的next处插入e,分配空间失败时返回false
  17. bool push_back(const Element &e);
  18. bool pop_front(); // 弹出头节点的next节点并释放,不可弹出时返回false
  19. bool pop_back();
  20. bool remove(const Element &e); // 删除链表中所有值为e的节点并释放,成功删除返回true
  21. bool insert(const Element &e, ListNode *ln); // 在节点ln后插入e,分配空间失败时返回false,测试用例中给定的ln均合法
  22. void erase(ListNode *ln); // 从链表中删除节点ln,释放ln的空间,测试用例中给定的ln均合法
  23. ListNode *operator[](size_t i); // 获取正向第i个节点(从0开始,不包括head)的指针,不存在则返回空
  24. // 遍历输出
  25. void print(); // 正向(next)输出链表中的元素num,不包括head,空格分隔,换行符结尾;空则输出换行符
  26. };

//List.cpp

  1. #include "List.h"
  2. using namespace std;
  3. List::List()
  4. {
  5. size_=0;
  6. //注意,head是一个List Node类的对象,但head.next是一个指针,所以head要取地址
  7. head.next = &head;
  8. head.back = &head;
  9. }
  10. List::List(const List &l_)//自己定义拷贝构造函数,负责深拷贝(在上一章我们谈过Vector)
  11. {
  12. size_ = l_.size_;
  13. head.e.num = l_.head.e.num;//拷贝值
  14. ListNode *curr = l_.head.next;//现在指向l_的第一个节点
  15. ListNode *prev = &head;//指向head
  16. while (curr != &l_.head) //
  17. {
  18. ListNode *new_node = new ListNode(curr->e);
  19. /*这句代码创建了一个新的ListNode对象,将其地址存储在指针变量new_node中。该对象的值被设置为curr指针所指向的节点的值curr->e。这个操作可以用于复制链表节点,以便在链表中插入新节点或进行其他操作。*/
  20. prev->next = new_node;
  21. new_node->back = prev;
  22. prev = new_node;
  23. curr = curr->next;
  24. }
  25. prev->next = &head;
  26. head.back = prev;
  27. // prev=nullptr;//将两个指针赋值为nullptr
  28. // curr=nullptr;
  29. }
  30. List::~List()
  31. {//析构函数
  32. ListNode *current = head.next;
  33. while (current != &head)//来个浅浅的判断
  34. {
  35. ListNode *Next = current->next;//一般链表问题画图理解可能更形象一点
  36. ListNode *pre=current;
  37. head.next=Next;
  38. Next->back=&head;
  39. current = Next;
  40. delete pre;
  41. // Next=nullptr;
  42. // delete next;
  43. }
  44. //current=nullptr;//将current置为空,其实也无所谓啦
  45. size_=0;
  46. }
  47. void List::operator=(const List &l_)
  48. {
  49. if(&l_!=this){
  50. // Delete the existing nodes in the current list
  51. ListNode *curr = head.next;
  52. while(curr != &head){
  53. ListNode *temp = curr;
  54. curr = curr->next;
  55. delete temp;
  56. //可能有的读者会认为这种写法不严谨,其实也没错,因为反正要删除所有结点,链表中断也可以,因为最后只剩一个head
  57. }
  58. size_ = 0;
  59. // Copy the nodes from the input list
  60. ListNode *curr_input = l_.head.next;
  61. while(curr_input != &l_.head){
  62. push_back(curr_input->e);
  63. curr_input = curr_input->next;
  64. }
  65. // curr_input=nullptr;
  66. }
  67. else return;
  68. }
  69. //以下4个函数建议画图理解
  70. bool List::push_front(const Element &e)
  71. {
  72. ListNode *ln = new ListNode(e);
  73. if(ln == nullptr) return false;
  74. ln->next = head.next;
  75. ln->back = &head;
  76. head.next->back = ln;
  77. head.next = ln;
  78. size_++;
  79. return true;
  80. }
  81. bool List::push_back(const Element &e)
  82. {
  83. ListNode *ln = new ListNode(e);
  84. if(ln == nullptr) return false;
  85. ln->back = head.back;
  86. ln->next = &head;
  87. head.back->next = ln;
  88. head.back = ln;
  89. size_++;
  90. return true;
  91. }
  92. bool List::pop_front()
  93. {
  94. if (head.next == nullptr||head.next == &head)//此处要注意最后一次的情况
  95. {
  96. return false;
  97. }
  98. ListNode* ln = head.next;
  99. head.next = ln->next;
  100. ln->next->back = &head;
  101. delete ln;
  102. size_--;
  103. return true;
  104. }
  105. bool List::pop_back()
  106. {
  107. if(head.back == &head ||head.back == nullptr) // list is empty
  108. return false;
  109. ListNode *temp = head.back;
  110. head.back = temp->back;
  111. temp->back->next = &head;
  112. delete temp;
  113. size_--;
  114. return true;
  115. }
  116. bool List::remove(const Element &e)
  117. {
  118. ListNode *current = head.next;
  119. ListNode *prev = &head;
  120. bool found = false;
  121. while (current != &head) {
  122. if (current->e == e) //用到了operator==这个重载函数
  123. {
  124. prev->next = current->next;
  125. current->next->back = prev;
  126. delete current;
  127. current = prev->next;
  128. found = true;
  129. size_--;
  130. } else {
  131. prev = current;
  132. current = current->next;
  133. }
  134. }
  135. return found;
  136. }
  137. bool List::insert(const Element &e, ListNode *ln)
  138. {
  139. ListNode *new_node = new ListNode(e);
  140. if (new_node == nullptr) //判断分配空间是否成功
  141. {
  142. return false;
  143. }
  144. new_node->next = ln->next;
  145. new_node->back = ln;
  146. ln->next->back = new_node;
  147. ln->next = new_node;
  148. size_++;
  149. return true;//其实这里和前面的push_front函数很像
  150. }
  151. void List::erase(ListNode *ln)
  152. {
  153. ln->back->next = ln->next;
  154. ln->next->back = ln->back;
  155. delete ln;
  156. size_--;
  157. }
  158. ListNode* List::operator[](size_t i)//重载[]
  159. {
  160. if (i >= size_) {
  161. return nullptr;//千万不要越界
  162. }
  163. ListNode *current = head.next;
  164. //这里j用size_t类型,一般数组的循环计数变量都是无符号整数
  165. for (size_t j = 0; j < i; j++)
  166. {
  167. current = current->next;
  168. }
  169. return current;
  170. }
  171. void List::print() {
  172. ListNode *current = head.next;
  173. while (current != &head) {
  174. cout << current->e.num << " ";//C++的输入输出以后会讲
  175. current = current->next;//链表结点数据的循环打印,
  176. }
  177. cout << endl;
  178. }

//test-example

  1. /**
  2. * 测试push_front push_back
  3. * 注意:同样需要保证构造函数、析构函数和print正确实现
  4. */
  5. void test1()
  6. {
  7. List l;
  8. l.push_front({2});
  9. l.push_back({3});
  10. l.push_front({1});
  11. l.push_back({4});
  12. l.print();
  13. }
  14. /** 输出
  15. * 1 2 3 4
  16. */
  17. /**
  18. * 测试pop_front pop_back
  19. */
  20. void test2()
  21. {
  22. List l;
  23. l.push_back({1});
  24. l.push_back({2});
  25. l.pop_front();
  26. l.print();
  27. l.push_back({4});
  28. l.push_front({3});
  29. l.pop_back();
  30. l.print();
  31. }
  32. /** 输出
  33. * 2
  34. * 3 2
  35. */
  36. /**
  37. * 测试pop_front pop_back
  38. */
  39. void test3()
  40. {
  41. List l;
  42. l.push_back({7890});
  43. l.pop_back();
  44. l.pop_front();
  45. l.push_front({2});
  46. l.push_back({3});
  47. l.push_front({1});
  48. l.push_back({4});
  49. l.print();
  50. l.pop_front();
  51. l.pop_back();
  52. l.print();
  53. l.pop_back();
  54. l.pop_front();
  55. l.print();
  56. }
  57. /** 输出
  58. * 1 2 3 4
  59. * 2 3
  60. *
  61. */
  62. /**
  63. * 测试remove
  64. */
  65. void test4()
  66. {
  67. List l;
  68. l.push_front({1});
  69. l.push_front({2});
  70. l.push_front({3});
  71. l.push_front({1});
  72. l.push_front({3});
  73. l.push_front({1});
  74. l.print();
  75. l.remove({1});
  76. l.print();
  77. l.remove({2});
  78. l.print();
  79. }
  80. /** 输出
  81. * 1 3 1 3 2 1
  82. * 3 3 2
  83. * 3 3
  84. */
  85. /**
  86. * 测试insert和earse
  87. */
  88. void test5()
  89. {
  90. List l;
  91. l.push_back({1});
  92. l.push_back({2});
  93. l.push_back({3});
  94. l.push_back({4});
  95. l.push_back({5});
  96. l.push_back({6});
  97. l.print();
  98. l.insert({23},l[1]);
  99. l.insert({56},l[5]);
  100. l.print();
  101. l.erase(l[5]);
  102. l.erase(l[1]);
  103. l.print();
  104. }
  105. /** 输出
  106. * 1 2 3 4 5 6
  107. * 1 2 23 3 4 5 56 6
  108. * 1 23 3 4 56 6
  109. */
  110. /**
  111. * 测试拷贝构造函数和赋值操作符
  112. */
  113. void test6()
  114. {
  115. List l;
  116. l.push_back({1});
  117. l.push_back({2});
  118. l.push_back({3});
  119. l.push_back({4});
  120. l.push_back({5});
  121. l.push_back({6});
  122. List l_cp=l;
  123. l.remove({4});
  124. l.remove({2});
  125. List l_assign;
  126. l_assign=l;
  127. l_cp.print();
  128. l_assign.print();
  129. }
  130. /** 输出
  131. * 1 2 3 4 5 6
  132. * 1 3 5 6
  133. */

总结:

1 .==操作符重载是一种自定义类型的比较方式,可以通过重载==操作符来实现。重载后的==操作符可以用于比较两个对象是否相等。

下面是一个示例代码,演示了如何重载==操作符:

  1. #include <iostream>
  2. class MyClass {
  3. public:
  4. int value;
  5. MyClass(int v) : value(v) {}
  6. bool operator==(const MyClass& other) const { return value == other.value; }
  7. };
  8. int main()
  9. {
  10. MyClass a(10);
  11. MyClass b(20);
  12. MyClass c(10);
  13. if (a == b)
  14. { std::cout << "a and b are equal" << std::endl; }
  15. else
  16. { std::cout << "a and b are not equal" << std::endl; }
  17. if (a == c)
  18. { std::cout << "a and c are equal" << std::endl; }
  19. else
  20. { std::cout << "a and c are not equal" << std::endl; }
  21. return 0; }

输出结果为:

a and b are not equal        a and c are equal

可以看到,重载后的==操作符可以用于比较两个MyClass对象的value是否相等。

2. 编程技巧

当题目中的有些接口十分相似,或者有些接口则可以用来实现另一些接口时,最好灵活地决定接口的实现顺序,或者使用接口来实现接口,从而减轻编程压力。

3. new和delete是C++中用于动态分配内存和释放内存的操作符。new用于在堆上分配内存,返回指向分配内存的指针;delete用于释放动态分配的内存,将指向该内存的指针传递给delete操作符即可释放该内存。在使用new和delete时,需要注意内存泄漏和空指针的问题。同时,可以通过重载new和delete函数来实现自定义内存管理。(在上一讲中我们谈论了new的相关知识点,所以此处不再赘述)。

4.

双向链表是一种链式数据结构,每个节点有两个指针,一个指向前一个节点,一个指向后一个节点。双向链表可以从头到尾或从尾到头遍历,而单向链表只能从头到尾遍历。

双向链表的优点是可以快速访问前一个节点和后一个节点,但缺点是需要更多的空间来存储指针。

以下是双向链表的常见操作:

  1. 插入节点:在双向链表中插入一个节点,需要将新节点的前一个节点的指针指向新节点,新节点的后一个节点的指针指向新节点,新节点的指针指向前一个节点和后一个节点。

  2. 左图为一种方式。

  3. 删除节点:在双向链表中删除一个节点,需要将该节点的前一个节点的指针指向该节点的后一个节点,该节点的后一个节点的指针指向该节点的前一个节点。

  4. 查找节点:在双向链表中查找一个节点,可以从头到尾或从尾到头遍历链表,直到找到目标节点。

  5. http://t.csdn.cn/pM4hd这是某位大佬的讲解。

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

闽ICP备14008679号