【C++】21.map和set的使用
文章目录
- 1. 序列式容器和关联式容器
- 2. set系列的使用
- 2.1 set和multiset参考文档
- 2.2 set类的介绍
- 2.3 set的构造和迭代器
- 构造函数:
- 双向迭代器
- 迭代器:
- 2.4 set的增删查
- 2.5 insert和迭代器遍历使用样例:
- 2.6 find和erase使用样例:
- 2.7 multiset和set的差异
- 2.8 349. 两个数组的交集 - 力扣(LeetCode)
- 2.9 142. 环形链表 II - 力扣(LeetCode)
- 3. map系列的使用
- 3.1 map和multimap参考文档
- 3.2 map类的介绍
- 3.3 pair类型介绍
- 3.4 map的构造
- 3.5 map的增删查
- 3.6 map的数据修改
- 3.7 构造遍历及增删查使用样例
- 3.8 map的迭代器和[]功能样例:
- 3.9 multimap和map的差异
- 3.10 138. 随机链表的复制 - 力扣(LeetCode)
- 3.11 692. 前K个高频单词 - 力扣(LeetCode)
- 解决思路1:
- 解决思路2:
1. 序列式容器和关联式容器
前面我们已经接触过STL
中的部分容器如:string、vector、list、deque、array、forward_list
等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,比如交换一下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。
关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构,两个位置有紧密的关联关系,交换一下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set
系列和unordered_map/unordered_set
系列。
本章节讲解的map
和set
底层是红黑树,红黑树是一颗平衡二叉搜索树。set
是key
搜索场景的结构,map
是key/value
搜索场景的结构。
2. set系列的使用
2.1 set和multiset参考文档
https://legacy.cplusplus.com/reference/set/
2.2 set类的介绍
-
set
的声明如下,T
就是set
底层关键字的类型 -
set
默认要求T
支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第二个模版参数 -
set
底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。 -
一般情况下,我们都不需要传后两个模版参数。
-
set
底层是用红黑树实现,增删查效率是O(logN)
,迭代器遍历是走的搜索树的中序,所以是有序的。 -
前面部分我们已经学习了
vector/list
等容器的使用,STL
容器接口设计,高度相似,所以这里我们就不再一个接口一个接口的介绍,而是直接挑比较重要的接口进行介绍。
template < class T, // set::key_type/value_typeclass Compare = less<T>, // set::key_compare/value_compareclass Alloc = allocator<T> // set::allocator_type>
class set; // 声明这是一个名为set的类
这是C++ STL中set容器的模板声明:
-
template <...>
- 这是一个模板类声明,包含三个模板参数 -
第一个参数
class T
- 这是
set
存储的元素类型 - 也是
key
和value
的类型(在set
中key
就是value
) - 必须显式指定,没有默认值
- 这是
-
第二个参数
class Compare = less<T>
- 用于元素比较的仿函数类型
- 默认使用
less<T>
,即按升序排序 - 你可以自定义比较器来改变排序方式
-
第三个参数
class Alloc = allocator<T>
- 内存分配器类型
- 默认使用标准
allocator
- 通常不需要修改此参数
2.3 set的构造和迭代器
set
的构造我们关注以下几个接口即可。
set
的支持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for
,set
的iterator
和const_iterator
都不支持迭代器修改数据,修改关键字数据,破坏了底层搜索树的结构。
// empty (1) 无参默认构造
explicit set (const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>set (InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type& = allocator_type());// copy (3) 拷贝构造
set (const set& x);
// initializer list (5) initializer 列表构造
set (initializer_list<value_type> il,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());// 迭代器是一个双向迭代器
iterator -> a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();
构造函数:
- 默认构造函数
explicit set (const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
- 创建空
set
explicit
防止隐式转换- 可选参数:比较器和分配器
- key_compare& comp = key_compare()
- 比较器参数,默认是
less<T>
- 用于维护
set
的元素顺序
- 比较器参数,默认是
- allocator_type& alloc = allocator_type()
- 内存分配器参数
- 默认使用
std::allocator
- 负责
set
内存的分配和释放
- 迭代器区间构造
template <class InputIterator>
set (InputIterator first, // 起始迭代器InputIterator last, // 结束迭代器const key_compare& comp = key_compare(), // 可选的比较器const allocator_type& = allocator_type() // 可选的分配器
);
- 用迭代器范围内的元素构造
set
- 常用于从其他容器构造
set
template <class InputIterator>
- 接受任何满足
InputIterator
(输入迭代器)要求的迭代器类型 - 可以是来自不同容器的迭代器,只要它们指向的元素类型可以转换为
set
的元素类型
- 接受任何满足
- 拷贝构造
set (const set& x);
- 复制另一个
set
创建新set
- 初始化列表构造
set (initializer_list<value_type> il, // 初始化列表const key_compare& comp = key_compare(), // 可选的比较器const allocator_type& alloc = allocator_type() // 可选的分配器
);
- 使用初始化列表构造
set
双向迭代器
iterator -> a bidirectional iterator to const value_type
描述的是set
迭代器的特性:
-
双向迭代器(bidirectional iterator)
- 可以前后移动(++和–操作)
- 不支持随机访问(不能+n或-n)
- 移动方向:向前(++)或向后(–)
-
const value_type
set
中的元素不能通过迭代器修改- 这是因为修改可能破坏
set
的有序性
示例代码:
set<int> s = {1, 2, 3, 4, 5};// 正确用法
set<int>::iterator it = s.begin();
cout << *it; // 可以读取
++it; // 可以向前
--it; // 可以向后// 错误用法
*it = 10; // 编译错误:不能修改元素
it += 2; // 编译错误:不支持随机访问
it = it + 2; // 编译错误:不支持随机访问// 正确的遍历方式
for(set<int>::iterator it = s.begin(); it != s.end(); ++it) {cout << *it << " "; // 只能读取,不能修改
}// 现代C++的简化写法
for(const auto& element : s) {cout << element << " ";
}
与vector
的随机访问迭代器相比:
vector<int> v = {1, 2, 3, 4, 5};
vector<int>::iterator vit = v.begin();vit += 2; // vector支持随机访问
*vit = 10; // vector允许修改元素
迭代器:
- 正向迭代器
iterator begin(); // 指向第一个元素
iterator end(); // 指向最后一个元素之后的位置
- 反向迭代器
reverse_iterator rbegin(); // 指向最后一个元素
reverse_iterator rend(); // 指向第一个元素之前的位置
使用示例:
// 不同构造方式
set<int> s1; // 默认构造
set<int> s2({1,2,3,4}); // 初始化列表构造
vector<int> vec = {1,2,3,4};
set<int> s3(vec.begin(), vec.end()); // 迭代器区间构造
set<int> s4(s3); // 拷贝构造// 迭代器使用
for(auto it = s1.begin(); it != s1.end(); ++it) {// 正向遍历
}for(auto it = s1.rbegin(); it != s1.rend(); ++it) {// 反向遍历
}
2.4 set的增删查
set
的增删查关注以下几个接口即可:
Member types
key_type -> The first template parameter (T)
value_type -> The first template parameter (T)// 单个数据插入,如果已经存在则插入失败
pair<iterator,bool> insert (const value_type& val);
// 列表插入,已经在容器中存在的值不会插入
void insert (initializer_list<value_type> il);
// 迭代器区间插入,已经在容器中存在的值不会插入
template <class InputIterator>void insert (InputIterator first, InputIterator last);
// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find (const value_type& val);
// 查找val,返回Val的个数
size_type count (const value_type& val) const;
// 删除一个迭代器位置的值
iterator erase (const_iterator position);
// 删除val,val不存在返回0,存在返回1
size_type erase (const value_type& val);
// 删除一段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回大于等val位置的迭代器
iterator lower_bound (const value_type& val) const;
// 返回大于val位置的迭代器
iterator upper_bound (const value_type& val) const;
2.5 insert和迭代器遍历使用样例:
#include<iostream>
#include<set> // 包含set容器头文件
using namespace std;int main()
{// set默认情况:// 1. 去重:相同元素只保留一个// 2. 升序:使用less<T>比较器,从小到大排序set<int> s;// 如果想降序,可以使用greater<int>比较器//set<int, greater<int>> s; // 注释掉的降序写法// 插入元素,重复的5只会保存一次s.insert(5);s.insert(2);s.insert(7);s.insert(5); // 这个5会插入失败,因为已经存在// 使用迭代器遍历//set<int>::iterator it = s.begin(); // 完整的迭代器类型声明auto it = s.begin(); // 使用auto自动推导类型while (it != s.end()){// set中的元素不能修改,因为修改可能会破坏有序性// *it = 1; // 错误:set迭代器指向的元素是只读的cout << *it << " "; // 只能读取元素++it; // 迭代器移动到下一个位置}cout << endl;// 使用初始化列表插入多个元素// 如果元素已存在(如2),则该元素插入失败// 新元素(如3,8,9)会插入并保持有序s.insert({ 2,8,3,9 });// 使用范围for循环遍历for (auto e : s){cout << e << " ";}cout << endl;// 创建string类型的set// 使用初始化列表构造set<string> strset = { "sort", "insert", "add" };// string比较规则:// 1. 按字典序(ASCII码)比较// 2. 从第一个字符开始,按位比较ASCII值// 输出会按字典序排列:"add" "insert" "sort"for (auto& e : strset){cout << e << " ";}cout << endl;return 0;
}
补充说明:
set
的特性:
- 元素唯一性(去重)
- 自动排序
- 元素不可修改(
const
)- 排序规则:
- 默认使用
less<T>
,升序排序- 可以使用
greater<T>
或自定义比较器实现降序或其他排序方式string
的排序:
- 按字典序比较,即
ASCII
码值的大小- 例如:“
add
” < “insert
” < “sort
”- 迭代器:
set
的迭代器是只读的,不能通过迭代器修改元素- 因为修改可能破坏
set
的有序性
2.6 find和erase使用样例:
#include<iostream>
#include<set> // 包含set容器头文件
using namespace std;int main()
{// 使用初始化列表构造set// set会自动排序并去重:{2,4,5,7,8,9}set<int> s = { 4,2,7,2,8,5,9 }; // 使用范围for循环遍历set中的元素for (auto e : s){cout << e << " "; // 输出每个元素}cout << endl;// 删除最小值(即第一个元素)// s.begin()返回指向第一个元素的迭代器s.erase(s.begin());// 再次遍历输出set中的元素for (auto e : s){cout << e << " ";}cout << endl;// 方式1:直接删除值为x的元素int x;cin >> x;// erase返回实际删除的元素个数:存在则返回1,不存在返回0int num = s.erase(x);if (num == 0){cout << x << "不存在!" << endl;}// 输出删除后的结果for (auto e : s){cout << e << " ";}cout << endl;// 方式2:先查找再删除cin >> x;// find返回指向找到元素的迭代器,如果没找到返回end()auto pos = s.find(x);if (pos != s.end()) // 找到了{s.erase(pos); // 使用迭代器删除元素}else{cout << x << "不存在!" << endl;}// 输出删除后的结果for (auto e : s){cout << e << " ";}cout << endl;// 两种查找方式的比较// 1. 算法库的find:遍历查找,时间复杂度O(N)auto pos1 = find(s.begin(), s.end(), x); // 2. set容器自带的find:二叉搜索树查找,时间复杂度O(logN)auto pos2 = s.find(x); // 使用count间接实现快速查找// count对于set来说返回值只可能是0或1,因为set不允许重复cin >> x;if (s.count(x)) // 如果count返回1,表示存在{cout << x << "在!" << endl;}else // 如果count返回0,表示不存在{cout << x << "不存在!" << endl;}return 0;
}
#include<iostream>
#include<set> // 包含set容器头文件
using namespace std;int main()
{// 创建空的set容器std::set<int> myset;// 插入1-9的10倍数到set中for (int i = 1; i < 10; i++)myset.insert(i * 10); // 插入后set内容:10 20 30 40 50 60 70 80 90// 遍历并打印set中的所有元素for (auto e : myset){cout << e << " ";}cout << endl;// lower_bound(key):返回指向大于或等于key的第一个元素的迭代器// 这里返回指向30的迭代器,因为30是第一个大于等于30的元素auto itlow = myset.lower_bound(30);// upper_bound(key):返回指向大于key的第一个元素的迭代器// 这里返回指向70的迭代器,因为70是第一个大于60的元素auto itup = myset.upper_bound(60);// erase(first, last):删除[first,last)区间的所有元素// 这里删除[30,60]这个闭区间的所有元素// 即删除30,40,50,60这些元素myset.erase(itlow, itup);// 再次遍历打印,检查删除结果// 预期输出:10 20 70 80 90for (auto e : myset){cout << e << " ";}cout << endl;return 0;
}
补充说明:
lower_bound
和upper_bound
都是基于二分搜索实现的,时间复杂度为O(logN)
- 区间
[itlow,itup)
是一个左闭右开区间:
itlow
指向第一个>=30
的元素(即30
)itup
指向第一个>60
的元素(即70
)- 因此这个区间包含了
30,40,50,60
这些元素- 如果要删除的
key
不存在,lower_bound
和upper_bound
会返回最接近的位置:
- 如果
key
小于所有元素,返回begin()
- 如果
key
大于所有元素,返回end()
2.7 multiset和set的差异
multiset
和set
的使用基本完全类似,主要区别点在于multiset
支持值冗余,那么insert/find/count/erase
都围绕着支持值冗余有所差异,具体参看下面的样例代码理解。
#include<iostream>
#include<set> // 包含set/multiset容器头文件
using namespace std;int main()
{// multiset特点:// 1. 有序:默认按照小于比较,从小到大排序// 2. 允许重复值存在// 排序后结果为:{2,2,4,4,4,4,5,7,8,9}multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };// 使用迭代器遍历multiset中的所有元素auto it = s.begin(); // 获取指向第一个元素的迭代器while (it != s.end()) // 当迭代器未到达末尾时继续循环{cout << *it << " "; // 解引用迭代器,输出当前元素++it; // 迭代器移动到下一个位置}cout << endl;// 查找元素xint x;cin >> x;// find在multiset中返回第一个等于x的元素位置// 如果有多个相同元素,find返回中序遍历遇到的第一个auto pos = s.find(x);// 通过迭代器遍历所有等于x的元素// 因为multiset是有序的,所有相等的元素都是相邻的while (pos != s.end() && *pos == x){cout << *pos << " "; // 输出当前找到的x++pos; // 移动到下一个位置继续查找}cout << endl;// count返回元素x在multiset中出现的次数// 而在set中count只会返回0或1cout << s.count(x) << endl;// erase删除所有值等于x的元素// 而在set中最多只会删除一个元素s.erase(x);// 使用范围for循环遍历删除后的结果for (auto e : s){cout << e << " ";}cout << endl;return 0;
}
multiset
与set
的主要区别:
multiset
允许重复元素,set
不允许find
在multiset
中返回第一个匹配的元素位置count
在multiset
中返回实际出现次数,在set
中只返回0
或1
erase
在multiset
中删除所有匹配的元素,在set
中最多删除一个
2.8 349. 两个数组的交集 - 力扣(LeetCode)
349. 两个数组的交集 - 力扣(LeetCode)
class Solution {
public:// 函数功能:求两个数组的交集// 参数:两个整型vector数组的引用// 返回值:包含交集元素的vectorvector<int> intersection(vector<int>& nums1, vector<int>& nums2) {// 构造两个set,利用set自动去重和排序的特性// 用nums1和nums2的迭代器区间初始化setset<int> s1(nums1.begin(), nums1.end()); // nums1中的不重复元素set<int> s2(nums2.begin(), nums2.end()); // nums2中的不重复元素// 创建结果数组,用于存储交集元素vector<int> ret;// 获取两个set的起始迭代器auto it1 = s1.begin();auto it2 = s2.begin();// 同时遍历两个set,直到其中一个遍历完while(it1 != s1.end() && it2 != s2.end()){if(*it1 < *it2) {// 如果s1当前元素小,移动s1的迭代器it1++;}else if(*it1 > *it2){// 如果s2当前元素小,移动s2的迭代器it2++;}else // *it1 == *it2{// 相等说明是交集元素ret.push_back(*it1); // 将交集元素加入结果数组// 两个迭代器都要往后移动it1++;it2++;}}return ret; // 返回结果数组}
};
算法思路解析:
- 预处理:
- 将两个数组转换为
set
,实现去重和排序- 时间复杂度:
O(NlogN)
,空间复杂度:O(N)
- 求交集:
- 利用
set
有序的特性,用双指针(迭代器)同时遍历两个set
- 类似归并排序的思路,比较两个当前元素:
- 如果
s1
当前元素小,移动s1
迭代器- 如果
s2
当前元素小,移动s2
迭代器- 如果相等,就是交集元素,加入结果数组,两个迭代器都移动
- 时间复杂度:
O(min(N,M))
,N
和M
是两个set
的大小- 优势:
- 利用
set
的特性自动去重和排序- 双指针遍历的方式效率高
- 代码简洁易懂
2.9 142. 环形链表 II - 力扣(LeetCode)
142. 环形链表 II - 力扣(LeetCode)
数据结构初阶阶段,我们通过证明一个指针从头开始走一个指针从相遇点开始走,会在入口点相遇,理解证明都会很麻烦。这里我们使用set
查找记录解决非常简单方便,这里体现了set
在解决一些问题时的价值,完全是降维打击。
class Solution {
public:// 函数功能:检测链表中的环,并返回环的入口节点// 参数:链表头节点指针// 返回值:如果有环,返回环的入口节点;如果无环,返回nullptrListNode *detectCycle(ListNode *head) {// 创建一个存储ListNode指针的set容器// 用于记录已经访问过的节点地址set<ListNode*> s;// cur指针用于遍历链表ListNode* cur = head;// 遍历链表while(cur){// 尝试将当前节点地址插入set中// insert返回一个pair:// - first是一个迭代器,指向插入位置// - second是一个bool值,表示插入是否成功// - true表示插入成功(之前没有这个元素)// - false表示插入失败(已经存在这个元素)auto ret = s.insert(cur);// 如果插入失败(ret.second为false)// 说明这个节点之前已经访问过// 也就是找到了环的入口if(ret.second == false)return cur; // 返回环的入口节点// 继续遍历下一个节点cur = cur->next;}// 如果遍历完整个链表都没有找到重复节点// 说明链表中没有环return nullptr;}
};
算法思路解析:
- 基本思想:
- 用
set
存储已经访问过的节点地址- 如果某个节点地址已经在
set
中存在,说明找到了环- 具体步骤:
- 创建
set
存储节点地址- 遍历链表,尝试将每个节点地址插入
set
- 如果插入失败,说明这个地址之前已存在,即找到环的入口
- 如果遍历结束都没有重复地址,说明无环
- 时间复杂度分析:
O(N)
,N
为链表节点数- 每个节点最多被访问一次
set
的插入操作是O(logN)
- 空间复杂度分析:
O(N)
,需要额外空间存储访问过的节点地址
3. map系列的使用
3.1 map和multimap参考文档
https://legacy.cplusplus.com/reference/map/
3.2 map类的介绍
map
的声明如下,Key
就是map
底层关键字的类型,T
是map
底层value
的类型,set
默认要求Key
支持小于比较,如果不支持或者需要的话可以自行实现仿函数传给第二个模版参数,map
底层存储数据的内存是从空间配置器申请的。一般情况下,我们都不需要传后两个模版参数。map
底层是用红黑树实现,增删查改效率是O(logN)
,迭代器遍历是走的中序,所以是按key
有序顺序遍历的。
template < class Key, // map::key_typeclass T, // map::mapped_typeclass Compare = less<Key>, // map::key_compareclass Alloc = allocator<pair<const Key,T> > // map::allocator_type>
class map;
- Key - key_type(键类型)
template <class Key> // 第一个模板参数
- 用作
map
中的键 - 必须支持比较操作(能够排序)
- 不可重复
- T - mapped_type(值类型)
class T // 第二个模板参数
- 存储的实际数据类型
- 可以修改
- 没有特殊要求
- Compare - key_compare(比较器)
class Compare = less<Key> // 第三个模板参数,有默认值
- 用于键的排序
- 默认使用
less<Key>
- 可自定义比较规则
- Alloc - allocator_type(分配器)
class Alloc = allocator<pair<const Key,T>> // 第四个模板参数,有默认值
- 内存分配器
- 注意是
pair
的分配器 - 键值对
pair
中的Key
是const
的
3.3 pair类型介绍
map
底层的红黑树节点中的数据,使用pair<Key, T>
存储键值对数据。
// value_type定义了map中存储的键值对类型
// Key是const的,防止键被修改
typedef pair<const Key, T> value_type;// pair是一个通用的模板结构体,用于存储两个不同类型的值
template <class T1, class T2>
struct pair
{// 定义first成员的类型别名typedef T1 first_type;// 定义second成员的类型别名typedef T2 second_type;// pair的两个公有成员变量T1 first; // 第一个成员T2 second; // 第二个成员// 默认构造函数// 使用T1和T2的默认构造函数初始化first和secondpair(): first(T1()), second(T2()){}// 带参数的构造函数// @param a: first成员的初始值// @param b: second成员的初始值pair(const T1& a, const T2& b): first(a), second(b){}// 拷贝构造函数模板// 允许从不同类型的pair进行构造(如果类型可以转换)// @param pr: 源pair对象// @template U: 源pair的first类型// @template V: 源pair的second类型template<class U, class V> pair (const pair<U,V>& pr): first(pr.first), second(pr.second){}
};// 辅助函数:创建pair对象的便捷方式
// 通过类型推导自动确定pair的类型
// @param x: 第一个值
// @param y: 第二个值
// @return: 返回构造好的pair对象
template <class T1,class T2>
inline pair<T1,T2> make_pair (T1 x, T2 y)
{return ( pair<T1,T2>(x,y) );
}
为什么写成pair(): first(T1()), second(T2())
而不是pair(): first(T1), second(T2)
- T1() vs T1 的区别:
pair(): first(T1()), second(T2()) // 值初始化
pair(): first(T1), second(T2) // 默认初始化
- 对于不同类型的影响:
// 基本类型(int, double等)
int n1(); // 值初始化 -> 0
int n2; // 默认初始化 -> 未定义值// 示例
pair<int, int> p1; // 使用T1()
// p1.first = 0, p1.second = 0pair<int, int> p2; // 如果使用T1
// p1.first = 未定义值, p1.second = 未定义值
3.4 map的构造
map
的构造我们关注以下几个接口即可。
map
的支持正向和反向迭代遍历,遍历默认按key
的升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,map
支持修改value
数据,不支持修改key
数据,修改关键字数据,破坏了底层搜索树的结构。
// empty (1) 无参默认构造
explicit map (const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
// - explicit防止隐式转换
// - key_compare默认是less<Key>
// - allocator_type是内存分配器,默认使用标准分配器
// range (2) 迭代器区间构造
template <class InputIterator> // 模板参数,可以接受任何输入迭代器类型
map (InputIterator first, // 起始迭代器InputIterator last, // 结束迭代器const key_compare& comp = key_compare(), // 比较器const allocator_type& = allocator_type()); // 分配器
// copy (3) 拷贝构造
map (const map& x);
// initializer list (5) initializer 列表构造
// 4. 初始化列表构造函数
map (// 接收初始化列表作为参数,类型为 initializer_list// 其中 value_type 通常是 pair<const Key, T>// 例如: {"key", value} 这样的初始化形式initializer_list<value_type> il,// 接收比较函数对象,用于定义键的排序规则// key_compare 默认是 less<Key>// comp 参数可选,如果不传则使用默认构造的 key_compareconst key_compare& comp = key_compare(),// 接收内存分配器,用于管理map的内存分配// allocator_type 是分配器类型// alloc 参数可选,如果不传则使用默认构造的 allocator_typeconst allocator_type& alloc = allocator_type()
);// 迭代器是一个双向迭代器
// 这表示:
// 1. 迭代器是双向的
// 2. 键是const的,不能修改
// 3. value_type是pair<const Key, T>类型
iterator -> a bidirectional iterator to const value_type// 正向迭代器// 正向迭代器声明
iterator begin(); // 返回指向第一个元素的迭代器
iterator end(); // 返回指向最后一个元素后面的迭代器// 反向迭代器声明
reverse_iterator rbegin(); // 返回指向最后一个元素的反向迭代器
reverse_iterator rend(); // 返回指向第一个元素前面的反向迭代器
3.5 map的增删查
map
的增删查关注以下几个接口即可:
map
增接口,插入的pair
键值对数据,跟set
所有不同,但是查和删的接口只用关键字key
跟set
是完全类似的,不过find
返回iterator
,不仅仅可以确认key
在不在,还找到key
映射的value
,同时通过迭代还可以修改value
Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>
// 单个数据插入,如果已经key存在则插入失败,key存在相等value不相等也会插入失败
pair<iterator,bool> insert (const value_type& val);
// 列表插入,已经在容器中存在的值不会插入
void insert (initializer_list<value_type> il);
// 迭代器区间插入,已经在容器中存在的值不会插入
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find (const key_type& k);
// 查找k,返回k的个数
size_type count (const key_type& k) const;
// 删除一个迭代器位置的值
iterator erase (const_iterator position);
// 删除k,k存在返回0,存在返回1
size_type erase (const key_type& k);
// 删除一段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);// 返回大于等k位置的迭代器
iterator lower_bound (const key_type& k);
// 返回大于k位置的迭代器
const_iterator lower_bound (const key_type& k) const;
3.6 map的数据修改
前面我提到map
支持修改mapped_type
数据,不支持修改key
数据,修改关键字数据,破坏了底层搜索树的结构。
map
第一个支持修改的方式时通过迭代器,迭代器遍历时或者find
返回key
所在的iterator
修改,map
还有一个非常重要的修改接口operator[]
,但是operator[]
不仅仅支持修改,还支持插入数据和查找数据,所以他是一个多功能复合接口
需要注意从内部实现角度,map
这里把我们传统说的value
值,给的是T
类型,typedef
为mapped_type
。而value_type
是红黑树结点中存储的pair
键值对值。日常使用我们还是习惯将这里的T
映射值叫做value
。
Member typeskey_type -> The first template parameter (Key)mapped_type -> The second template parameter (T)value_type -> pair<const key_type,mapped_type>// 查找k,返回k所在的迭代器,没有找到返回end(),如果找到了通过iterator可以修改key对应的mapped_type值iterator find (const key_type& k);
// 文档中对insert返回值的说明
// The single element versions (1) return a pair, with its member pair::first set to an iterator pointing to either the newly inserted element or to theelement with an equivalent key in the map. The pair::second element in the pair is set to true if a new element was inserted or false if an equivalent key already existed.
// insert插入一个pair<key, T>对象
// 1、如果key已经在map中,插入失败,则返回一个pair<iterator,bool>对象,返回pair对象first是key所在结点的迭代器,second是false
// 2、如果key不在在map中,插入成功,则返回一个pair<iterator,bool>对象,返回pair对象first是新插入key所在结点的迭代器,second是true
// 也就是说无论插入成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭代器
// 那么也就意味着insert插入失败时充当了查找的功能,正是因为这一点,insert可以用来实现operator[]
// 需要注意的是这里有两个pair,不要混淆了,一个是map底层红黑树节点中存的pair<key, T>,另一个是insert返回值pair<iterator,bool>
pair<iterator,bool> insert (const value_type& val);
mapped_type& operator[] (const key_type& k);
// operator的内部实现
mapped_type& operator[] (const key_type& k)
{// 1、如果k不在map中,insert会插入k和mapped_type默认值,同时[]返回结点中存储mapped_type值的引用,那么我们可以通过引用修改返映射值。所以[]具备了插入+修改功能// 2、如果k在map中,insert会插入失败,但是insert返回pair对象的first是指向key结点的迭代器,返回值同时[]返回结点中存储mapped_type值的引用,所以[]具备了查找+修改的功能pair<iterator, bool> ret = insert({ k, mapped_type() });iterator it = ret.first;return it->second;
}
3.7 构造遍历及增删查使用样例
#include<iostream>
#include<map>
using namespace std;
int main()
{// initializer_list构造及迭代遍历map<string, string> dict = { {"left", "左边"}, {"right", "右边"},{"insert", "插入"},{ "string", "字符串" } };//map<string, string>::iterator it = dict.begin();auto it = dict.begin();while (it != dict.end()){//cout << (*it).first <<":"<<(*it).second << endl;// map的迭代基本都使用operator->,这里省略了一个->// 第一个->是迭代器运算符重载,返回pair*,第二个箭头是结构指针解引用取pair数据//cout << it.operator->()->first << ":" << it.operator->()->second << endl;cout << it->first << ":" << it->second << endl;++it;}cout << endl;// insert插入pair对象的4种方式,对比之下,最后一种最方便pair<string, string> kv1("first", "第一个");dict.insert(kv1);dict.insert(pair<string, string>("second", "第二个"));dict.insert(make_pair("sort", "排序"));dict.insert({ "auto", "自动的" });// "left"已经存在,插入失败dict.insert({ "left", "左边,剩余" });// 范围for遍历for (const auto& e : dict){cout << e.first << ":" << e.second << endl;}cout << endl;string str;while (cin >> str){auto ret = dict.find(str);if (ret != dict.end()){cout << "->" << ret->second << endl;}else{cout << "无此单词,请重新输入" << endl;}}// erase等接口跟set完全类似,这里就不演示讲解了return 0;
}
3.8 map的迭代器和[]功能样例:
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{// 利用find和iterator修改功能,统计水果出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (const auto& str : arr){// 先查找水果在不在map中// 1、不在,说明水果第一次出现,则插入{水果, 1}// 2、在,则查找到的节点中水果对应的次数++auto ret = countMap.find(str);if (ret == countMap.end()){countMap.insert({ str, 1 });}else{ret->second++;}}for (const auto& e : countMap){cout << e.first << ":" << e.second << endl;}cout << endl;return 0;
}#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{// 利用[]插入+修改功能,巧妙实现统计水果出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (const auto& str : arr){// []先查找水果在不在map中// 1、不在,说明水果第一次出现,则插入{水果, 0},同时返回次数的引用,++一下就变成1次了// 2、在,则返回水果对应的次数++countMap[str]++;}for (const auto& e : countMap){cout << e.first << ":" << e.second << endl;}cout << endl;return 0;
}
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{map<string, string> dict;dict.insert(make_pair("sort", "排序"));// key不存在->插入 {"insert", string()}dict["insert"];// 插入+修改dict["left"] = "左边";// 修改dict["left"] = "左边、剩余";// key存在->查找cout << dict["left"] << endl;return 0;
}
3.9 multimap和map的差异
multimap
和map
的使用基本完全类似,主要区别点在于multimap
支持关键值key
冗余,那么insert/find/count/erase
都围绕着支持关键值ke
y冗余有所差异,这里跟set
和multiset
完全一样,比如find
时,有多个key
,返回中序第一个。其次就是multimap
不支持[]
,因为支持key
冗余,[]
就只能支持插入了,不能支持修改。
3.10 138. 随机链表的复制 - 力扣(LeetCode)
138. 随机链表的复制 - 力扣(LeetCode)
数据结构初阶阶段,为了控制随机指针,我们将拷贝结点链接在原节点的后面解决,后面拷贝节点还得解下来链接,非常麻烦。这里我们直接让{原结点,拷贝结点}建立映射关系放到map
中,控制随机指针会非常简单方便,这里体现了map
在解决一些问题时的价值,完全是降维打击。
class Solution {
public:// 函数功能:深拷贝带随机指针的链表// 参数:原链表的头节点指针// 返回值:拷贝后的新链表的头节点指针Node* copyRandomList(Node* head) {// 创建map用于存储原节点到拷贝节点的映射关系// key: 原链表节点地址// value: 对应的拷贝节点地址map<Node*, Node*> nodeMap;// copyhead指向拷贝链表的头节点// copytail指向拷贝链表的尾节点Node* copyhead = nullptr, *copytail = nullptr;// 第一次遍历:复制节点值和next指针Node* cur = head;while(cur){// 如果是第一个节点if(copytail == nullptr){// 创建头节点copyhead = copytail = new Node(cur->val);}else{// 创建新节点并连接到尾部copytail->next = new Node(cur->val);copytail = copytail->next;}// 建立原节点和拷贝节点的映射关系nodeMap[cur] = copytail;// 继续处理下一个节点cur = cur->next;}// 第二次遍历:处理random指针cur = head; // cur指向原链表Node* copy = copyhead; // copy指向拷贝链表while(cur){// 如果原节点的random为空if(cur->random == nullptr){copy->random = nullptr;}else{// 通过map找到原节点random指向的节点对应的拷贝节点copy->random = nodeMap[cur->random];}// 继续处理下一个节点cur = cur->next;copy = copy->next;}return copyhead;}
};
算法思路解析:
- 整体思路:
- 分两次遍历完成深拷贝
- 第一次遍历复制节点值和
next
指针,同时建立映射关系- 第二次遍历处理
random
指针- 具体步骤:
- 第一次遍历:
- 复制每个节点的值
- 建立
next
连接- 将原节点和对应的拷贝节点存入
map
- 第二次遍历:
- 根据原节点的
random
指向- 通过
map
找到对应的拷贝节点- 建立
random
连接- 时间复杂度:
O(N)
,需要两次遍历链表map
的插入和查找操作是O(logN)
- 空间复杂度:
O(N)
,需要额外的map
存储映射关系
3.11 692. 前K个高频单词 - 力扣(LeetCode)
692. 前K个高频单词 - 力扣(LeetCode)
本题目我们利用map
统计出次数以后,返回的答案应该按单词出现频率由高到低排序,有一个特殊要求,如果不同的单词有相同出现频率,按字典顺序排序。
解决思路1:
用排序找前k
个单词,因为map
中已经对key
单词排序过,也就意味着遍历map
时,次数相同的单词,字典序小的在前面,字典序大的在后面。那么我们将数据放到vector
中用一个稳定的排序就可以实现上面特殊要求,但是sort
底层是快排,是不稳定的,所以我们要用stable_sort
,他是稳定的。
解决思路1:
class Solution {
public:// 定义仿函数,用于排序规则的比较struct Compare{// 重载operator(),实现比较规则// 参数:两个pair<string,int>的常引用// 返回值:bool,表示x是否应该排在y前面bool operator()(const pair<string, int>& x, const pair<string, int>& y)const{// 按照频率降序排列(即频率高的在前)return x.second > y.second;}};// 函数功能:找出字符串数组中出现频率最高的k个字符串// 参数:字符串数组的引用,和需要返回的个数k// 返回值:包含前k个高频字符串的vectorvector<string> topKFrequent(vector<string>& words, int k) {// 统计每个字符串出现的频率// key: 字符串// value: 出现次数map<string, int> countMap;for(auto& e : words){countMap[e]++; // 统计频率}// 将map中的数据转换为pair的vector// 便于排序vector<pair<string, int>> v(countMap.begin(), countMap.end());// 使用stable_sort保持相同频率字符串的原有顺序// Compare()定义排序规则:按照频率降序排列stable_sort(v.begin(), v.end(), Compare());// sort和stable_sort的区别:// sort不保证相等元素的相对位置// stable_sort保证相等元素的相对位置不变//sort(v.begin(), v.end(), Compare());// 取排序后的前k个字符串vector<string> strV;for(int i = 0; i < k; ++i){strV.push_back(v[i].first); // 只需要字符串,不需要频率}return strV;}
};
算法思路解析:
- 步骤分解:
- 统计频率:使用
map
记录每个字符串出现的次数- 转换数据:将
map
转为pair
的vector
便于排序- 排序:按照频率降序排列
- 获取结果:取前
k
个字符串- 关键点解析:
- 使用
map
统计频率的优势:
- 自动去重
- 按照
key
排序- 高效的查找和统计
Compare
仿函数:
- 定义排序规则
- 按照
pair
的second
(频率)降序排序- 可以方便地修改排序规则
stable_sort
vssort
:
stable_sort
能保持相同频率字符串的原有顺序- 在处理相同频率的字符串时更稳定
- 时间复杂度分析:
- 统计频率:
O(NlogM)
,N
是words
长度,M
是不同字符串个数- 排序:
O(MlogM)
,M
是不同字符串个数- 总体:
O(NlogM + MlogM)
- 空间复杂度:
O(M)
,M
是不同字符串个数
解决思路2:
将map
统计出的次数的数据放到vector
中排序,或者放到priority_queue
中来选出前k
个。利用仿函数强行控制次数相等的,字典序小的在前面。
class Solution {
public:// 定义仿函数,实现自定义排序规则struct Compare{bool operator()(const pair<string, int>& x, const pair<string, int>& y)const{// 排序规则:// 1. 按频率降序排序(频率高的在前)// 2. 如果频率相同,按字典序升序排序(字母顺序小的在前)return x.second > y.second || // 频率降序(x.second == y.second && x.first < y.first); // 频率相同时字典序升序}};// 函数功能:找出字符串数组中出现频率最高的k个字符串// 要求:1. 按照出现频率降序排列// 2. 频率相同时,按照字典序升序排列vector<string> topKFrequent(vector<string>& words, int k) {// 统计每个字符串出现的频率map<string, int> countMap;for(auto& e : words){countMap[e]++;}// 将map转换为pair的vector以便排序vector<pair<string, int>> v(countMap.begin(), countMap.end());// 使用自定义的Compare仿函数排序// 不需要使用stable_sort,因为已经在Compare中处理了相等情况sort(v.begin(), v.end(), Compare());// 取前k个字符串vector<string> strV;for(int i = 0; i < k; ++i){strV.push_back(v[i].first);}return strV;}
};
算法思路解析:
关键改进:
- 在
Compare
仿函数中增加了第二个排序条件- 当频率相同时,按字典序排序
Compare
仿函数详解:x.second > y.second || // 条件1:频率降序 (x.second == y.second && x.first < y.first) // 条件2:频率相同时字典序升序
- 条件1:优先按频率降序排序
- 条件2:频率相同时,按字符串字典序升序排序
不再需要
stable_sort
:
- 因为
Compare
仿函数已经完整定义了相等情况的处理规则- 使用普通的
sort
即可
class Solution {
public:// 定义仿函数,为优先级队列提供比较规则struct Compare{bool operator()(const pair<string, int>& x, const pair<string, int>& y)const{// 注意:priority_queue默认是大顶堆,但比较规则是相反的// 如果要实现次数降序,反而要用小于号// 如果要实现字典序升序,反而要用大于号return x.second < y.second || // 频率降序(x.second == y.second && x.first > y.first); // 频率相同时字典序升序}};vector<string> topKFrequent(vector<string>& words, int k) {// 统计词频map<string, int> countMap;for(auto& e : words){countMap[e]++;}// 创建优先级队列// 模板参数:// 1. 存储的元素类型:pair<string,int>// 2. 底层容器:vector// 3. 比较规则:Compare仿函数priority_queue<pair<string, int>, vector<pair<string, int>>, Compare> p(countMap.begin(), countMap.end());// 取出前k个元素vector<string> strV;for(int i = 0; i < k; ++i){strV.push_back(p.top().first); // 只需要单词,不需要频率p.pop();}return strV;}
};
算法思路解析:
- 关键点:优先级队列的特性
priority_queue
默认是大顶堆- 但其比较规则与直觉相反:
- 如果
a < b
返回true
,则a
排在b
后面- 如果
a < b
返回false
,则a
排在b
前面- Com
p
are仿函数详解:x.second < y.second || // 频率比较 (x.second == y.second && x.first > y.first) // 字典序比较
- 频率比较用
<
号:实际效果是频率高的在队列顶部- 字典序比较用
>
号:实际效果是字典序小的在顶部
- 处理流程:
- 统计词频:使用
map
记录每个单词出现次数- 构建优先级队列:
- 直接用
map
的迭代器范围构造- 自动按照
Compare
规则排序- 获取结果:
- 连续
k
次取出队列顶部元素- 每次取出后删除顶部元素
则是相反的
// 如果要实现次数降序,反而要用小于号
// 如果要实现字典序升序,反而要用大于号
return x.second < y.second || // 频率降序
(x.second == y.second && x.first > y.first); // 频率相同时字典序升序
}
};
相关文章:

【C++】21.map和set的使用
文章目录 1. 序列式容器和关联式容器2. set系列的使用2.1 set和multiset参考文档2.2 set类的介绍2.3 set的构造和迭代器构造函数:双向迭代器迭代器: 2.4 set的增删查2.5 insert和迭代器遍历使用样例:2.6 find和erase使用样例:2.7 …...

burpsiute的基础使用(2)
爆破模块(intruder): csrf请求伪造访问(模拟攻击): 方法一: 通过burp将修改,删除等行为的数据包压缩成一个可访问链接,通过本地浏览器访问(该浏览器用户处于登陆状态&a…...
ElasticSearch 同义词匹配
synonym.txt 电脑, 计算机, 主机 复印纸, 打印纸, A4纸, 纸, A3 平板电脑, Pad DELETE /es_sku_index_20_20250109 PUT /es_sku_index_20_20250109 {"settings": {"index": {"number_of_shards": "5","number_of_replicas&quo…...

linux RT-Preempt spin lock实现
一、spin_lock概述 Spinlock是linux内核中常用的一种互斥锁机制,和mutex不同,当无法持锁进入临界区的时候,当前执行线索不会阻塞,而是不断的自旋等待该锁释放。正因为如此,自旋锁也是可以用在中断上下文的。也正是因为…...
PySpark广播表连接解决数据倾斜的完整案例
使用PySpark解决数据倾斜问题的完整案例,通过广播表连接的方式来优化性能。 准备数据 假设我们有两张表,一张大表 big_table 和一张小表 small_table ,小表将作为广播表。 from pyspark.sql import SparkSession# 初始化SparkSession spar…...

Chromium CDP 开发(十二):为自己的Domain建立custom_config.json
引言 本章详细介绍了如何为自定义的 CDP Domain 创建 custom_config.json 文件,并通过修改 BUILD.gn 文件来确保自定义的配置文件参与编译。我们通过 inspector_protocol_generate 配置段自动生成自定义 Domain 的头文件和实现文件,并成功将其集成到构建…...

【Vue】全局/局部组件使用流程(Vue2为例)
全局组件和局部组件区别 如何使用 全局组件:全局注册后,可以在任意页面中直接使用。局部组件:在页面中需要先导入子组件路径,注册组件才能使用。 适用场景 全局组件:适用于高频使用的组件,如导航栏、业…...
Vue.js组件开发详解
在现代前端开发中,Vue.js 凭借其简洁、高效、灵活的特性,成为了众多开发者的首选框架之一,而组件化开发则是 Vue.js 的核心优势。组件可以将复杂的 UI 界面拆分成一个个独立的、可复用的小块,极大地提高了开发效率和代码的可维护性…...

解决:ubuntu22.04中IsaacGymEnv保存视频报错的问题
1. IsaacGymEnvs项目介绍 IsaacGymEnvs:基于NVIDIA Isaac Gym的高效机器人训练环境 IsaacGymEnvs 是一个基于 NVIDIA Isaac Gym 的开源 Python 环境库,专为机器人训练提供高效的仿真环境。Isaac Gym 是由 NVIDIA 开发的一个高性能物理仿真引擎…...
深度学习camp-第J7周:对于ResNeXt-50算法的思考
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 📌你需要解决的疑问:这个代码是否有错?对错与否都请给出你的思考 📌打卡要求:请查找相关资料、逐步…...
java: 错误: 无效的源发行版:17解决办法
遇到“java: 错误: 无效的源发行版:17”的问题,通常是因为项目设置中指定的Java版本与当前环境不一致导致的。以下是几种可能的解决方案: 检查并升级Java版本:确保你已经安装了支持Java 17的JDK版本。你可以通过命令行输入java -v…...

Docker 安装开源的IT资产管理系统Snipe-IT
一、安装 1、创建docker-compose.yaml version: 3services:snipeit:container_name: snipeitimage: snipe/snipe-it:v6.1.2restart: alwaysports:- "8000:80"volumes:- ./logs:/var/www/html/storage/logsdepends_on:- mysqlenv_file:- .env.dockernetworks:- snip…...
Go语言封装加解密包(AES/DES/RSA)
Go语言封装加解密包(AES/DES/RSA) 1. Base64编码与解码2. AES加解密3. DES加解密4. RSA加解密5. SHA256哈希6. 单元测试1. AES加解密单元测试2. DES加解密单元测试3. RSA加解密单元测试4. SHA256哈希单元测试测试用例说明 总结 在现代软件开发中…...
sql server 对 nvarchar 类型的列进行 SUM() 运算
因为 SUM() 是一个数值聚合函数,不能直接应用于字符串类型的数据。为了正确汇总标准数量,你需要确保该列的数据类型是数值类型,如 int、decimal 或 float。 假设要统计数量列的和,由于数量列是 nvarchar 类型,你需要先…...
java中json字符串键值获取
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version> </dependency>使用fastjson依赖 JSONObject jsonObject JSON.parseObject(s); 这个jsonObject本质就是一个map&…...

MPLS原理及配置
赶时间可以只看实验部分 由来:90年代中期,互联网流量的快速增长。传统IP报文依赖路由器查询路由表转发,但由于硬件技术存在限制导致转发性能低,查表转发成为了网络数据转发的瓶颈。 因此,旨在提高路由器转发速度的MPL…...

口碑很好的国产LDO芯片,有哪些?
在几乎任何一个电路设计中,都可能会使用LDO(低压差线性稳压器)这个器件。 虽然LDO不是什么高性能的IC,但LDO芯片市场竞争异常激烈。最近几年,诞生了越来越多的精品国产LDO,让人看得眼花缭乱。 业内人士曾经…...

【流程设计】类似钉钉的流程设计功能样式demo
对于一些审批流程,可能会用到这个功能,通过这样一层层的加下来,弄一个审批流程的数组,然后根据这个来审核是否都通过审批,这里是简单的弄一个样式的demo,功能自由发挥 <!DOCTYPE html> <html>…...

ChatGPT入门之文本情绪识别:先了解LSTM如何处理文字序列
文章目录 0. 首先聊聊什么是RNN1. 理解LSTM,从数据如何喂给 LSTM开始2. LSTM每个门是如何处理序列数据的?2.1 遗忘门(Forget Gate):该忘掉哪些信息?2.2 输入门(Input Gate)ÿ…...
测试开发之面试宝典
目录 session和cookie的区别 session和cookie的区别 1.session和cookie都是鍵值对应的 2.session和cookie都是服务器生成的,session的ID,即服各器用来识别读取session对象的一把钥匙 3.session是保存在服各器端,而cookie是返回給客戶端的&…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...