1.认识KV结构
KV 结构(Key-Value 结构)是一种简单而高效的数据存储和组织方式,核心思想是通过 "键(Key)" 与 "值(Value)" 的对应关系来管理数据。
基本概念
- 键(Key):唯一的标识符,相当于数据的 "名字" 或 "索引",用于快速查找对应的值
- 值(Value):实际存储的数据,可以是简单类型(如数字、字符串)或复杂类型(如对象、列表)
特点
- 简单直观:类似现实中的 "字典" 或 "通讯录",通过名字找内容
- 快速查询:通常基于哈希表实现,查找效率高(时间复杂度接近 O (1))
- 灵活性强:值可以是任意类型,不需要固定的数据结构
- 无固定 schema:不需要预先定义数据结构,可动态添加键值对
通俗易懂的解释:
KV 结构其实就是 “键值对” 结构,你可以把它想象成现实生活中的 “字典” 或 “通讯录”:
- 键(Key) 就像字典里的 “词语” 或通讯录里的 “名字”,是唯一的标识
- 值(Value) 就像字典里的 “解释” 或通讯录里的 “电话号码”,是对应的数据
比如:
- 手机通讯录里,"张三" 是键,"138xxxx1234" 是值
- 字典里,"苹果" 是键,"一种水果..." 是值
- 你的个人信息中,"年龄" 是键,"25" 是值
这种结构的好处是:
- 找东西快,直接通过 “键” 就能定位到 “值”,不用从头到尾翻
- 灵活,想存什么就存什么,不需要固定格式
- 简单,一看就懂,就像查字典一样自然
2.序列式容器和关联式容器
序列式容器:
- 像排队,数据按插入顺序排列,靠位置(比如下标)访问,允许重复。比如数组、链表。
- 数据按 "线性顺序" 排列,就像一条直线上的珠子,每个元素有明确的前后位置关系。元素的位置仅由插入顺序决定,和元素本身的值无关。
- 因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,两个值交换位置,他依旧是序列式容器。
关联式容器:
- 像字典,数据以 “键 - 值” 形式存,靠唯一的 “键” 直接查找,键一般不重复。比如 Map、Set。
- 从逻辑结构来看:数据按 "键 - 值关联" 关系组织,更像一张对照表。元素的位置由 "键" 决定(通常通过哈希或排序规则),和插入顺序无关。查找时不需要关心位置,直接通过 "键" 就能定位到对应 "值",就像查字典时用单词(键)找解释(值),不用管这个单词在字典的第几页。
- 关联容器中元素的位置是通过“键”值确定的,如果交换两个元素的位置,原有结构就被破坏了,就像平衡二叉树,在插入时就已经确定位置关系,这些元素共同组成了一个稳定的整体,如果改变了一个元素的位置,原有结构被破坏,不再是平衡树。
前置知识stl----pair
- 成员类型:
first_type
:第一个模板参数T1
的类型,即pair
中第一个成员的类型。second_type
:第二个模板参数T2
的类型,即pair
中第二个成员的类型。
- 成员变量:
T1 first;
:存储第一个值的成员变量。T2 second;
:存储第二个值的成员变量。
#include <iostream>
#include <utility>
int main() {
// 创建一个 pair 对象,第一个元素是 int 类型,第二个是 string 类型
std::pair<int, std::string> myPair(10, "Hello");
// 访问 pair 的成员
std::cout << "First: " << myPair.first << std::endl;
std::cout << "Second: " << myPair.second << std::endl;
return 0;
}
构造函数
(1)默认构造
成员变量为默认值
(2)拷贝构造和移动构造
(3)pair (const first_type& a, const second_type& b);
通过分别提供第一个元素和第二个元素的值来初始化 pair
对象。
#include <iostream>
#include <utility>
int main() {
std::pair<int, double> p(1, 2.5);
std::cout << p.first << " " << p.second << std::endl;
return 0;
}
可以通过初始化列表的方式,初始化对象,也最常用
pair<int, int> p2{ 1,2 };
make_pair
在构造pair对象时可以通过make_pair函数构造对象。
pair<int,double> p1(make_pair(1, 2.5));
std::cout << p1.first << " " << p1.second << std::endl;
3.set系列的使用
3.1set简单介绍
C++ STL 中的 set
是一种关联式容器
- 存储唯一元素:里面的元素不允许重复,每个值都是唯一的
- 自动排序:元素会按照默认(升序)或自定义的规则自动排序
- 基于键访问:
set
中元素本身就是 "键",通过元素值直接查找,无需索引 - 底层实现:通常用红黑树(一种平衡二叉搜索树)实现,查找、插入、删除效率高(时间复杂度 O (log n))
适合场景:需要存储不重复元素,且经常需要查找(判断一个元素是否存在)、排序的场景(如去重、集合运算等)。
set和map底层使用红黑树(kv结构)实现
3.2set常用接口介绍
1.模板参数
class T
:这是set
容器中存储元素的数据类型。例如,如果你想要一个存储整数的set
,那么T
就会被实例化为int
。set::key_type
和set::value_type
都会是这个T
类型,因为在set
中,元素本身既作为键(key)也作为值(value)。class Compare = less<T>
:这是一个用于比较set
中元素的函数对象类型。默认情况下,使用less<T>
,它会按照元素的升序来排列set
中的元素。你也可以自定义一个比较函数对象来改变set
中元素的排序规则(模板参数如果为大于关系,大的在左子树,小的在右子树)。class Alloc = allocator<T>
:这是用于内存分配的分配器类型。默认情况下,使用allocator<T>
,它负责set
容器的内存分配和释放。
2.成员变量,迭代器类型
成员变量只有一种类型T
迭代器为双向迭代器,不能使用算法库中的sort排序
3.构造函数
1.explicit set (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
- 作用:这是
std::set
的默认构造函数,用于创建一个空的set
容器。 - 参数:
comp
:用于比较set
中元素的函数对象,默认是key_compare()
(通常是std::less<T>
)。alloc
:内存分配器,默认是allocator_type()
(通常是std::allocator<T>
)。
explicit
关键字:它防止了隐式类型转换,即不能通过单个参数来隐式地构造一个set
对象。
2.explicit set (const allocator_type& alloc);
- 作用:这是一个只接受内存分配器参数的构造函数,同样用于创建一个空的
set
容器,但可以指定自定义的内存分配器。 - 参数:
alloc
是自定义的内存分配器。
3.template <class InputIterator> set (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& = allocator_type());
- 作用:这是一个范围构造函数,用于从指定的输入迭代器范围
[first, last)
中复制元素来初始化set
容器。 - 参数:
first
和last
:定义了要复制元素的范围的起始和结束迭代器。comp
:比较函数对象,默认是key_compare()
。alloc
:内存分配器,默认是allocator_type()
。
4.set (const set& x);
- 作用:这是拷贝构造函数,用于创建一个新的
set
容器,其内容是另一个set
容器x
的拷贝。
5.set (const set& x, const allocator_type& alloc);
- 作用:这是带内存分配器的拷贝构造函数,用于创建一个新的
set
容器,其内容是另一个set
容器x
的拷贝,并且使用指定的内存分配器alloc
。
6.set (set&& x);
- 作用:这是移动构造函数,用于从另一个
set
容器x
中 “移动” 资源来创建新的set
容器,它可以避免不必要的数据拷贝,提高性能。
7.set (initializer_list<value_type> il, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
- 作用:这是使用初始化列表来构造
set
容器的构造函数,它可以方便地用花括号{}
包围的元素列表来初始化set
。 - 参数:
il
:初始化列表,包含了要插入到set
中的元素。comp
:比较函数对象,默认是key_compare()
。alloc
:内存分配器,默认是allocator_type()
。
4.operator=
1.set& operator= (const set& x);
- 作用:这是拷贝赋值运算符,用于将另一个
set
容器x
的内容拷贝到当前set
容器中,替换当前set
容器的内容。
#include <iostream>
#include <set>
int main() {
std::set<int> set1 = {1, 2, 3};
std::set<int> set2;
// 使用拷贝赋值运算符
set2 = set1;
// 输出 set2 的内容
for (int num : set2) {
std::cout << num << " ";
}
// 输出:1 2 3
return 0;
}
2.set& operator= (set&& x);
- 作用:这是移动赋值运算符,用于将另一个
set
容器x
的内容 “移动” 到当前set
容器中,而不是进行拷贝。这可以提高性能,因为它避免了不必要的数据拷贝,之后x
将不再拥有原来的数据。
3.set& operator= (initializer_list<value_type> il);
- 作用:这是使用初始化列表进行赋值的运算符重载,用于将初始化列表
il
中的元素赋值给当前set
容器,替换当前set
容器的内容。
#include <iostream>
#include <set>
#include <initializer_list>
int main() {
std::set<int> set1;
// 使用初始化列表赋值
set1 = {10, 20, 30};
// 输出 set1 的内容
for (int num : set1) {
std::cout << num << " ";
}
// 输出:10 20 30
return 0;
}
5.迭代器
6.容量相关函数
1.empty()
- 作用:用于测试容器是否为空。如果容器中没有元素,返回
true
;否则返回false
。
2.size()
- 作用:返回容器中当前元素的数量。
3.max_size()
- 作用:返回容器理论上可以容纳的最大元素数量,这取决于系统和库的实现。
7.insert()
(1)pair<iterator, bool> insert (const value_type& val);
和 pair<iterator, bool> insert (value_type&& val);
- 作用:插入一个值为
val
的元素到容器中。返回一个pair
,其中first
是指向插入位置的迭代器,second
是一个布尔值,表示插入是否成功(如果容器中已存在该元素,则插入失败,second
为false
;否则为true
)。
#include <iostream>
#include <set>
int main() {
std::set<int> s;
auto result = s.insert(5);
std::cout << "Insert successful: " << (result.second ? "Yes" : "No") << std::endl;
return 0;
}
(2)iterator insert (const_iterator position, const value_type& val);和iterator insert (const_iterator position, value_type&& val);
- 作用:在指定的位置
position
附近插入值为val
的元素。返回一个指向插入元素的迭代器。
该接口使用频率很低
(3)通过一段迭代器区间初始化
#include <iostream>
#include <set>
#include <vector>
int main() {
std::set<int> s;
std::vector<int> vec = {1, 2, 3};
s.insert(vec.begin(), vec.end());
for (const auto& num : s) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
(4)通过初始化列表
#include <iostream>
#include <set>
int main() {
std::set<int> s;
s.insert({1, 2, 3});
for (const auto& num : s) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
8.erase
(1)删除一个迭代器位置,通常与find配合使用,返回值为指向被删除元素的下一个元素的迭代器
(2)删除某个值,常用
(3)删除一段迭代器区间
9.swap,clear
swap:
std::set::swap
成员函数用于交换两个 std::set
容器的内容。即交换当前 std::set
对象和参数 x
所指向的 std::set
对象的元素。
几乎用不上
clear:
std::set::clear
成员函数用于清空 std::set
容器中的所有元素,使其大小变为 0
。
10.find与count
find:
- 作用:
find
成员函数用于在容器中查找指定的键。如果找到,返回指向该键的迭代器;如果未找到,返回指向容器末尾的迭代器(即end()
)。
#include <iostream>
#include <set>
int main() {
std::set<int> s = {1, 2, 3, 4, 5};
auto it = s.find(3);
if (it != s.end()) {
std::cout << "Found: " << *it << std::endl;
} else {
std::cout << "Not found" << std::endl;
}
return 0;
}
count:
- 作用:
count
成员函数用于统计容器中指定键的出现次数。对于std::set
和std::map
(键唯一),返回值只能是0
或1
;
#include <iostream>
#include <set>
int main() {
std::set<int> s = {1, 2, 3, 4, 5};
size_t count = s.count(3);
std::cout << "Count of 3: " << count << std::endl;
return 0;
}
11.lower_bound与upper_bound
lower_bound:
- 作用:
lower_bound
成员函数返回一个迭代器,指向容器中第一个不小于(即大于或等于)指定键的元素。
#include <iostream>
#include <set>
int main() {
std::set<int> s = {1, 3, 5, 7, 9};
auto it = s.lower_bound(5);
std::cout << *it << std::endl;
return 0;
}
这里在 std::set<int>
中查找第一个不小于 5
的元素,找到了 5
,所以返回的迭代器指向 5
。
upper_bound:
- 作用:
upper_bound
成员函数返回一个迭代器,指向容器中第一个大于指定键的元素。
#include <iostream>
#include <set>
int main() {
std::set<int> s = {1, 3, 5, 7, 9};
auto it = s.upper_bound(5);
std::cout << *it << std::endl;
return 0;
}
这里在 std::set<int>
中查找第一个大于 5
的元素,找到了 7
,所以返回的迭代器指向 7
。
总结:这两个接口通常和erase的删除迭代器区间配合使用。
4.set与multiset差异
在 C++ 标准库中,std::set
和 std::multiset
都是基于红黑树实现的关联容器,它们都能对元素进行自动排序 。但它们在元素唯一性、插入删除操作返回值以及应用场景等方面存在差别
元素唯一性
std::set
:要求元素唯一,不允许容器中有重复的元素。当插入已经存在的元素时,插入操作会失败,容器的大小不会改变。std::multiset
:允许元素重复,插入相同元素时,容器会增加该元素的数量。
#include <iostream>
#include <set>
int main() {
std::multiset<int> myMultiSet;
myMultiSet.insert(1);
myMultiSet.insert(2);
auto result = myMultiSet.insert(2);
std::cout << "Insert result: Success" << std::endl;
std::cout << "Multiset size: " << myMultiSet.size() << std::endl;
return 0;
}
插入删除操作返回值
std::set
:insert
操作返回一个std::pair
,其中first
是一个迭代器,指向插入的元素(如果插入成功)或者已经存在的元素;second
是一个bool
值,表示插入是否成功。erase
函数删除指定元素,返回void
。std::multiset
:insert
操作返回一个迭代器,指向新插入的元素 。erase
函数如果传入单个值作为参数,返回值是被删除元素的个数;如果传入迭代器作为参数,返回void。
#include <iostream>
#include <set>
int main() {
std::multiset<int> myMultiSet;
myMultiSet.insert(2);
myMultiSet.insert(2);
size_t erasedCount = myMultiSet.erase(2);
std::cout << "Number of elements erased: " << erasedCount << std::endl;
return 0;
}
输出为 Number of elements erased: 2
,表示删除了两个值为 2
的元素。
std::multiset
:适用于需要统计元素出现次数,或者允许元素重复出现的场景,比如统计一个班级中不同分数的学生人数(分数允许重复),或者统计文本中每个单词出现的次数等。
5.map系列的使用
在 C++ 的标准模板库(STL)中,std::map
是一种关联容器,它以键值对(key-value pairs)的形式存储数据,基于红黑树实现
特性
- 键值对存储:每个元素都是一个键值对,键(key)用于唯一标识元素,值(value)是与键相关联的数据。比如,用学生学号作为键,学生成绩作为值来构建一个
map
。 - 自动排序:
std::map
中的元素会根据键的大小自动排序,默认使用std::less<Key>
作为比较函数(Key
是键的类型)。例如,当键为整数时,元素会按照升序排列。 - 键的唯一性:
map
中的键必须是唯一的,不允许出现重复的键。如果插入一个已存在的键,会覆盖该键对应的旧值。
map常用接口介绍
1.模板参数
模板参数
class Key
:键的类型。在std::map
中,键是唯一的,用于标识元素。class T
:值的类型。与键相关联的数据类型。class Compare = less<Key>
:比较函数对象的类型,用于确定键的排序顺序。默认情况下,使用std::less<Key>
,即按照键的升序排列。class Alloc = allocator<pair<const Key, T>>
:分配器类型,用于分配和释放内存。默认情况下,使用std::allocator<pair<const Key, T>>
。
#include <iostream>
#include <map>
int main() {
std::map<int, std::string> myMap;
myMap[1] = "One";
myMap[2] = "Two";
myMap[3] = "Three";
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
kv结构及双向迭代器
2.构造函数
1.explicit map (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
- 作用:默认构造函数,创建一个空的
std::map
对象。可以指定比较函数comp
和分配器alloc
。
2.explicit map (const allocator_type& alloc);
- 作用:仅指定分配器的构造函数,创建一个空的
std::map
对象,并使用指定的分配器alloc
。
3.template <class InputIterator> map (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& = allocator_type());
- 作用:范围构造函数,使用范围
[first, last)
内的元素初始化std::map
对象。可以指定比较函数comp
和分配器alloc
。
#include <iostream>
#include <map>
#include <vector>
int main() {
std::vector<std::pair<int, std::string>> vec = {{1, "One"}, {2, "Two"}, {3, "Three"}};
std::map<int, std::string> myMap(vec.begin(), vec.end());
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
- 解释:这里使用
std::vector
中的元素范围[vec.begin(), vec.end())
初始化std::map<int, std::string>
对象myMap
,然后遍历输出所有元素。
4.map (const map& x);
- 作用:复制构造函数,使用另一个
std::map
对象x
初始化当前std::map
对象。
#include <iostream>
#include <map>
int main() {
std::map<int, std::string> originalMap;
originalMap[1] = "One";
originalMap[2] = "Two";
std::map<int, std::string> myMap(originalMap);
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
5.map (initializer_list<value_type> il, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
- 作用:初始化列表构造函数,使用初始化列表
il
中的元素初始化std::map
对象。可以指定比较函数comp
和分配器alloc
。
#include <iostream>
#include <map>
int main() {
std::map<int, std::string> myMap = {{1, "One"}, {2, "Two"}, {3, "Three"}};
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
3.operator=
(1)作用:复制赋值运算符,将另一个 std::map
对象 x
的内容复制到当前 std::map
对象中。
#include <iostream>
#include <map>
int main() {
std::map<int, std::string> originalMap;
originalMap[1] = "One";
originalMap[2] = "Two";
std::map<int, std::string> myMap;
myMap = originalMap;
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
(3)作用:初始化列表赋值运算符,将初始化列表 il
中的元素赋值给当前 std::map
对象
#include <iostream>
#include <map>
int main() {
std::map<int, std::string> myMap;
myMap = {{1, "One"}, {2, "Two"}, {3, "Three"}};
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
4.迭代器
可对迭代器进行->first或->second等操作,获取迭代器位置的kv值
5.容量相关容器
用法与set相同
6.insert
(1)
部分
pair<iterator, bool> insert (const value_type& val);
- 作用:插入一个值为
val
的元素到容器中。返回一个pair
,其中first
是指向插入位置的迭代器,second
是一个布尔值,表示插入是否成功(如果容器中已存在该元素,则插入失败,second
为false
;否则为true
)。
- 作用:插入一个值为
#include <iostream>
#include <map>
int main() {
std::map<int, std::string> myMap;
auto result = myMap.insert({1, "One"});
std::cout << "Insert successful: " << (result.second ? "Yes" : "No") << std::endl;
return 0;
}
- 这里向空的
std::map<int, std::string>
中插入{1, "One"}
,插入成功,所以result.second
为true
。
(3)
部分
template <class InputIterator> void insert (InputIterator first, InputIterator last);
- 作用:插入范围
[first, last)
内的所有元素到容器中。
- 作用:插入范围
#include <iostream>
#include <map>
#include <vector>
int main() {
std::map<int, std::string> myMap;
std::vector<std::pair<int, std::string>> vec = {{1, "One"}, {2, "Two"}, {3, "Three"}};
myMap.insert(vec.begin(), vec.end());
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
- 解释:这里将
std::vector
中的元素插入到std::map<int, std::string>
中,由于std::map
会自动排序和去重(这里没有重复元素),所以输出为1: One
、2: Two
、3: Three
。
initializer list (4)
部分
void insert (initializer_list<value_type> il);
- 作用:插入初始化列表
il
中的所有元素到容器中。
- 作用:插入初始化列表
#include <iostream>
#include <map>
int main() {
std::map<int, std::string> myMap;
myMap.insert({{1, "One"}, {2, "Two"}, {3, "Three"}});
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
7.erase
- 删除迭代器位置
- 删除k值
- 删除一段迭代器区间
8.operator[]
std::map::operator[]
是一个重载的运算符,用于访问 std::map
中指定键 k
对应的值。如果键 k
不存在于 std::map
中,它会自动插入一个新的键值对,其中键为 k
,值为该类型的默认值(例如,对于 int
类型,默认值为 0
;对于 std::string
类型,默认值为空字符串)。
如果键k存在该函数会返回该“键”对应的“值”的引用,用于修改“值”
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<int, std::string> myMap;
myMap[1] = "One";
myMap[2] = "Two";
std::cout << myMap[1] << std::endl;
std::cout << myMap[3] << std::endl;
return 0;
}
返回值:
9.find与count
查找键,返回键位置的迭代器
判断一个值是否存在容器中
10.lower_bound与upper_bound
用法与set类似
6.multimap和map的差异
元素唯一性
std::map
:要求键必须唯一,不允许容器中存在重复的键。当插入一个已存在的键时,会覆盖该键原来对应的值 。-
std::multimap
:允许键重复,同一键可以对应多个不同的值。在插入操作时,即便插入与已存在键相同的键值对,也会成功插入,不会覆盖 。
#include <iostream>
#include <map>
int main() {
std::multimap<int, std::string> myMultiMap;
myMultiMap.insert({1, "Alice"});
myMultiMap.insert({1, "Bob"});
std::cout << "Size of multimap: " << myMultiMap.size() << std::endl;
return 0;
}
插入操作返回值
std::map
:insert
操作返回一个std::pair
,其中first
是一个迭代器,指向插入的元素(如果插入成功)或者已经存在的元素;second
是一个bool
值,表示插入是否成功。std::multimap
:insert
操作返回一个迭代器,指向新插入的元素。因为一定能插入成功(键可重复),所以不通过返回值表示插入是否成功 。
#include <iostream>
#include <map>
int main() {
std::multimap<int, std::string> myMultiMap;
auto it = myMultiMap.insert({1, "Alice"});
std::cout << it->second << std::endl;
return 0;
}
查找与删除操作特性
std::map
:使用find
查找元素时,若找到返回指向该元素的迭代器,找不到返回end()
迭代器;使用erase
删除元素时,删除指定键的元素或指定迭代器指向的元素。由于键唯一,操作相对简单。std::multimap
:由于键可重复,使用find
查找元素时,找到的是键值对中第一个具有指定键的元素的迭代器;删除操作若传入键作为参数,会删除所有具有该键的元素,返回被删除元素的数量;若传入迭代器作为参数,仅删除迭代器指向的元素。
#include <iostream>
#include <map>
int main() {
std::multimap<int, std::string> myMultiMap;
myMultiMap.insert({1, "Alice"});
myMultiMap.insert({1, "Bob"});
size_t erasedCount = myMultiMap.erase(1);
std::cout << "Number of elements erased: " << erasedCount << std::endl;
return 0;
}