引言:
在计算机科学中,我们经常需要处理一系列具有关键字的元素,并希望对这些元素进行高效的查找、插入和删除操作。为了满足这些需求,我们可以使用BSTree来实现。BSTree作为一种基础的数据结构,它不仅能够帮助我们快速地定位元素,还可以方便地进行元素的插入和删除操作。
在此基础上,map和set这两种数据结构在BSTree的基础上进行了封装,提供了更为丰富的功能。
本文将通过三个方面,对map和set进入学习。
一、关联式容器
关联式容器的几个特征
- 元素根据特定的排序标准(通常是关键字)存储。
- 元素的位置与它们的关键字有关。
- 通常通过关键字来访问元素。
- 支持对关键字的范围查询和快速查找。
- 访问元素的时间复杂度通常是O(log n),因为它们通常基于平衡二叉搜索树(如红黑树)实现。
set
和map
容器保证元素的唯一性。multiset
和multimap
允许重复的元素。- 不支持随机访问,因为元素是排序的。
- 支持快速的查找、插入和删除操作,尤其是基于关键字的操作。
- 自动保持元素的排序状态。
- 适用于需要根据关键字快速查找元素的情况。
- 适用于元素唯一性很重要的情况。
二、 键值对
简介
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2())
{}
pair(const T1& a, const T2& b): first(a), second(b)
{}
};
这里的键值对通常是pair类和make_pair函数实现的。
pair类
pair
是一个模板类,它可以存储两个值,这两个值可以是不同的数据类型。当你需要将两个相关数据作为一个单元来处理时,pair
非常有用。
定义:
std::pair<T1, T2> p(value1, value2);
T1
和 T2
是 pair
中两个元素的数据类型, value1
和 value2
是这两个元素的初始值。
示例:
#include <utility> // 引入 pair 的定义std::pair<int, double> p1(1, 2.3);
std::pair<std::string, int> p2("example", 10);
这里的p1和p2就是一个pair对象,是存储了两个值的小单元。可以通过p1.first p1.second这种形式来访问单元内部的成员。
make_pair函数
同样在<utility>中。make_pair
是一个函数模板,它用于从两个值创建一个 pair
对象。这个函数的目的是为了方便,它允许你不必显式指定 pair
的类型,函数会根据提供的参数自动推导出 pair
的类型。
定义:
std::pair<T1, T2> make_pair(T1 x, T2 y);
可以看出这个函数的返回值是一个pair对象。在我们使用的时候,只需要写出make_pair的参数即可,会自动推导出pair对象。这里 T1
和 T2
是从参数 x
和 y
的类型推导出来的。
使用示例:
#include <utility> // 引入 make_pair 的定义std::pair<int, double> p1 = std::make_pair(1, 2.3);
std::pair<std::string, int> p2 = std::make_pair("example", 10);
在返回类型设置为pair类型时,make_pair作返回十分好用!
区别
- 类型:
pair
是一个类模板,用于定义存储两个值的对象;而make_pair
是一个函数模板,用于创建pair
对象。 - 使用方式:当你使用
pair
时,必须指定存储的数据类型;而使用make_pair
时,数据类型可以从提供的参数中自动推导。 - 灵活性:
make_pair
在某些情况下更灵活,尤其是当你不知道或不想明确指定pair
的类型时。
怎么使用
- 当你需要创建一个
pair
对象并且知道类型时,可以直接使用pair
的构造函数。 - 当你想让编译器自动推导
pair
的类型,或者代码更简洁时,可以使用make_pair
。
#include <iostream>
#include <utility> // 包含 pair 和 make_pair 的定义int main() {// 使用 pair 的构造函数std::pair<int, double> p1(1, 2.3);// 使用 make_pair 创建 pair 对象std::pair<std::string, int> p2 = std::make_pair("example", 10);// 输出 pair 的值std::cout << "p1: " << p1.first << ", " << p1.second << std::endl;std::cout << "p2: " << p2.first << ", " << p2.second << std::endl;return 0;
}
3. 树形结构的关联式容器
set
简介:
在C++的STL(标准模板库)中,set
是一种关联容器,它存储了由关键字组成的已排序的集合,并且每个关键字的值都是唯一的。以下是set
的一些基本特性和用法简介:
特性
- 唯一性:在
set
中,所有的元素都是唯一的,不能有重复的元素。 - 自动排序:
set
中的元素总是按照特定的顺序排序,默认是升序排序。这是通过元素的关键字进行比较来完成的,通常使用<
运算符。 - 基于红黑树实现:在大多数实现中,
set
是基于红黑树来实现的,这意味着操作如插入、删除和查找都具有对数时间复杂度,即O(log n)。 - 非顺序访问:与
vector
或deque
不同,set
不支持快速随机访问,因为它是基于树结构实现的。
常用成员函数
insert(const T& value)
:在set
中插入一个元素。如果元素已存在,则set
保持不变。erase(const T& value)
:从set
中删除指定的元素。find(const T& value)
:在set
中查找元素,如果找到则返回指向元素的迭代器,否则返回end()
。size()
:返回set
中元素的数量。empty()
:检查set
是否为空。begin()
:返回指向set
中第一个元素的迭代器。end()
:返回指向set
中最后一个元素之后位置的迭代器。clear()
:删除set
中的所有元素。
set
是一个非常有用的容器,当需要存储不重复的元素集合,并且经常进行元素的查找和删除操作时,它是一个很好的选择。
关键:
#include <set>
void TestSet()
{// 用数组array中的元素构造setint array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4,
6, 8, 0 };set<int> s(array, array+sizeof(array)/sizeof(array));cout << s.size() << endl;// 正向打印set中的元素,从打印结果中可以看出:set可去重for (auto& e : s)cout << e << " ";cout << endl;// 使用迭代器逆向打印set中的元素for (auto it = s.rbegin(); it != s.rend(); ++it)cout << *it << " ";cout << endl;// set中值为3的元素出现了几次cout << s.count(3) << endl;
}
set复杂接口的分析
1.构造函数
Return value
An iterator to the the first element in the container which is considered to go after val, or set::end if no elements are considered to go after val.
equal_range() 函数在 C++ 中的 std::set 或 std::multiset 容器中返回的是一个左闭右开的区间(返回的是一个pair)。该函数返回一对迭代器,第一个迭代器指向第一个不小于(对于 std::set 和 std::multiset 来说是等于)给定值的元素,第二个迭代器指向第一个大于给定值的元素。
multiset
允许插入相同值的变异的搜索树。插入相同的左边右边都一样。
当我们查找时:
查找相同的元素,返回中序的第一个2
multiset的接口根set也是大差不差
map
std::map
是 C++ 标准库中的一个关联容器,它存储键值对,其中键是唯一的,而值则可以是重复的。std::map
会根据键的值自动排序,通常使用红黑树实现,这意味着在 std::map
中插入和删除操作通常具有对数时间复杂度(O(log n))。
以下是 std::map
的一些关键特性:
基本属性
- 键值对存储:
std::map
存储键值对(key-value
对),其中每个键都是唯一的,而值可以是重复的。 - 排序:
std::map
根据键的值自动排序所有元素。 - 唯一性:不允许重复的键,每个键只能映射到一个值。
- 关联性:元素是根据键来访问的,而不是它们在容器中的位置。
主要成员函数
- 构造函数:创建一个空的
std::map
或者从一个已有的范围填充。 - insert:插入一个新的键值对到
map
中,如果键已存在,则不会插入。 - find:查找具有特定键的元素,如果找到返回一个迭代器,否则返回
end()
。 - erase:删除由迭代器或键指定的元素。
- size:返回
map
中元素的数量。 - empty:检查
map
是否为空。 - clear:删除
map
中所有的元素。
示例代码
#include <iostream>
#include <map>int main() {std::map<int, std::string> students;// 插入键值对students.insert(std::pair<int, std::string>(1, "Alice"));students.insert(std::make_pair(2, "Bob"));students[3] = "Charlie"; // 使用下标操作符插入// 查找键为 2 的元素auto it = students.find(2);if (it != students.end()) {std::cout << "Student ID: " << it->first << ", Name: " << it->second << std::endl;}// 遍历 mapstd::cout << "All students:" << std::endl;for (const auto& pair : students) {std::cout << "Student ID: " << pair.first << ", Name: " << pair.second << std::endl;}// 删除键为 1 的元素students.erase(1);// 检查 map 是否为空if (students.empty()) {std::cout << "The map is empty." << std::endl;} else {std::cout << "The map is not empty." << std::endl;}return 0;
}
在这个示例中,我们创建了一个 std::map
,用于存储学生的 ID 和姓名。我们展示了如何插入元素、查找元素、遍历 map
以及删除元素。由于 std::map
是基于红黑树实现的,因此它的元素会按键值自动排序。
总结:
map的重要接口
1.构造
map提供了三种构造方式:1.空 2.迭代器范围 3.拷贝构造
2.operator[]
#include <iostream>
#include <map>int main() {std::map<int, std::string> studentGrades;// 插入或修改键值对studentGrades[1] = "A"; // 插入键为 1 的元素,值为 "A"studentGrades[2] = "B"; // 插入键为 2 的元素,值为 "B"studentGrades[1] = "A+"; // 修改键为 1 的元素的值// 使用 operator[] 访问元素std::cout << "Student ID 1 has grade: " << studentGrades[1] << std::endl;// 使用 operator[] 访问不存在的键,这将插入一个新的键值对// 假设 std::string 有一个默认构造函数,它将创建一个空字符串std::cout << "Student ID 3 has grade: " << studentGrades[3] << std::endl;// 打印所有学生的成绩std::cout << "All student grades:" << std::endl;for (const auto& pair : studentGrades) {std::cout << "Student ID: " << pair.first << ", Grade: " << pair.second << std::endl;}return 0;
}
支持迭代器范围for,但是这是个pair对象
函数声明 | 功能简介 |
pair<iterator,bool> insert ( const value_type& x ) | 在 map 中插入键值对 x ,注意 x 是一个键值 对,返回值也是键值对: iterator 代表新插入 元素的位置, bool 代表释放插入成功 |
void erase ( iterator position ) | 删除 position 位置上的元素 |
size_type erase ( const key_type& x ) | 删除键值为 x 的元素 |
void erase ( iterator first, iterator last ) | 删除 [first, last) 区间中的元素 |
void swap ( map<Key,T,Compare,Allocator>& mp ) | 交换两个 map 中的元素 |
void clear ( ) | 将 map 中的元素清空 |
iterator find ( const key_type& x ) | 在 map 中插入 key 为 x 的元素,找到返回该元 素的位置的迭代器,否则返回 end |
const_iterator find ( const key_type& x ) const | 在 map 中插入 key 为 x 的元素,找到返回该元 素的位置的 const 迭代器,否则返回 cend |
size_type count ( const key_type& x ) const | 返回 key 为 x 的键值在 map 中的个数,注意 map 中 key 是唯一的,因此该函数的返回值 要么为 0 ,要么为 1 ,因此也可以用该函数来 检测一个 key 是否在 map 中 |
这里的value_type(一个K/V对)等介绍
当然也可以make_pair("sort", "排序“)
map的key不希望被修改,但是value可以
数据的遍历
需要注意的是,由于 operator[] 在键不存在时会插入一个新的元素,因此如果键的类型没有默认构造函数,或者默认构造的值不是期望的,那么直接使用 operator[] 可能会导致意外的行为。在这种情况下,应该使用 find 或 insert 方法来更安全地处理元素。
multimap
multimap不支持[],他不知道返回哪一个键值的val的引用
std::multimap
是 C++ 标准库中的一个关联容器,它存储键值对,其中每个键可以与多个值相关联。与 std::map
不同,std::map
中每个键只能有一个对应的值,而 std::multimap
允许键有多个副本,每个副本都与不同的值相关联。
以下是 std::multimap
的一些详细信息:
特点
std::multimap
中的元素总是按键的升序排列。- 它使用红黑树实现,确保了元素的有序性。
- 每个键可以出现多次,每个键关联的值可以不同。
- 不支持通过键直接访问单个元素,因为可能有多个元素具有相同的键。
- 查找、插入和删除操作的时间复杂度为 O(log n),其中 n 是容器中元素的数量。
成员函数
insert
:插入一个或多个元素。erase
:删除一个或多个元素。find
:查找具有特定键的元素。count
:返回具有特定键的元素的数量。lower_bound
和upper_bound
:返回一个迭代器,分别指向第一个不小于(或等于)给定键的元素和第一个大于给定键的元素。equal_range
:返回一个迭代器对,表示具有特定键的所有元素的范围。