突破编程_C++_STL教程( deque 的实战应用)

1 std::deque 的主要应用场景

std::deque(双端队列)在C++编程中有许多应用场景,尤其是当需要在序列的两端进行高效的插入和删除操作时。以下是std::deque的一些典型应用场景:

(1)排队系统: std::deque 非常适合实现排队系统,如电影院售票或银行排队系统。可以使用 push_back() 在队列的末尾添加新客户,使用 pop_front() 从队列的头部移除已服务的客户。由于 std::deque 在两端操作具有高效的性能,这种实现方式比使用 std::vector 或 std::list 更加高效。

(2)缓冲区处理: 在处理数据流或需要维护一个固定大小的缓冲区时,std::deque 也非常有用。可以使用 push_back()添加新数据,并使用 pop_front() 移除旧数据,以保持缓冲区的大小恒定。

(3)撤销与重做功能: 在实现如文本编辑器或图形设计工具的撤销与重做功能时,std::deque 可以存储用户的操作历史。使用push_back()添加新操作,使用 pop_front() 撤销最近的操作。由于 std::deque 在头部和尾部的操作都很高效,这可以提供快速且流畅的撤销与重做体验。

(4)历史记录管理: 在需要维护一个操作历史记录的系统中,如网页浏览器或游戏应用,std::deque 可以用于存储最近的访问历史或得分记录。

(5)任务调度: 在任务调度系统中,std::deque 可以用于存储待处理的任务。新的任务可以添加到队列的末尾,而处理完成的任务可以从队列的头部移除。

1.1 std::deque 应用于排队系统

在应用于排队系统的开发中,std::deque(双端队列)的优势主要体现在以下几个方面:

(1)高效的插入和删除操作: std::deque 允许在队列的头部和尾部进行高效的插入和删除操作,这对于排队系统来说非常有用,因为客户通常是在队列的尾部加入,而在头部被处理。

(2)空间优化: std::deque 通常不是以连续内存存储的,而是通过一系列小的、单独的内存块来实现。这意味着它可以在内存分配方面更加灵活,尤其是在处理大量数据时,可以更有效地利用内存。

(3)无需重新分配内存: 当在std::deque的尾部添加元素时,通常不需要重新分配内存或移动现有的元素,因为std::deque预先分配了一些内存块。这可以减少内存分配和拷贝的开销。

注意:虽然上述优点 std::list 也具备,但是 std::deque 还支持元素的随机访问(另外与 std::list 对比,std::deque 所占据的内存空间也小一些),其效率低于 std::vector,但是比 std::list 要好很多,所以如果在排队系统中还需要随机访问元素时,std::deque 就更合适一些。

如下为样例代码:

第一步:定义 Customer 类
首先,需要定义一个 Customer 类来表示排队系统中的客户。这个类应该包含一个成员变量来存储客户的 ID,并且应该有一个方法来打印客户的信息。

#include <iostream>  
#include <deque>  
#include <memory>  // 定义一个表示客户的简单类  
class Customer {
public:Customer(int id) : m_id(id) {}// 打印客户的信息  void print() const {std::cout << "Customer ID: " << m_id << std::endl;}private:int m_id;
};

第二步:定义QueueSystem类
接下来,定义 QueueSystem 类,这个类将使用 std::deque 来存储 Customer 对象。QueueSystem 类将包含 enqueue(入队)、dequeue(出队)和size(获取队列大小)等方法。

// 排队系统类  
class QueueSystem {
public:// 添加客户到队列  void enqueue(int customerId) {m_customers.emplace_back(new Customer(customerId));}// 处理队列中的下一个客户  void dequeue() {if (m_customers.empty()) {std::cout << "Queue is empty." << std::endl;return;}// 获取并移除队列头部的客户  auto customer = std::move(m_customers.front());m_customers.pop_front();// 处理客户(在这里只是打印信息)  customer->print();// 客户处理完毕后,智能指针会自动释放内存  }// 显示队列中的客户数量  size_t size() const {return m_customers.size();}// 队列中的客户数量是否为空  bool empty() {return m_customers.empty();}private:std::deque<std::unique_ptr<Customer>> m_customers; // 使用智能指针的deque  
};

第三步:实现 main 函数
最后,需要在 main 函数中实例化 QueueSystem 对象,并使用它的方法来模拟排队系统。

int main() 
{QueueSystem system;// 添加客户到队列  system.enqueue(1);system.enqueue(2);system.enqueue(3);// 显示队列中的客户数量  std::cout << "Number of customers in queue: " << system.size() << std::endl;// 处理队列中的客户  while (!system.empty()) {system.dequeue();}return 0;
}

上面的代码的输出为:

Number of customers in queue: 3
Customer ID: 1
Customer ID: 2
Customer ID: 3

在这个示例中,QueueSystem 类使用 std::deque<std::unique_ptr>来存储等待处理的客户。客户通过 enqueue 方法添加到队列的尾部,而 dequeue 方法则从队列的头部移除并返回一个客户。由于 std::deque 在尾部插入和头部删除操作上的高效性,这个排队系统可以快速地处理大量的客户。

1.2 std::deque 应用于撤销与重做功能

在使用 std::deque 来实现撤销与重做功能时,其优势在于能够高效地在队列两端添加和删除元素。对于撤销操作,可以将操作存储在一个 std::deque 中,并允许用户撤销最近的操作。对于重做操作,可以简单地从队列的另一端取出操作并执行。由于 std::deque 支持两端操作,它非常适合这种撤销/重做场景。

以下是一个使用 std::deque 实现撤销与重做功能的示例:

第一步:定义 SimpleAction 类
首先,需要定义一个 SimpleAction 类,该类负责具体的执行、撤销与重做职责。

#include <iostream>  
#include <deque>  
#include <memory>  
#include <string>  // 定义一个操作类,包含执行、撤销与重做的方法  
class SimpleAction 
{
public:SimpleAction(const std::string& description): m_description(description) {}void execute() {std::cout << "Executing: " << m_description << std::endl;// 执行操作的代码  }void undo() {std::cout << "Undoing: " << m_description << std::endl;// 执行撤销操作的代码  }void redo() {std::cout << "Redoing: " << m_description << std::endl;// 执行重做操作的代码  }private:std::string m_description;
};

第二步:定义 UndoRedoManager 类
UndoRedoManager 类负责维护历史记录以及重做队列,并且通过创建一个包含“执行”和“撤销”操作对的方法实现了具体的撤销与重做功能。

class UndoRedoManager 
{
public:void execute(std::unique_ptr<SimpleAction> action) {// 执行操作  action->execute();// 将操作添加到历史记录中  m_actions.emplace_back(std::move(action));// 清空重做队列  m_redoActions.clear();}void undo() {if (m_actions.empty()) {std::cout << "No more actions to undo." << std::endl;return;}// 获取并执行最后一个操作的撤销过程  auto lastAction = std::move(m_actions.back());m_actions.pop_back();lastAction->undo();// 将操作添加到重做队列中  m_redoActions.emplace_front(std::move(lastAction));}void redo() {if (m_redoActions.empty()) {std::cout << "No more actions to redo." << std::endl;return;}// 获取并执行最后一个操作的重做过程  auto lastAction = std::move(m_redoActions.front());m_redoActions.pop_front();lastAction->redo();// 将操作添加到历史记录中  m_actions.emplace_front(std::move(lastAction));}private:std::deque<std::unique_ptr<SimpleAction>> m_actions;std::deque<std::unique_ptr<SimpleAction>> m_redoActions;
};

第三步:实现 main 函数
最后,需要在 main 函数中测试 UndoRedoManager 的功能。

int main() 
{UndoRedoManager manager;// 创建并执行一些操作  manager.execute(std::make_unique<SimpleAction>("Action 1"));manager.execute(std::make_unique<SimpleAction>("Action 2"));manager.execute(std::make_unique<SimpleAction>("Action 3"));// 撤销操作  manager.undo();manager.undo();// 重做操作  manager.redo();return 0;
}

上面的代码的输出为:

Executing: Action 1
Executing: Action 2
Executing: Action 3
Undoing: Action 3
Undoing: Action 2
Redoing: Action 2

注意:如果操作非常多(频繁的进行撤销与重做),还可以考虑使用 std::deque 的 shrink_to_fit 方法来减少内存使用。

2 std::deque 与 std::vector 的对比

下面是 std::deque 和 std::vector 在几个关键方面的对比:

内存布局和性能特性:

  • std::vector:通常,std::vector 在内存中是一块连续存储的数组。这使得它在随机访问(通过索引)元素时非常高效,因为可以直接通过偏移量计算得到元素地址。然而,std::vector 在序列开始或中间插入或删除元素时,可能需要重新分配和复制内存,这会导致较高的开销。
  • std::deque:std::deque 通常是以一系列固定大小的块(chunks)来实现的,这些块可以在内存中独立分配和释放。因此,std::deque 在序列的两端插入和删除元素时非常高效,因为这些操作通常只需要改变一些指针或句柄,而不需要移动大量元素。然而,std::deque 的随机访问性能通常比 std::vector 差,因为需要额外的间接引用。

空间效率:

  • std::vector:由于 std::vector 在内存中连续存储,因此它在空间效率方面通常优于std::deque,尤其是在存储大量数据时。
  • std::deque:由于 std::deque 使用多个独立的内存块,它可能会引入一些额外的内存开销,尤其是当存储的元素较小时。

迭代器失效:

  • std::vector:当 std::vector 进行插入或删除操作时,可能会导致迭代器、引用和指针失效。特别是当在 std::vector 的开始或中间位置插入或删除元素时。
  • std::deque:相比之下,std::deque 在两端插入或删除元素时,不会使已存在的迭代器、引用和指针失效。

扩展和收缩:

  • std::vector:当 std::vector 的大小增长时,它可能需要重新分配更大的内存块并将现有元素复制到新位置。这可能会导致额外的性能开销。同样,当 std::vector 的大小减小时,它可能会释放内存。
  • std::deque:std::deque 的扩展和收缩通常是通过添加或删除内存块来实现的,这些操作通常比 std::vector 的重新分配和复制要快。

初始化和填充:

  • std::vector:由于 std::vector 是连续存储的,因此可以使用 std::vector::assign、std::vector::resize 等成员函数进行高效的初始化和填充。
  • std::deque:虽然 std::deque 的填充操作也可以很高效,但它通常不如 std::vector 在连续内存上的操作那么快速。

总结来说,选择std::deque还是std::vector取决于具体的使用场景:

  • 如果需要频繁在序列两端插入和删除元素,并且不关心随机访问性能,那么 std::deque 可能是更好的选择。
  • 如果需要高效的随机访问,并且不经常在序列中间进行插入和删除操作,那么 std::vector 可能更合适。

3 std::deque 与 std::list 的对比

下面是 std::deque 和 std::list 在几个关键方面的对比:

内存布局和性能特性:

  • std::deque:std::deque 通常是以一系列固定大小的块(chunks)来实现的,这些块可以在内存中独立分配和释放。这使得 std::deque 在序列的两端插入和删除元素时非常高效,因为这些操作通常只需要改变一些指针或句柄,而不需要移动大量元素。然而,由于需要额外的间接引用,std::deque 的随机访问性能可能不如连续存储的容器。
  • std::list:std::list 是一个双向链表,每个元素都包含指向前一个和下一个元素的指针。这使得 std::list 在序列的任意位置进行插入和删除操作都非常高效,因为只需要更新少数几个指针。与 std::deque 相比,std::list 的随机访问性能较差,因为需要遍历链表来访问特定位置的元素。

空间效率:

  • std::deque:由于 std::deque 使用多个独立的内存块,它可能会引入一些额外的内存开销,尤其是当存储的元素较小时。然而,与 std::list 相比,std::deque 通常更加空间高效,因为 std::list 中的每个元素都需要存储额外的指针。
  • std::list:由于 std::list 的每个元素都包含指向前一个和下一个元素的指针,因此它通常比 std::deque 更占用内存。

迭代器失效:

  • std::deque:在 std::deque 的两端插入或删除元素时,不会使已存在的迭代器、引用和指针失效。然而,在 std::deque 的中间进行插入或删除操作可能会使迭代器、引用和指针失效。
  • std::list:在 std::list 的任意位置进行插入或删除操作都不会使已存在的迭代器、引用和指针失效。这是因为 std::list 的插入和删除操作只涉及更新指针,而不涉及移动元素。

扩展和收缩:

  • std::deque:std::deque 的扩展和收缩通常是通过添加或删除内存块来实现的,这些操作通常比连续存储的容器要快。然而,当 std::deque 的大小增长时,可能需要重新分配更大的内存块。
  • std::list:std::list 的扩展和收缩是通过在链表中添加或删除节点来实现的,这些操作通常很快,因为它们只涉及更新指针。

初始化和填充:

  • std::deque:std::deque 可以使用 std::deque::assign、std::deque::resize 等成员函数进行高效的初始化和填充。然而,与 std::vector 相比,std::deque 的填充操作可能不如连续存储的容器那么快速。
  • std::list:std::list 的初始化和填充操作通常涉及在链表中添加节点,这些操作相对较快,但可能不如连续存储的容器那么高效。

总结来说,选择std::deque还是std::list取决于具体的使用场景:

  • 如果需要频繁在序列两端插入和删除元素,并且关心空间效率,那么std::deque可能是更好的选择。
  • 如果需要在序列的任意位置进行高效的插入和删除操作,并且不关心随机访问性能,那么std::list可能更合适。

4 实现一个简单的 std::deque 容器

如下是一个简化的 std::deque 容器的实现,主要体现 std::deque 的分段存储特性:

#include <iostream>  
#include <memory>  
#include <vector>  template <typename T>
class Deque {
private:struct Node {std::vector<T> buffer;std::shared_ptr<Node> prev;std::shared_ptr<Node> next;Node() = default;Node(const Node&) = delete;Node& operator=(const Node&) = delete;Node(Node&& other) noexcept : buffer(std::move(other.buffer)), prev(std::move(other.prev)), next(std::move(other.next)) {}~Node() {// 当Node被销毁时,其管理的内存也会被自动释放  }};public:// 构造函数和析构函数  Deque(){auto newNode = std::make_shared<Node>();m_front = newNode;m_back = newNode;}~Deque() {clear();}// 禁用拷贝构造函数和赋值运算符  Deque(const Deque&) = delete;Deque& operator=(const Deque&) = delete;// 移动构造函数和移动赋值运算符  Deque(Deque&& other) noexcept : m_front(std::move(other.m_front)), m_back(std::move(other.m_back)), m_size(other.m_size) {other.m_size = 0;}Deque& operator=(Deque&& other) noexcept {if (this != &other) {clear();m_front = std::move(other.m_front);m_back = std::move(other.m_back);m_size = other.m_size;other.m_size = 0;}return *this;}// 在队首插入元素  void push_front(const T& value) {if (m_front && m_front->buffer.size() == NODE_CAPACITY) {push_front_node(value);}else {m_front->buffer.insert(m_front->buffer.begin(),value);++m_size;if (m_front->buffer.size() == 1) {m_back = m_front;}}}// 在队尾插入元素  void push_back(const T& value) {if (m_back && m_back->buffer.size() == NODE_CAPACITY) {push_back_node(value);}else {m_back->buffer.push_back(value);++m_size;if (m_back->buffer.size() == 1) {m_front = m_back;}}}// 从队首移除元素  void pop_front() {if (m_front && !m_front->buffer.empty()) {m_front->buffer.erase(m_front->buffer.begin());--m_size;if (m_front->buffer.empty()) {if (m_front == m_back) {m_front.reset();m_back.reset();}else {auto nextNode = m_front->next;nextNode->prev.reset();m_front = nextNode;}}}}// 从队尾移除元素  void pop_back() {if (m_back && !m_back->buffer.empty()) {m_back->buffer.pop_back();--m_size;if (m_back->buffer.empty()) {if (m_front == m_back) {m_front.reset();m_back.reset();}else {auto prevNode = m_back->prev;prevNode->next.reset();m_back = prevNode;}}}}// 获取队首元素  T& front() {if (empty()) {throw std::out_of_range("Deque is empty");}return m_front->buffer.front();}// 获取队尾元素  T& back() {if (empty()) {throw std::out_of_range("Deque is empty");}return m_back->buffer.back();}// 检查deque是否为空  bool empty() const {return m_size == 0;}// 获取deque的大小  size_t size() const {return m_size;}// 清除deque中的所有元素  void clear() {while (m_front) {pop_front();}}private:// 私有辅助函数,用于在deque前端创建新节点  void push_front_node(const T& value) {auto newNode = std::make_shared<Node>();newNode->buffer.push_back(value);newNode->next = m_front;if (m_front) {m_front->prev = newNode;}else {m_back = newNode;}m_front = newNode;++m_size;}// 私有辅助函数,用于在deque后端创建新节点  void push_back_node(const T& value) {auto newNode = std::make_shared<Node>();newNode->buffer.push_back(value);newNode->prev = m_back;if (m_back) {m_back->next = newNode;}else {m_front = newNode;}m_back = newNode;++m_size;}private:std::shared_ptr<Node> m_front;std::shared_ptr<Node> m_back;size_t m_size = 0;static constexpr size_t NODE_CAPACITY = 4; // 每个Node的缓冲区大小  };int main() 
{Deque<int> myDeque;// 在队首和队尾插入元素  myDeque.push_back(1);myDeque.push_back(2);myDeque.push_front(3);myDeque.push_front(4);// 输出Deque的元素  std::cout << "Front: " << myDeque.front() << std::endl;std::cout << "Back: " << myDeque.back() << std::endl;std::cout << "Size: " << myDeque.size() << std::endl;// 从队首和队尾移除元素  myDeque.pop_front();myDeque.pop_back();// 再次输出Deque的元素  std::cout << "Front: " << myDeque.front() << std::endl;std::cout << "Back: " << myDeque.back() << std::endl;std::cout << "Size: " << myDeque.size() << std::endl;return 0;
}

上面代码的输出为:

Front: 4
Back: 2
Size: 4
Front: 3
Back: 1
Size: 2

在上面的代码中,实现了一个简单的 std::deque 容器,它使用了分段连续存储技术。每个 Node 包含一个 std::vector<T> 作为缓冲区,以及指向前后 Node 的 std::unique_ptr。当在 deque 的前端或后端添加元素时,如果当前 Node 的缓冲区已满,就会创建一个新的 Node,并将元素添加到新 Node 的缓冲区中。同样地,当从 deque 的前端或后端移除元素时,如果导致某个 Node 变为空,就会释放该 Node。

这个实现展示了 std::deque 的基本特性,包括在队首和队尾高效地插入和移除元素,以及访问队首和队尾元素。然而,这个实现并不完整,例如它没有提供迭代器支持、异常安全性保证,也没有优化内存分配等。此外,它也没有处理可能的内存分配失败情况。在实际应用中,std::deque 的实现会更加复杂和健壮。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/722979.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Studio开发(一) 构建项目

1、项目创建测试 1.1 前言 Android Studio 是由 Google 推出的官方集成开发环境&#xff08;IDE&#xff09;&#xff0c;专门用于开发 Android 应用程序。 基于 IntelliJ IDEA: Android Studio 是基于 JetBrains 的 IntelliJ IDEA 开发的&#xff0c;提供了丰富的功能和插件…

Qt 通过pdfium将网络上的pdf显示为图片

前言 遇到个需求&#xff0c;就是在qt客户端显示服务器上的pdf文档&#xff0c;文档以base64格式返回给客户端。以下是实现方法&#xff1a; 1、在pro文件增加以下代码&#xff1a; INCLUDEPATH $$PWD/PDFiumSDK/include/publicDEPENDPATH $$PWD/PDFiumSDK/include/public…

Python 全栈系列232 再次搭建RabbitMQ

说明 最近想重新上RabbitMQ&#xff0c;主要目的还是为了分布式任务调度。在Kafka和RabbitMQ两者犹豫了一下&#xff0c;还是觉得RabbitMQ好一些。 在20年的时候有搞过一阵子的RabbitMQ,看了下当时的几篇文章&#xff0c;觉得其实想法一直没变过。 Python - 装机系列24 消息…

常用“树”数据结构

哈夫曼树 在许多应用中&#xff0c;树中结点常常被赋予一个表示某种意义的数值&#xff0c;称为该结点的权。从树的根到任意结点的路径长度(经过的边数)与该结点上权值的乘积&#xff0c;称为该结点的带权路径长度。树中所有叶结点的带权路径长度之和称为该树的带权路径长度&am…

出现身份验证错误,无法连接到本地安全机构 顺利解决这个问题希望能帮助大家

出现身份验证错误&#xff0c;无法连接到本地安全机构&#xff0c;远程计算机&#xff1a;XX&#xff0c;这可能是由于密码过期&#xff0c;如果密码已过期请更新密码。 我们可以在系统属性中对远程进行设置&#xff0c;以解决远程桌面无法连接到本地安全机构这一问题。 步骤…

倒计时34天

L2-1 堆宝塔 - B107 2023级选拔春季开学测重现 (pintia.cn) #include<bits/stdc.h> using namespace std; //#define int long long const int N2e56; const int inf0x3f3f3f3f; const double piacos(-1.0); vector<int>ve1,ve2; vector<vector<int> >…

企业出海WAS安全自动化解决方案

随着企业出海的日益激烈&#xff0c;安全风险正在成为企业日益关注的问题之一&#xff0c;九河云携手AWS带来了使用Amazon WAF 与 Amazon Shield 的 CloudFront安全自动化。Aws WAF是一种web应用防火墙&#xff0c;可帮助保护客户的web应用程序或api免遭常规web漏洞的攻击。Aws…

【R语言教程】

R语言简介&#xff1a; R 是一种自由、开源的编程语言&#xff0c;专门用于统计分析、数据挖掘、数据可视化以及整理和清洗数据。 R 的强大功能和丰富的扩展包使得它在全球统计学家、数据科学家甚至其它领域的研究员和技术人员中都非常受欢迎。 R语言环境&#xff1a; 要开始…

【Hadoop大数据技术】——Hadoop概述与搭建环境(学习笔记)

&#x1f4d6; 前言&#xff1a;随着大数据时代的到来&#xff0c;大数据已经在金融、交通、物流等各个行业领域得到广泛应用。而Hadoop就是一个用于处理海量数据的框架&#xff0c;它既可以为海量数据提供可靠的存储&#xff1b;也可以为海量数据提供高效的处理。 目录 &#…

【数据结构】用栈实现队列

前言&#xff1a;本节博客分享了用栈实现队列效果的思路以及代码&#xff0c;有需要借鉴即可。 1.题目及链接 LINK 2.思路分析 如果要用栈实现队列&#xff0c;我们直到栈是先入后出的一个效果&#xff0c;所以我们可以用两个栈&#xff0c;这样逆转两次数不就是入栈之前数组…

Scanner类的九大输入方法,三种输出方法

目录 输入方法 Scanner类的9大输入方法 输出方法 print println printf 例题实战 题目进阶 输入方法 最常见的输入输出方法 输入Scanner类 Scanner是Java5的新特征&#xff0c;在java.util包里&#xff0c;可以完成用户输入&#xff1a; 导入java.util包 构造Scanner对象&…

Linux环境下终端( 串口)接口应用

目录 概述 1 了解串口应用编程 1.1 认识 struct termios 结构体 1.1.1 认识c_iflag 1.1.2 认识c_oflag 1.1.3 认识 c_cflag 的配置参数 1.1.4 认识c_lflag 1.2 终端相关的函数库 2 编写串口应用接口 2.1 设置baud函数 2.2 设置停止位函数 2.3 设置奇偶校验函数 2.4…

SpringBoot约定大于配置

什么是约定大于配置 "约定大于配置"&#xff08;Convention Over Configuration&#xff09;是一种理念&#xff0c;旨在通过默认约定和规则来减少开发人员需要做的配置工作。在Spring Boot框架中&#xff0c;这一原则得到了充分应用&#xff0c;帮助开发者更快地构…

Blender和3ds Max哪个会是行业未来?

Blender和3ds Max都是很强大的三维建模和渲染软件&#xff0c;各有各的好处。选择哪个软件更好&#xff0c;要看你的需求、预算、技术水平以及行业趋势等因素。 Blender最大的优点是免费且开源&#xff0c;这对预算有限的个人和小团队来说很有吸引力。它有很多建模工具和功能&…

在电脑桌面打开任意应用程序的快捷键

首先为某个程序&#xff08;比如谷歌浏览器&#xff09;创建一个快捷方式&#xff0c; 其次右键快捷方式&#xff0c;找到属性一栏 单击快捷键三个字右边的方框&#xff08;里面有一个“无”&#xff09;&#xff0c;然后按下你所需要设置的快捷键

【强化学习的数学原理-赵世钰】课程笔记(七)时序差分方法

一.内容概述 第五节课蒙特卡洛&#xff08;Mento Carlo&#xff09;方法是全课程中第一次介绍 model-free 的方法&#xff0c;本节课的 Temporal-difference learning&#xff08;TD learning&#xff09;是我们要介绍的第二种 model-free 的方法。基于蒙特卡洛&#xff08;Me…

【数据结构高阶】并查集

目录 一、什么是并查集 二、并查集的原理 三、并查集的作用 四、并查集的代码实现 一、什么是并查集 在一些应用问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个 单元素集合&#xff0c;然后按一定的规律将归于同一组元…

【LeetCode每日一题】【BFS模版与例题】【二维数组】1293. 网格中的最短路径

BFS基本模版与案例可以参考&#xff1a; 【LeetCode每日一题】【BFS模版与例题】863.二叉树中所有距离为 K 的结点 【LeetCode每日一题】【BFS模版与例题】【二维数组】130被围绕的区域 && 994 腐烂的橘子 思路&#xff1a; 特殊情况&#xff1a; 最短的路径是向下再向…

【电路笔记】-双极晶体管

双极晶体管 文章目录 双极晶体管1、概述2、双极晶体管结构3、双极晶体管配置3.1 共基极 (CB) 配置3.2 共发射极 (CE) 配置3.3 共集极 (CC) 配置4、总结1、概述 双极结型晶体管是一种可用于开关或放大的半导体器件。 与半导体二极管不同,半导体二极管由两片半导体材料组成,形…

【Java基础面试题2】

目录 前言 1.11 int和Integer有什么区别&#xff0c;二者在做运算时会得到什么结果&#xff1f; 1.12 说一说你对面向对象的理解 1.13 面向对象的三大特征是什么&#xff1f; 1.14 封装的目的是什么&#xff0c;为什么要有封装&#xff1f; 1.15 说一说你对多态的理解 1…