💓博主CSDN主页:麻辣韭菜💓
⏩专栏分类:C++知识分享⏪
🚚代码仓库:C++高阶🚚
🌹关注我🫵带你学习更多C++知识
🔝🔝
目录
前言
C++二叉搜索树 最后简单的讲解了搜索二叉树的应用场景,而本篇的set对应的就是K模型,
map对应KV模型。
一.树形结构的关联式容器
(一)set
1.1 set介绍
set
是C++标准模板库(STL)中的一个关联容器,它包含唯一元素的集合。以下是关于set
的一些基本介绍:
唯一性:
set
中的元素都是唯一的,重复的元素在插入时会被自动忽略。自动排序:
set
中的元素默认按照升序排序。如果需要自定义排序规则,可以通过提供比较函数或重载<
运算符来实现。迭代器:
set
支持前向迭代器,可以用来遍历容器中的元素。常用操作:
insert()
:向set
中插入一个元素。
find()
:查找一个元素是否存在于set
中。
erase()
:删除一个元素。
size()
:返回set
中元素的个数。
empty()
:检查set
是否为空。
begin()
和end()
:返回指向set
中第一个元素和尾后位置的迭代器。
复杂度:
set
的插入、删除和查找操作的平均时间复杂度都是O(log n),其中n是set
中元素的个数。底层实现:
set
通常使用红黑树(一种自平衡的二叉搜索树)来实现,以确保元素的排序和快速查找。
1.2 set模板参数
- T: set中存放元素的类型,实际在底层存储<value, value>的键值对。
- Compare:set中元素默认按照小于来比较
- Alloc:set中元素空间的管理方式,使用STL提供的空间配置器管理
1.3 set成员函数
构造函数:用于创建
set
对象。析构函数:用于释放
set
对象占用的资源。
begin()
:返回指向容器中第一个元素的迭代器。
end()
:返回指向容器“尾部之后”的迭代器。
rbegin()
:返回指向容器中最后一个元素的反向迭代器。
rend()
:返回指向容器“起始之前”的反向迭代器。
empty()
:检查容器是否为空。
size()
:返回容器中元素的数量。
max_size()
:返回容器可能包含的最大元素数量。
clear()
:删除容器中的所有元素。
insert(const value_type& val)
:在容器中插入一个元素。
erase(const_iterator pos)
:删除迭代器pos
指向的元素。
erase(const key_type& key)
:删除键为key
的元素。
erase(const_iterator first, const_iterator last)
:删除范围[first, last)
内的元素。
find(const key_type& key) const
:查找键为key
的元素,若找到则返回指向该元素的迭代器,否则返回end()
。
count(const key_type& key) const
:返回键为key
的元素在容器中出现的次数(对于set
,这个值总是0或1,因为set
中的元素是唯一的)。
lower_bound(const key_type& key)
和upper_bound(const key_type& key)
:返回指向不小于(大于)键key
的第一个元素的迭代器。
equal_range(const key_type& key)
:返回一个包含两个迭代器的pair
,表示键为key
的元素在容器中的范围。对于set
,如果元素存在,这个范围只包含一个元素;如果不存在,这个范围是[end(), end())
1.4 set使用
#include <iostream>
#include <string>
#include <map>
#include <set>
using namespace std;
void set_test()
{
set<string> s;
s.insert("张三");
s.insert("李四");
s.insert("王二麻子");
s.insert("赵六");
s.insert("孙七");
set<string>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
auto it1 = s.begin();
while (it1 != s.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
}
void set_test2()
{
//排序+去重
set<int> s1;
s1.insert(3);
s1.insert(1);
s1.insert(4);
s1.insert(2);
s1.insert(1);
s1.insert(2);
auto it1 = s1.begin();
while (it1 != s1.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
}
int main()
{
set_test();
return 0;
}
set 和vector list stack queue 这些容器使用方法到差不差 这里就不细讲了!!!
(二)multiset
set前面加multi顾名思义那就是可以插入相同的值。
1.1multiset介绍
1. multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
2. 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是<value, value>组成
的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器
中进行修改(因为元素总是const的),但可以从容器中插入或删除。
3. 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则进行排序。
4. multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭
代器遍历时会得到一个有序序列。
5. multiset底层结构为二叉搜索树(红黑树)。
5. multiset中的元素不能修改
6. 在multiset中找某个元素,时间复杂度为$O(log_2 N)$
7. multiset的作用:可以对元素进行排序
2.2 multiset模板参数
2.3 multiset常用成员函数
multiset
是C++ STL(标准模板库)中的一个容器,它允许存储重复的元素,并且元素在multiset
中自动排序。下面是multiset
的一些基本函数:
构造函数
multiset<T>()
: 创建一个空的multiset
。multiset<T>(const multiset& other)
: 创建一个multiset
作为另一个multiset
的副本。插入元素
pair<iterator, bool> insert(const value_type& val)
: 插入一个元素。如果插入成功,返回指向新插入元素的迭代器;如果元素已存在,则不插入并返回指向已存在元素的迭代器。void insert(const_iterator position, const value_type& val)
: 在指定位置前插入一个元素。注意,由于multiset
是有序的,这个“位置”只是一个提示,实际插入位置可能根据元素的值进行调整。void insert(const_iterator first, const_iterator last)
: 插入一个范围内的元素。void insert(initializer_list<value_type> il)
: 使用初始化列表插入元素。删除元素
size_type erase(const key_type& key)
: 删除所有等于指定键的元素,并返回删除的元素数量。iterator erase(const_iterator position)
: 删除位于指定位置的元素,并返回指向下一个元素的迭代器。iterator erase(const_iterator first, const_iterator last)
: 删除一个范围内的元素,并返回指向下一个元素的迭代器。查找元素
iterator find(const key_type& key)
: 查找第一个等于指定键的元素,如果找到则返回指向该元素的迭代器,否则返回end()
。size_type count(const key_type& key) const
: 返回等于指定键的元素数量。iterator lower_bound(const key_type& key)
: 返回指向第一个不小于指定键的元素的迭代器。iterator upper_bound(const key_type& key)
: 返回指向第一个大于指定键的元素的迭代器。pair<iterator, iterator> equal_range(const key_type& key)
: 返回一个范围,包含所有等于指定键的元素。修改容器大小
void clear()
: 删除multiset
中的所有元素。获取容器信息
bool empty() const
: 如果multiset
为空,则返回true
。size_type size() const
: 返回multiset
中的元素数量。iterator begin()
: 返回指向第一个元素的迭代器。const_iterator begin() const
: 返回指向第一个元素的常量迭代器。iterator end()
: 返回指向multiset
末尾之后位置的迭代器。const_iterator end() const
: 返回指向multiset
末尾之后位置的常量迭代器。reverse_iterator rbegin()
: 返回指向最后一个元素的反向迭代器。const_reverse_iterator rbegin() const
: 返回指向最后一个元素的常量反向迭代器。reverse_iterator rend()
: 返回指向multiset
开头之前位置的反向迭代器。const_reverse_iterator rend() const
: 返回指向multiset
开头之前位置的常量反向迭代器。 这些函数可以让你有效地在multiset
上进行各种操作。记住,由于multiset
是有序的,因此插入和查找操作的复杂度通常是对数级别的。
2.4multiset使用
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
// 1. 定义multiset,存储string类型
multiset<string> ms;
// 2. 插入元素
ms.insert("apple");
ms.insert("banana");
ms.insert("apple");
ms.insert("cherry");
// 3. 访问元素
for (auto it = ms.begin(); it != ms.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 4. 删除元素
// 删除第一个出现的"apple"
auto it = ms.find("apple");
if (it != ms.end()) {
ms.erase(it);
}
// 再次访问元素
cout << "After deleting an 'apple': ";
for (auto it = ms.begin(); it != ms.end(); ++it) {
cout << *it << " ";
}
cout << endl;
return 0;
}
(三)map
3.1 man介绍
map
是C++标准库中的一个关联容器,它存储的元素都是键值对(key-value pair),并且允许基于键快速访问各个元素。map
中的元素总是按键值进行排序存储的。 以下是关于map
的基本介绍:1.存储结构:
map
内部通常实现为一个平衡搜索树(比如红黑树),因此其元素的插入、删除和查找的时间复杂度都是对数级别的。2.元素类型:
map
中的元素是键值对(pair),其中键(key)是唯一的,用于标识元素,而值(value)是与该键相关联的数据。3.键的唯一性:
- 在
map
中,每个键只出现一次。如果试图插入具有相同键的新元素,该操作会替换原有的元素值。4.排序:
map
中的元素按键的升序自动排序。默认情况下,键的比较使用std::less<Key>
,但也可以指定自定义的比较函数或对象。5.基本操作:
插入元素:使用
insert
成员函数或operator[]
(后者在键不存在时会创建一个新元素)。访问元素:使用
operator[]
或at
成员函数通过键来访问值。如果键不存在,operator[]
会插入一个新元素并返回其引用,而at
会抛出一个异常。删除元素:使用
erase
成员函数。查找元素:使用
find
成员函数来查找具有指定键的元素。遍历元素:可以使用迭代器来遍历
map
中的所有元素。
3.2 pair
3.3 map常用函数
插入元素
insert(const value_type& val)
: 插入一个元素(键值对)。
insert(const_iterator position, const value_type& val)
: 在指定位置前插入一个元素。
insert(InputIterator first, InputIterator last)
: 插入一个元素范围。查找元素
find(const key_type& k)
: 查找键为k的元素,如果找到则返回指向该元素的迭代器,否则返回end()
。
count(const key_type& k)
: 查找键为k的元素的数量,因为map中的键是唯一的,所以结果要么是0(没找到)要么是1(找到了)。删除元素
erase(const key_type& k)
: 删除键为k的元素。
erase(const_iterator position)
: 删除指定位置的元素。
erase(const_iterator first, const_iterator last)
: 删除一个元素范围。获取元素数量
size()
: 返回map中元素的数量。判断map是否为空
empty()
: 如果map为空则返回true,否则返回false。访问元素
operator[] (const key_type& k)
: 通过键访问元素,如果键不存在则插入一个具有该键的新元素,并初始化为默认值。
at(const key_type& k)
: 通过键访问元素,如果键不存在则抛出std::out_of_range
异常。获取最大和最小元素
begin()
,end()
: 分别返回指向map中第一个和最后一个元素的迭代器。
rbegin()
,rend()
: 分别返回指向map中最后一个和第一个元素的反向迭代器(即从后往前遍历)。修改键值
map
中的键在插入后不能被修改,但是可以通过删除旧元素并插入新元素来“修改”键。值则可以通过迭代器或引用直接修改
3.4 map使用
void map_test1()
{
map<string, int> dict;
dict.insert(make_pair("one", 1));
dict.insert(make_pair("two", 2));
dict.insert(make_pair("three", 3));
dict.insert(make_pair("four", 4));
//插入也可以用方括号
dict["five"] = 5;
dict["six"] = 6;
dict["six"] = 6; //插入失败
map<string, int>::iterator it = dict.begin();
while (it != dict.end())
{
//cout << (*dict).first << ":" << (*dict).second << endl;
cout << it->first << ":" << it->second << " ";
++it;
}
cout << endl;
}
void map_test2()
{
string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };
map<string, int> countMap;
//传统写法插入
for (auto& e : arr)
{
auto ret = countMap.find(e);
if (ret == countMap.end())
{
countMap.insert(make_pair(e, 1));
}
else
{
ret->second++;
}
}
for (auto &kv : countMap)
{
cout << kv.first << ": " << kv.second << endl;
}
// 【】写法
/*for (auto& e : arr)
{
countMap[e]++;
}
for (auto& kv : countMap)
{
cout << kv.first << ":" << kv.second << endl;
}*/
}
(四) multimap
它和map区别就是可以插入重复键,但是他没有重载【】其他的用法都是一样。
// 声明一个multimap变量
multimap<string, int> mm;
// 示例:向multimap中添加元素
mm.insert(make_pair("apple", 1));
mm.insert(make_pair("banana", 2));
mm.insert(make_pair("apple", 3)); // 可以有重复的键
// 示例:遍历multimap
for (const auto& kv : mm) {
cout << kv.first << ": " << kv.second << endl;
}
return 0;
}
(五)set和map OJ题
1.692. 前K个高频单词 - 力扣(LeetCode)
class Solution {
public:
struct Compare
{
bool operator()(const pair<string,int>& kv1, const pair<string,int>& kv2)
{
return kv1.second > kv2.second || (kv1.second == kv2.second && kv1.first < kv2.first);
}
//次数最多的返回 或者次数相等相等的情况再根据字典序排序规则比较
};
vector<string> topKFrequent(vector<string>& words, int k)
{
map<string,int> countMap;
for(auto& kv:words)
{
countMap[kv]++; //插入+统计次数
}
vector<pair<string,int>> v(countMap.begin(),countMap.end()); //把数据方便vector这个容器
//为什么是vector 方便sort 关联式的容器 sort是排不了序的
sort(v.begin(),v.end(),Compare());//sort排序不稳定 我们需要加仿函数自己控制排序
vector<string> ret; //因为前面的v是键值对数据,我们再单独创建一个顺序表把first放到这个数组里再返回这个ret
for(size_t i = 0; i<k; i++)
{
ret.push_back(v[i].first);
}
return ret;
}
};
2.349. 两个数组的交集 - 力扣(LeetCode)
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
// 先去重
set<int> s1;
for(auto e : nums1)
{
s1.insert(e);
}
set<int> s2;
for(auto e : nums2)
{
s2.insert(e);
}
// set排过序,依次比较,小的一定不是交集,相等的是交集
auto it1 = s1.begin();
auto it2 = s2.begin();
vector<int> ret;
while(it1 != s1.end() && it2 != s2.end())
{
//为什么是小的++ 大的那个数,可能是小的那个set里面也有一个同样的
//这时走大的不就错过了?
if(*it1 < *it2)
{
it1++;
}
else if(*it2 < *it1)
{
it2++;
}
else
{
ret.push_back(*it1);
it1++;
it2++;
}
}
return ret;
}
};
map和set 底层实现逻辑还没有讲,下篇预告AVL树 对应就是map的底层原理,关注我带你学习更多C++知识!!!