赞
踩
//由于某些问题,代码可能无法运行,但是实现是全部正确的,大家冲!冲!冲!
考察知识点:
操作符重载;双向链表(文末有总结哦)
任务描述:
在不使用C++ STL中的list的情况下来完成以下的操作。
双向链表是一种链表结构,和普通的单向链表不同,它的每个链表节点不仅包含next指针,还包含一个back指针指向前一个节点,如下所示:(图片来自知乎)

//List Node.h
- struct Element
- {
- int num;
- // 可以添加任何你需要的数据类型,但与本关测评无关
- bool operator==(const Element e) const;//
- };
- bool operator==(const Element e) const
- {
- return num==e.num;
-
- }
-
- class ListNode
- {
- Element e;
- ListNode *back = nullptr;
- ListNode *next = nullptr;
- ListNode();
- ListNode(const Element &e_);
- friend class List; // 为便于测评,这里设置为友元
- };

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

//List.cpp
- #include "List.h"
- using namespace std;
- List::List()
- {
- size_=0;
- //注意,head是一个List Node类的对象,但head.next是一个指针,所以head要取地址
- head.next = &head;
- head.back = &head;
- }
-
- List::List(const List &l_)//自己定义拷贝构造函数,负责深拷贝(在上一章我们谈过Vector)
- {
- size_ = l_.size_;
- head.e.num = l_.head.e.num;//拷贝值
-
- ListNode *curr = l_.head.next;//现在指向l_的第一个节点
- ListNode *prev = &head;//指向head
- while (curr != &l_.head) //
- {
- ListNode *new_node = new ListNode(curr->e);
- /*这句代码创建了一个新的ListNode对象,将其地址存储在指针变量new_node中。该对象的值被设置为curr指针所指向的节点的值curr->e。这个操作可以用于复制链表节点,以便在链表中插入新节点或进行其他操作。*/
- prev->next = new_node;
- new_node->back = prev;
- prev = new_node;
- curr = curr->next;
- }
- prev->next = &head;
- head.back = prev;
- // prev=nullptr;//将两个指针赋值为nullptr
- // curr=nullptr;
- }
-
- List::~List()
- {//析构函数
- ListNode *current = head.next;
- while (current != &head)//来个浅浅的判断
- {
- ListNode *Next = current->next;//一般链表问题画图理解可能更形象一点
- ListNode *pre=current;
- head.next=Next;
- Next->back=&head;
- current = Next;
- delete pre;
- // Next=nullptr;
- // delete next;
- }
- //current=nullptr;//将current置为空,其实也无所谓啦
- size_=0;
- }
-
- void List::operator=(const List &l_)
- {
- if(&l_!=this){
-
- // Delete the existing nodes in the current list
- ListNode *curr = head.next;
- while(curr != &head){
- ListNode *temp = curr;
- curr = curr->next;
- delete temp;
- //可能有的读者会认为这种写法不严谨,其实也没错,因为反正要删除所有结点,链表中断也可以,因为最后只剩一个head
- }
- size_ = 0;
-
- // Copy the nodes from the input list
- ListNode *curr_input = l_.head.next;
- while(curr_input != &l_.head){
- push_back(curr_input->e);
- curr_input = curr_input->next;
- }
- // curr_input=nullptr;
-
- }
- else return;
- }
- //以下4个函数建议画图理解
- bool List::push_front(const Element &e)
- {
- ListNode *ln = new ListNode(e);
- if(ln == nullptr) return false;
- ln->next = head.next;
- ln->back = &head;
- head.next->back = ln;
- head.next = ln;
- size_++;
- return true;
- }
- bool List::push_back(const Element &e)
- {
- ListNode *ln = new ListNode(e);
- if(ln == nullptr) return false;
- ln->back = head.back;
- ln->next = &head;
- head.back->next = ln;
- head.back = ln;
- size_++;
- return true;
- }
-
- bool List::pop_front()
- {
- if (head.next == nullptr||head.next == &head)//此处要注意最后一次的情况
- {
- return false;
- }
- ListNode* ln = head.next;
- head.next = ln->next;
- ln->next->back = &head;
- delete ln;
- size_--;
- return true;
- }
-
- bool List::pop_back()
- {
- if(head.back == &head ||head.back == nullptr) // list is empty
- return false;
- ListNode *temp = head.back;
- head.back = temp->back;
- temp->back->next = &head;
- delete temp;
- size_--;
- return true;
- }
-
- bool List::remove(const Element &e)
- {
- ListNode *current = head.next;
- ListNode *prev = &head;
- bool found = false;
- while (current != &head) {
- if (current->e == e) //用到了operator==这个重载函数
- {
- prev->next = current->next;
- current->next->back = prev;
- delete current;
- current = prev->next;
- found = true;
- size_--;
- } else {
- prev = current;
- current = current->next;
- }
- }
- return found;
- }
- bool List::insert(const Element &e, ListNode *ln)
- {
- ListNode *new_node = new ListNode(e);
- if (new_node == nullptr) //判断分配空间是否成功
- {
- return false;
- }
- new_node->next = ln->next;
- new_node->back = ln;
- ln->next->back = new_node;
- ln->next = new_node;
- size_++;
- return true;//其实这里和前面的push_front函数很像
- }
- void List::erase(ListNode *ln)
- {
- ln->back->next = ln->next;
- ln->next->back = ln->back;
- delete ln;
- size_--;
- }
-
- ListNode* List::operator[](size_t i)//重载[]
- {
- if (i >= size_) {
- return nullptr;//千万不要越界
- }
- ListNode *current = head.next;
- //这里j用size_t类型,一般数组的循环计数变量都是无符号整数
- for (size_t j = 0; j < i; j++)
- {
- current = current->next;
- }
- return current;
- }
-
- void List::print() {
- ListNode *current = head.next;
- while (current != &head) {
- cout << current->e.num << " ";//C++的输入输出以后会讲
- current = current->next;//链表结点数据的循环打印,
- }
- cout << endl;
- }

//test-example
- /**
- * 测试push_front push_back
- * 注意:同样需要保证构造函数、析构函数和print正确实现
- */
- void test1()
- {
- List l;
- l.push_front({2});
- l.push_back({3});
- l.push_front({1});
- l.push_back({4});
- l.print();
- }
- /** 输出
- * 1 2 3 4
- */
- /**
- * 测试pop_front pop_back
- */
- void test2()
- {
- List l;
- l.push_back({1});
- l.push_back({2});
- l.pop_front();
- l.print();
- l.push_back({4});
- l.push_front({3});
- l.pop_back();
- l.print();
- }
- /** 输出
- * 2
- * 3 2
- */
- /**
- * 测试pop_front pop_back
- */
- void test3()
- {
- List l;
- l.push_back({7890});
- l.pop_back();
- l.pop_front();
- l.push_front({2});
- l.push_back({3});
- l.push_front({1});
- l.push_back({4});
- l.print();
- l.pop_front();
- l.pop_back();
- l.print();
- l.pop_back();
- l.pop_front();
- l.print();
- }
- /** 输出
- * 1 2 3 4
- * 2 3
- *
- */
- /**
- * 测试remove
- */
- void test4()
- {
- List l;
- l.push_front({1});
- l.push_front({2});
- l.push_front({3});
- l.push_front({1});
- l.push_front({3});
- l.push_front({1});
- l.print();
- l.remove({1});
- l.print();
- l.remove({2});
- l.print();
- }
- /** 输出
- * 1 3 1 3 2 1
- * 3 3 2
- * 3 3
- */
- /**
- * 测试insert和earse
- */
- void test5()
- {
- List l;
- l.push_back({1});
- l.push_back({2});
- l.push_back({3});
- l.push_back({4});
- l.push_back({5});
- l.push_back({6});
- l.print();
- l.insert({23},l[1]);
- l.insert({56},l[5]);
- l.print();
- l.erase(l[5]);
- l.erase(l[1]);
- l.print();
- }
- /** 输出
- * 1 2 3 4 5 6
- * 1 2 23 3 4 5 56 6
- * 1 23 3 4 56 6
- */
- /**
- * 测试拷贝构造函数和赋值操作符
- */
- void test6()
- {
- List l;
- l.push_back({1});
- l.push_back({2});
- l.push_back({3});
- l.push_back({4});
- l.push_back({5});
- l.push_back({6});
- List l_cp=l;
- l.remove({4});
- l.remove({2});
- List l_assign;
- l_assign=l;
- l_cp.print();
- l_assign.print();
- }
- /** 输出
- * 1 2 3 4 5 6
- * 1 3 5 6
- */

总结:
1 .==操作符重载是一种自定义类型的比较方式,可以通过重载==操作符来实现。重载后的==操作符可以用于比较两个对象是否相等。
下面是一个示例代码,演示了如何重载==操作符:
- #include <iostream>
- class MyClass {
- public:
- int value;
- MyClass(int v) : value(v) {}
- bool operator==(const MyClass& other) const { return value == other.value; }
- };
- int main()
- {
- MyClass a(10);
- MyClass b(20);
- MyClass c(10);
- if (a == b)
- { std::cout << "a and b are equal" << std::endl; }
- else
- { std::cout << "a and b are not equal" << std::endl; }
- if (a == c)
- { std::cout << "a and c are equal" << std::endl; }
- else
- { std::cout << "a and c are not equal" << std::endl; }
- 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.
双向链表是一种链式数据结构,每个节点有两个指针,一个指向前一个节点,一个指向后一个节点。双向链表可以从头到尾或从尾到头遍历,而单向链表只能从头到尾遍历。
双向链表的优点是可以快速访问前一个节点和后一个节点,但缺点是需要更多的空间来存储指针。
以下是双向链表的常见操作:
插入节点:在双向链表中插入一个节点,需要将新节点的前一个节点的指针指向新节点,新节点的后一个节点的指针指向新节点,新节点的指针指向前一个节点和后一个节点。
左图为一种方式。
删除节点:在双向链表中删除一个节点,需要将该节点的前一个节点的指针指向该节点的后一个节点,该节点的后一个节点的指针指向该节点的前一个节点。
查找节点:在双向链表中查找一个节点,可以从头到尾或从尾到头遍历链表,直到找到目标节点。
http://t.csdn.cn/pM4hd这是某位大佬的讲解。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。