简易STL实现 | Deque的实现

一种 在内存中存储元素的数据结构,它支持 在两端添加和删除元素(使用循环数组实现)

1、deque的特性(分段deque实现)

1、双端操作: deque支持在前端和后端执行快速的插入和删除操作

2、随机访问: 与list不同,deque允许在常量时间内 对元素进行随机访问。这是因为 deque的内部结构采用分段数组,而不是链表
myDeque[2] 和 myDeque[4] 的访问时间是常量的,不受deque的大小的影响
这是因为std::deque 使用了分段数组的结构,每个分段都是一个固定大小的数组,因此可以直接计算索引的位置,而不需要遍历整个容器

将数据存储 在多个较小的数组(块)中,而不是 单个连续的大数组。这样做的好处是 可以更有效地扩展容量,避免频繁的内存重新分配

#include <iostream>
#include <stdexcept>
#include <vector>template <typename T>
class Deque {
public:Deque() : capacity(1), size(0), frontIndex(0), backIndex(0) {blocks.push_back(new T[blockSize]); // 每次新分配小块数组都是固定大小(都是blockSize,就是多少个的问题)}~Deque() {clear();for (auto block : blocks) {delete[] block;}}void push_back(const T& e) {if (size == capacity) {resize();}blocks[backIndex / blockSize][backIndex % blockSize] = e; // 核心思路backIndex = (backIndex + 1) % capacity;size++;}void push_front(const T& e) {if (size == capacity) {resize();}frontIndex = (frontIndex - 1 + capacity) % capacity; // 思路一致,在不需要分块的代码的基础上(见后面)加一个在哪个块的计算blocks[frontIndex / blockSize][frontIndex % blockSize] = e;size++;}void pop_back() {if (size == 0) {throw std::out_of_range("Deque is empty");}size--;backIndex = (backIndex - 1 + capacity) % capacity;}void pop_front() {if (size == 0) {throw std::out_of_range("Deque is empty");}size--;frontIndex = (frontIndex + 1) % capacity;}void clear() {while (size > 0) {pop_back();}}size_t getSize() const {return size;}T& operator[](size_t index) {if (index >= size) {throw std::out_of_range("index out of range");}size_t realIndex = (frontIndex + index) % capacity;return blocks[realIndex / blockSize][realIndex % blockSize];}void printElement() const {if (size == 0) {std::cout << "empty";} else {size_t cur = frontIndex;for (size_t i = 0; i < size; i++) {std::cout << blocks[cur / blockSize][cur % blockSize] << " ";cur = (cur + 1) % capacity;}}std::cout << std::endl;}private:static const size_t blockSize = 64;  // 每个块的大小std::vector<T*> blocks;  // 存储块的指针size_t capacity;  // 总容量(所有块的总和)size_t size;  // 当前元素数量size_t frontIndex;  // 队首索引size_t backIndex;  // 队尾索引void resize() { // 核心思路size_t newCapacity = 2 * capacity;size_t newBlockCount = (newCapacity + blockSize - 1) / blockSize;blocks.resize(newBlockCount);for (size_t i = capacity / blockSize; i < newBlockCount; i++) {blocks[i] = new T[blockSize];}// 重新排列元素if (frontIndex > backIndex) {size_t shift = newCapacity - capacity;for (size_t i = 0; i < backIndex; i++) {blocks[(i + shift) / blockSize][(i + shift) % blockSize] = blocks[i / blockSize][i % blockSize];}backIndex += shift;}capacity = newCapacity;}
};

块数组的引入:
使用 std::vector<T*> blocks 来存储每个块的指针。每个块的大小由 blockSize 决定。每次新分配小块数组都是固定大小(都是blockSize,就是多少个的问题)

push_back 和 push_front:(pop跟不是块数组一致)
当插入新元素时,确定插入位置是否需要跨块,如果需要,则计算正确的块索引和块内的偏移量(思路一致,在不需要分块的代码的基础上(见后面)加一个在哪个块的计算)

resize:
当当前容量不足时,resize 函数会将容量扩展到原来的两倍,并重新分配块
如果 frontIndex 在 backIndex 后面(即队列在数组中是环形排列的),则需要将后半部分元素移至新位置

void resize() { // 核心思路size_t newCapacity = 2 * capacity;size_t newBlockCount = (newCapacity + blockSize - 1) / blockSize;blocks.resize(newBlockCount);for (size_t i = capacity / blockSize; i < newBlockCount; i++) {blocks[i] = new T[blockSize];}// 重新排列元素if (frontIndex > backIndex) {size_t shift = newCapacity - capacity;for (size_t i = 0; i < backIndex; i++) {blocks[(i + shift) / blockSize][(i + shift) % blockSize] = blocks[i / blockSize][i % blockSize];}backIndex += shift;}capacity = newCapacity;}

分段数组:Deque 内部的元素存储在多个小块数组(即“blocks”)中,而不是单一的大数组中。每个块的大小是固定的(由 blockSize 定义)。
动态扩展:当 Deque 需要存储更多元素时,resize() 函数会动态增加存储空间
size_t newCapacity = 2 * capacity;:动态扩展时,通常会将容量加倍以减少未来的扩展次数
size_t newBlockCount = (newCapacity + blockSize - 1) / blockSize;:计算扩展后的块数量 newBlockCount。每个块的大小是 blockSize,因此总块数为 newCapacity 除以 blockSize。(newCapacity + blockSize - 1) 用来确保向上取整,这样可以容纳所有的新元素
blocks.resize(newBlockCount);:blocks 是存储块指针的 std::vector,通过 resize 将 blocks 的大小调整为新的块数 newBlockCount

for (size_t i = capacity / blockSize; i < newBlockCount; i++) {blocks[i] = new T[blockSize];
}

为新增的块分配内存。循环遍历 blocks 中的新块区域,为每个新块分配大小为 blockSize 的数组

if (frontIndex > backIndex) {size_t shift = newCapacity - capacity;for (size_t i = 0; i < backIndex; i++) {blocks[(i + shift) / blockSize][(i + shift) % blockSize] = blocks[i / blockSize][i % blockSize];}backIndex += shift;
}

检测队列是否环绕:frontIndex > backIndex 表示当前的队列元素在数组中是环绕排列的(即头部索引在尾部索引后面)
计算偏移量 shift:新旧容量之间的差值 shift 是新元素需要移动的步数
元素重新排列:将当前 blocks 中 backIndex 前的元素依次复制到新的位置。新的位置是在旧的位置上加上 shift 偏移后的索引。通过这个操作,确保环绕的部分正确地移动到新的空间中(将环绕的部分(即 backIndex 之前的部分)移动到新的存储空间的后半部分,以使队列不再环绕),保持 Deque 的正确顺序
更新 backIndex:将 backIndex 也相应地向前移动 shift 个位置,以反映它在新扩展后的块数组中的位置

访问和打印:
operator[] 访问时,通过计算得到正确的块索引和块内偏移量
printElement 函数遍历元素,并以正确的顺序打印出来

3、动态扩展: deque的大小可以动态调整,无需 事先分配固定大小的内存。这使得 deque 适用于需要动态增长和缩小的情况

4、内存局部性: deque的内部结构利用了多个缓冲区,有助于提高内存局部性,从而在某些情况下提供更好的性能(指程序在执行过程中访问的内存地址 具有一定的规律性,即在时间和空间上相邻的内存地址被频繁访问)
与单一的大数组相比,deque 的分段数组结构 使得每个缓冲区的大小固定且较小。这样,即使需要扩展,也不会频繁地移动大量内存数据,只需操作当前使用的缓冲区即可,这降低了缓存未命中的可能性
分段数组结构 使得最近访问的缓冲区和其中的元素很可能会被缓存,从而在访问这些数据时 具有较高的缓存命中率,这在需要频繁操作队列的前后端时尤其明显

2、deque 工作原理

backIndex指向的位置是当前末尾元素的下一个位置, 也就是还没有有效的数据(如果容量满了, 其会指向frontIndex的位置)
在这里插入图片描述

3、实现 deque(不分段)

#include <iostream>
#include <stdexcept>
#include <string>
#include <sstream>template <typename T>
class Deque {
public:Deque() : elements(nullptr), capacity(0), size(0), frontIndex(0), backIndex(0) {}~Deque() {clear();delete[] elements;}void push_back(const T& e) {if (size == capacity) {resize();}elements[backIndex] = e;backIndex = (backIndex + 1) % capacity;size++;}void push_front(const T& e) {if (size == capacity) {resize();}size_t newFrontIndex = (frontIndex - 1 + capacity) % capacity; // 为了防止数组索引为负, 使其指向尾端frontIndex = newFrontIndex;elements[frontIndex] = e;size++;}void pop_back() { // 不需要负责内存的释放,内存的释放由析构函数解决if (size == 0) {throw std::out_of_range("Deque is empty");}size--;backIndex = (backIndex - 1 + capacity) % capacity;}void pop_front() {if (size == 0) {throw std::out_of_range("Deque is empty");}size--;frontIndex = (frontIndex + 1) % capacity;}void clear() {while (size > 0) {pop_back(); // 可以复用}}size_t getSize() {return size;}T& operator[](int index) { // 参数必须是int,不然就有可能越界(size_t转换负值为未知正整数)if (index < 0 || index >= size) {throw std::out_of_range("index out of range");}return elements[(frontIndex + index) % capacity]; // 注意下标不是直接是index}void printElement() {if (size == 0) {std::cout << "empty";}size_t cur = frontIndex;for (size_t i = 0; i < size; i++) {std::cout << elements[cur] << " ";cur = (cur + 1) % capacity;}std::cout << std::endl;}
private:T* elements;size_t capacity;size_t size;size_t frontIndex;size_t backIndex;void resize() {size_t newCapacity = (capacity == 0) ? 1 : 2 * capacity;T* newElements = new T[newCapacity];size_t cur = frontIndex;for (size_t i = 0; i < size; i++) {newElements[i] = elements[cur];cur = (cur + 1) % capacity;}delete[] elements;elements = newElements;frontIndex = 0;backIndex = size;capacity = newCapacity;}}; // 类定义后别忘了;int main() {Deque<int> myDeque;int N;std::cin >> N;getchar();for (int i = 0; i < N; i++) {std::string line;std::getline(std::cin, line);std::istringstream iss(line);std::string command;iss >> command;if (command == "push_back") {int num;iss >> num;myDeque.push_back(num);}else if (command == "push_front") {int num;iss >> num;myDeque.push_front(num);}else if (command == "pop_back") { // pop要判断是否为空if (myDeque.getSize() == 0) {continue;}myDeque.pop_back();}else if (command == "pop_front") {if (myDeque.getSize() == 0) {continue;}myDeque.pop_front();}else if (command == "clear") {myDeque.clear();}else if (command == "size") {std::cout << myDeque.getSize() << std::endl;}else if (command == "get") {size_t pos;iss >> pos;std::cout << myDeque[pos] << std::endl;}else if (command == "print") {myDeque.printElement();}}return 0;
}

和 C++ STL标准库实现版本的区别:
1、标准库中的 std::deque(双端队列)通常是通过 多个连续存储区域(即一维数组)来实现的,而不是单一的连续数组, 这多个一维数组 连接起来形成了deque数组, 目前的实现采用的是循环数组, 缺点就是resize时要复制旧数组, 而官方的std::deque只需要再串联一个一维数组就可以了, 效率更高

2、内存管理: 在实际的 STL 中,内存管理通常会更加复杂和高效。STL 会使用分配器(allocator)来管理内存,而不是直接使用 new 和 delete。分配器允许用户提供自定义的内存管理策略

3、迭代器和算法支持: STL 提供了丰富的迭代器和算法支持,允许通用的方式操作容器
迭代器的支持
输入迭代器:只能进行读操作,且只能单向遍历

#include <iostream>
#include <iterator>
#include <numeric>int main() {std::istream_iterator<int> input(std::cin), eof;int sum = std::accumulate(input, eof, 0);std::cout << "Sum: " << sum << std::endl;return 0;
}

输出迭代器:只能进行写操作,且只能单向遍历

#include <iostream>
#include <iterator>
#include <algorithm>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};std::ostream_iterator<int> output(std::cout, " ");std::copy(vec.begin(), vec.end(), output);std::cout << std::endl;return 0;
}

前向迭代器:可以读写操作,并且可以多次遍历

#include <iostream>
#include <algorithm>
#include <forward_list>int main() {std::forward_list<int> flist = {1, 2, 3, 2, 1};std::replace(flist.begin(), flist.end(), 2, 10);for (int n : flist) {std::cout << n << " ";}std::cout << std::endl;return 0;
}

双向迭代器:可以前后遍历,例如 list 容器的迭代器

#include <iostream>
#include <algorithm>
#include <list>int main() {std::list<int> lst = {1, 2, 3, 4, 5};std::reverse(lst.begin(), lst.end());for (int n : lst) {std::cout << n << " ";}std::cout << std::endl;return 0;
}

随机访问迭代器:支持常数时间内的随机访问操作,例如 vector 和 deque 容器的迭代器

#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> vec = {5, 3, 9, 1, 7};std::sort(vec.begin(), vec.end());if (std::binary_search(vec.begin(), vec.end(), 3)) {std::cout << "Found!" << std::endl;}return 0;
}

算法的支持
排序算法:std::sort 可以对任何随机访问迭代器(如 vector、deque)容器进行排序
搜索算法:std::find 可以在线性时间内在任何容器中查找特定元素
修改算法:std::transform 可以对容器中的每个元素应用指定的函数并存储结果
复制算法:std::copy 可以将一个容器的内容复制到另一个容器中

4、性能优化: 实际的 STL 库通常会进行更多的性能优化,包括使用更复杂的数据结构、考虑缓存友好性等

5、标准兼容性: 实际的 STL 遵循 C++ 标准,确保在不同的平台和编译器上的一致行为。它还提供了许多其他功能,如分配器的自定义、迭代器标签(iterator tags)等

4、高频面试题

1、deque 和 vector 的主要区别是什么
内存分配:vector 使用单一连续的内存空间来存储元素,而 deque 使用多个分散的内存块。这意味着 deque 可以在两端进行高效的插入和删除操作,而 vector 只能在尾部高效地进行这些操作
插入效率:在 deque 的前端和后端插入或删除元素的操作效率很高,但在中间插入或删除元素效率较低,因为它需要移动多个内存块中的元素。而 vector 在尾部插入和删除元素高效(头部不行),但在前端或中间进行这些操作效率较低,因为可能需要移动大量元素
随机访问:vector 提供了更快的随机访问性能,因为它使用连续内存,而 deque 由于其分散的内存块,随机访问性能稍低
内存耗费:vector 在扩展时可能会有较大的内存重新分配成本,而 deque 因为是分块存储,所以通常不需要大量的内存重新分配

2、使用 deque 实现固定大小滑动窗口

#include <iostream>
#include <deque>class SlidingWindow {
public:SlidingWindow(size_t windowSize) : maxSize(windowSize) {}void add(int value) {// 如果窗口已满,则删除最早的元素if (window.size() == maxSize) {window.pop_front();}// 添加新元素到窗口window.push_back(value);}double getAverage() const {if (window.empty()) return 0.0;double sum = 0;for (int value : window) {sum += value;}return sum / window.size();}void printWindow() const {for (int value : window) {std::cout << value << " ";}std::cout << std::endl;}private:std::deque<int> window;size_t maxSize;
};

3、在 deque 中使用 [] 操作符和 at() 方法有何区别
operator[] 提供对元素的无检查访问,这意味着如果使用超出范围的索引,它不会抛出异常,而是导致未定义行为
at() 方法提供边界检查的访问。如果指定的索引超出了 deque 的范围,at() 方法会抛出一个 std::out_of_range 异常。因此,at() 比 operator[] 更安全,但可能会略微降低性能
使用 at() 方法可以帮助调试中发现越界错误,而 operator[] 在确定不会越界时可以提供更好的性能

4、在 deque 的前端插入一个元素时,迭代器会发生什么

  1. std::deque 的行为
    std::deque 是一个双端队列,允许在两端快速插入和删除元素。与 std::vector 不同,std::deque 是分段存储的,因此在内部的实现中有多个内存块
    1)前端插入:
    在 std::deque 的前端插入元素时,通常不会导致整个容器的重新分配。但如果前端没有足够的空间(即最前面的块已满),std::deque 可能会在前端分配一个新的块
    由于 std::deque 可能需要移动或重新分配内部的内存块,因此所有的迭代器、引用和指针可能会失效
    2)后端插入:
    在 std::deque 的尾部插入元素时,情况与前端插入类似。如果后端没有足够的空间,可能需要分配新的块,从而导致迭代器、引用和指针失效
  2. std::vector 是一个动态数组,当在尾部插入元素时,其行为如下:
    1)尾部插入:
    如果 std::vector 的当前容量足够,那么插入新元素不会导致迭代器、引用和指针失效
    但是,如果插入新元素导致 std::vector 的容量不足,它会分配更大的内存块,并将现有的元素复制到新分配的内存中。这种情况下,所有指向旧元素的迭代器、引用和指针都会失效
    2)前端或中间插入:
    在 std::vector 的前端或中间插入元素时,即使没有触发容量重新分配,由于元素需要移动以为新元素腾出空间,指向被移动元素的迭代器、引用和指针也会失效

5、 解释 deque 的内部工作机制。它是如何实现 两端插入和删除操作的高效性的
std::deque 维护了一系列定长数组(称为块或缓冲区),并且有一个中央控制器(通常是指针数组)来管理这些块。每个块可以独立地增长和收缩,这意味着当在两端添加或删除元素时,只有相关联的块受到影响

在前端插入:如果第一个块有空间,则在该块的开始插入新元素。如果没有空间,deque 会分配一个新块 并将其链接到现有的块序列中
在后端插入:如果最后一个块有空间,则在该块的末尾插入新元素。如果没有空间,deque 会分配一个新块 并将其链接到现有的块序列中
在前端删除:移除第一个块的第一个元素。如果该块变空,则释放该块资源 并更新中央控制器
在后端删除:移除最后一个块的最后一个元素。如果该块变空,则释放该块资源 并更新中央控制器

6、deque 的构造函数

std::deque<int> dq; // 创建一个空的 dequestd::deque<int> dq(10, 5); // 10个元素,每个都是5std::vector<int> vec{1, 2, 3, 4, 5};
std::deque<int> dq(vec.begin(), vec.end()); // 复制给定迭代器范围内的元素std::deque<int> dq1(10, 5);
std::deque<int> dq2(dq1); // 拷贝构造函数std::deque<int> dq1(10, 5);
std::deque<int> dq2(std::move(dq1)); // 移动构造函数std::deque<int> dq{1, 2, 3, 4, 5}; // 使用一个初始化列表来创建// 带有分配器的默认构造函数:创建一个空的 deque,但是使用自定义的分配器来分配内存
std::allocator<int> alloc;
std::deque<int, std::allocator<int>> dq(alloc);std::allocator<int> alloc;
std::deque<int, std::allocator<int>> dq(10, 5, alloc); // 10个元素,每个都是5,使用自定义分配器std::vector<int> vec{1, 2, 3, 4, 5};
std::allocator<int> alloc;
std::deque<int, std::allocator<int>> dq(vec.begin(), vec.end(), alloc);std::deque<int> dq1(10, 5);
std::allocator<int> alloc;
std::deque<int, std::allocator<int>> dq2(dq1, alloc); // 拷贝dq1,使用自定义分配器std::deque<int> dq1(10, 5);
std::allocator<int> alloc;
std::deque<int, std::allocator<int>> dq2(std::move(dq1), alloc); // 移动dq1,使用自定义分配器std::allocator<int> alloc;
std::deque<int, std::allocator<int>> dq({1, 2, 3, 4, 5}, alloc);

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

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

相关文章

Servlet 简介+ Cookie和session+过滤器Filter和监听器Listener

目录 1.Servlet 介绍 1.1 什么是Servlet 1.2 Servlet的使用方法 1.3 Servlet接口的继承结构 2.Servlet的生命周期 2.1 servlet生命周期中重要的方法 3.获得前端提交数据 4.中文乱码的解决方案 5.重定向和转发 5.1 重定向 5.2 转发 6. Request对象 7. Response对象…

Linux上启动redis

1.默认启动方式:在系统的任意位置执行 redis-server即可启动 ps:这是前端界面启动&#xff0c;无法直接连接redis&#xff0c;想要连接的话只能另外启动一个窗口&#xff0c;因此下面我们介绍后台启动redis 2.指定配置启动&#xff1a; redis的配置文件位置&#xff1a…

华为手机数据丢失如何恢复?

在智能手机普及的今天&#xff0c;华为手机凭借其卓越的性能和用户体验赢得了众多用户的青睐。然而&#xff0c;在使用过程中&#xff0c;我们难免会遇到数据丢失或误删除的情况。面对这一困境&#xff0c;许多用户可能会感到束手无策。别担心&#xff0c;本文将为你提供一份全…

FastGPT:利用大模型重新定义传统知识库

引言 传统知识库的痛点 传统知识库广泛应用于企业文档管理、客户支持等场景&#xff0c;但随着信息量和复杂度的增加&#xff0c;存在以下显著问题&#xff1a; 数据难整合&#xff1a; 结构化与非结构化数据分散&#xff0c;更新维护成本高。检索不精准&#xff1a; 依赖关…

【前端开发必备小技巧】前端代码规范Vue篇

文章目录 &#x1f7e2; 前端代码规范&#x1f7e2; 一、前端代码规范Vue篇&#x1f449;1、Vue编码基础&#x1f449;1.1、组件规范&#x1f449;1.2、模板中使用简单的表达式&#x1f449;1.3、指令都使用缩写形式&#x1f449;1.4、 标签顺序保持一致&#x1f449;1.5、必须…

【Kotlin设计模式】Kotlin实现适配器模式

前言 适配器模式(Adapter Pattern)的核心将某个类的接口转换成客户端期望的另一个接口表示&#xff0c;使得客户端能够通过自己期望的接口与不兼容的类进行交互。适配器模式有三种实现方式&#xff0c;分别是类适配器模式、对象适配器模式、 接口适配器模式。 我们假设有个视频…

3D Tiles的4x4的仿射变换矩阵

前言 项目需要&#xff0c;使用Cesium技术&#xff0c;把STL格式模型加载进去。 一、格式转换 第一步&#xff0c;先将STL文件转换为glTF格式 第二步&#xff0c;将glTF文件转换为3D Tiles格式&#xff0c;使用Cesium ion 二、矩阵整体结构 这个矩阵是一个4x4的仿射变换矩阵&…

LeetCode题练习与总结:单词搜索Ⅱ--212

一、题目描述 给定一个 m x n 二维字符网格 board 和一个单词&#xff08;字符串&#xff09;列表 words&#xff0c; 返回所有二维网格上的单词 。 单词必须按照字母顺序&#xff0c;通过 相邻的单元格 内的字母构成&#xff0c;其中“相邻”单元格是那些水平相邻或垂直相邻…

中智科学技术评价研究中心与中企数研究院实现全面合作

8月29日&#xff0c;中智科学技术评价研究中心与《中国企业报》集团数字化发展研究院在北京顺喜山庄成功举办“数字经济GBC生态系统管理平台”项目实施落地座谈会及研究院高层管理集训班&#xff0c;并签署了项目合作协议。此次合作标志着双方将在“数字中国发展战略”的大背景…

人工智能领域正经历模型规模变革,小型语言模型(SLM)崛起,挑战“规模至上”观念。

在人工智能领域&#xff0c;一场关于模型规模的深刻变革正在悄然发生。长久以来&#xff0c;科技巨头们热衷于庞大语言模型&#xff08;LLM&#xff09;的开发竞赛&#xff0c;但如今&#xff0c;小型语言模型&#xff08;SLM&#xff09;正以其独特的优势逐步崭露头角&#xf…

WordNet介绍——一个英语词汇数据库

传统语义知识库最常见的更新方法是依赖人工手动更新&#xff0c;使用这种更新方法的语义知识库包括最早的 WordNet、FrameNet和 ILD&#xff0c;以及包含丰富内容的 ConceptNet和 DBPedia。此类语义知识库的特点是以单词作为语义知识库的基本构成元素&#xff0c;以及使用预先设…

Linux安装Hadoop(单机版)详细教程

目录 一、JDK安装 1、下载JDK安装包 2、解压下载的JDK安装包 3、移动并重命名JDK包 4、配置Java环境变量 5、验证安装是否成功 二、Hadoop安装 1、下载Hadoop安装包 2、解压Hadoop安装包 3、配置Hadoop环境变量 4、修改配置文件 5、验证Hadoop是否安装成功 三&…

代码随想录——回文子串(Leetcode 647)

题目链接 我的题解&#xff08;双指针&#xff09; 思路&#xff1a; 当然&#xff0c;以下是对您提供的代码的解释&#xff1a; class Solution {public int countSubstrings(String s) {// 初始化回文子字符串的数量int count 0;// 遍历字符串的每个字符&#xff0c;使用…

NCH DrawPad Pro for Mac/Win:强大的图像编辑处理软件

NCH DrawPad Pro for Mac/Win是一款功能全面的图像编辑和设计软件&#xff0c;专为Mac和Windows用户设计。它不仅适用于专业设计师&#xff0c;也深受业余爱好者和创意工作者的喜爱。DrawPad Pro凭借其丰富的绘图工具、强大的编辑功能和便捷的模板库&#xff0c;为用户提供了卓…

OpenCV杂项图像变换(2)线性混合函数blendLinear()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 执行两个图像的线性混合&#xff1a; dst ( i , j ) weights1 ( i , j ) ∗ src1 ( i , j ) weights2 ( i , j ) ∗ src2 ( i , j ) \texttt{…

策略模式+模版方法模式+简单工厂模式混用优化代码复杂分支问题

说明 这篇博客是在复杂场景使用策略和工厂模式代替分支语句升级版&#xff0c;增加了模版方法模式。将支付类的公共逻辑抽取到模板类中&#xff0c;使整个支付逻辑更加灵活&#xff0c;进一步优化了代码结构&#xff0c;提升了软件的可维护性和可读性。 流程图如下 先看一遍流…

.NET 多版本兼容的精美 WinForm UI控件库

目录 前言 项目介绍 项目使用 项目源码 项目案例 项目组件 项目地址 前言 有粉丝小伙伴在后台留言咨询有没有WinForm 控件库推荐&#xff0c;现在就给安排上。 .NET 平台进行 Windows 应用程序开发的我们来说&#xff0c;找一个既美观又实用的 WinForm UI 控件库至关重…

STM32通过ADM3222完成UART转232通信电平转换

1、简介 单片机默认串口输出电平是UART信号,但是在实际项目中经常需要将其转换成232电平,此时就需要ADM3222芯片来完成电平的转换,下面对使用过程进行总结。 2、硬件电路 从上图中可以看到芯片需要对1、18进行配置才能进行工作,通过查阅手册可知,1引脚需要配置低电平,…

云计算实训37——Dockerfile的应用+私有仓库的创建与管理

一、文件联合系统 文件联合系统&#xff08;Overlay Filesystem&#xff09;&#xff1a; 是一种允许多个文件系统层叠在一起的文件系统。它通常用于将只读文件系统&#xff08;如基础镜像&#xff09;与可写文件系统&#xff08;如用户的修改&#xff09;结合&#xff0c;形…

【3D目标检测】MMdetection3d——nuScenes数据集训练BEVFusion

引言 MMdetection3d&#xff1a;【3D目标检测】环境搭建&#xff08;OpenPCDet、MMdetection3d&#xff09; MMdetection3d源码地址&#xff1a;https://github.com/open-mmlab/mmdetection3d/tree/main?tabreadme-ov-file IS-Fusion源码地址&#xff1a;https://github.co…