【C++】map和set详解

目录

[1. 关联式容器](#1. 关联式容器)

[2. 键值对pair](#2. 键值对pair)

[3. 树形结构的关联式容器](#3. 树形结构的关联式容器)

[4. set](#4. set)

[4.1 set的介绍](#4.1 set的介绍)

[4.2 set的构造](#4.2 set的构造)

[4.3 set的迭代器](#4.3 set的迭代器)

[4.4 set的容量](#4.4 set的容量)

[4.5 set的常用函数](#4.5 set的常用函数)

[5. multiset](#5. multiset)

[6. map](#6. map)

[6.1 map的介绍](#6.1 map的介绍)

[6.2 map的构造](#6.2 map的构造)

[6.3 map的迭代器](#6.3 map的迭代器)

[6.4 map的容量](#6.4 map的容量)

[6.5 map的operator[]](#6.5 map的operator[])

[6.6 map的常用函数](#6.6 map的常用函数)

[7. multimap](#7. multimap)


1. 关联式容器

在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、 forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面 存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?

关联式容器(associative containers)与序列式容器(sequential containers)是C++标准库中的两种不同类型的容器。

**序列式容器是一种按照元素在容器中的位置进行存储和访问的容器。**它们按照元素的插入顺序进行存储,并且支持顺序访问元素的特性。常见的序列式容器有:vector、deque、list和forward_list。

**关联式容器是一种按照键值(key)进行存储和访问的容器。**它们通过一对键值对(key-value pair)来存储元素,其中的键(key)用于唯一标识元素,并且支持根据键值进行快速检索的特性。常见的关联式容器有:set、multiset、map和multimap。

2. 键值对pair

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然 有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。

SGI-STL中关于键值对的定义:

cpp 复制代码
template <class T1, class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 second_type;
	T1 first;    //key
	T2 second;    //value

    //构造函数
	pair() : first(T1()), second(T2())
	{}
	pair(const T1& a, const T2& b) : first(a), second(b)
	{}
};

所以,C++ 中的键值对是通过 pair 类来表示的。

pair包含两个成员变量first和second,分别表示两个值的类型T1和T2。我们可以使用pair来创建键值对或者存储两个值。

cpp 复制代码
int main() 
{
    // 创建一个键值对
    pair<int, string> student(1, "Alice");

    // 访问键值对的值
    cout << student.first << ": " << student.second << endl;

    // 修改键值对的值
    student.first = 2;
    student.second = "Bob";

    // 创建临时键值对
    pair<int, string> temp(3, "Charlie");

    // 使用make_pair创建临时键值对
    auto temp2 = make_pair(4, "David");

    return 0;
}

pair可以用于存储键值对,也可以用于其他需要存储两个不同类型的值的场景。它在C++标准库中被广泛使用,例如在map、unordered_map等容器中存储键值对。

此外,库中还设计了一个函数模板make_pai r, 可以根据传入的参数,去调用 pair 构建对象并返回。

cpp 复制代码
template <class T1, class T2>
constexpr pair<V1, V2> make_pair(T1&& t1, T2&& t2);

make_pair接受两个参数t1和t2,并将它们打包为一个pair对象并返回。make_pair会自动推导出pair对象中的第一个值和第二个值的类型。

以下是使用make_pair创建pair对象的示例:

cpp 复制代码
int main() 
{
    // 创建pair对象
    auto p1 = make_pair(1, "Alice");

    // 创建pair对象并指定类型
    pair<int, string> p2 = make_pair(2, "Bob");

    // 输出pair对象的值
    cout << p1.first << ": " << p1.second << endl;
    cout << p2.first << ": " << p2.second << endl;

    return 0;
}

**make_pair可以方便地创建pair对象,无需显式指定类型,编译器会根据参数类型进行类型推导。**它常用于在容器中插入键值对或者返回键值对的函数中。

3. 树形结构的关联式容器

根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结 构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一 个容器。

4. set

4.1 set的介绍

set容器:它是一个关联容器,用于存储唯一的元素,且按照一定的顺序排列。set容器中的元素按照默认的排序顺序存储,或者可以通过自定义的排序函数进行排序。set容器实际上是一个红黑树(red-black tree)实现的。

set容器中的元素会自动按照键的顺序进行排序,保持元素的有序性。同时,set容器中的元素是唯一的 ,即相同的元素只能出现一次

4.2 set的构造

set容器可以使用多种方式进行构造,以下是常见的构造方式:

  1. 默认构造函数:
cpp 复制代码
set<int> mySet; // 创建一个空的set容器
  1. 使用迭代器构造:
cpp 复制代码
vector<int> vec = {1, 2, 3, 4, 5};
set<int> mySet(vec.begin(), vec.end()); // 使用vec容器中的元素构造set容器
  1. 使用初始化列表构造:
cpp 复制代码
set<int> mySet = {1, 2, 3, 4, 5}; // 使用初始化列表中的元素构造set容器
  1. 拷贝构造函数:
cpp 复制代码
set<int> mySet1 = {1, 2, 3, 4, 5};
set<int> mySet2(mySet1); // 使用另一个set容器的副本构造新的set容器

需要注意的是,set容器中的元素默认按照升序进行排序。如果希望使用自定义的排序规则,可以在构造过程中提供一个比较函数对象作为参数,或者通过重载元素类型的比较运算符来实现。

例如,如果要创建一个按照降序排序的set容器,可以使用以下方式构造:

cpp 复制代码
struct Compare 
{
    bool operator()(int a, int b) const 
    {
        return a > b;
    }
};

set<int, Compare> mySet; // 使用自定义的比较函数对象构造set容器

4.3 set的迭代器

set容器提供了两种类型的迭代器:正向迭代器和反向迭代器。

正向迭代器:

  • begin():返回指向set容器中第一个元素的迭代器。
  • end():返回指向set容器中最后一个元素之后位置的迭代器。
cpp 复制代码
int main()
{
	set<int> s1 = { 1, 4, 2, 3, 5 };

	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	return 0;
}

反向迭代器:

  • rbegin():返回指向set容器中最后一个元素的迭代器。
  • rend():返回指向set容器中第一个元素之前位置的迭代器。
cpp 复制代码
int main()
{
	set<int> s1 = { 1, 4, 2, 3, 5 };


	set<int>::reverse_iterator rit = s1.rbegin();

	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

	return 0;
}

4.4 set的容量

set容器提供了以下几个容量函数来获取容器的大小和容量信息:

  1. size():返回set容器中元素的个数。
  2. empty():判断set容器是否为空,如果为空返回true,否则返回false。
  3. max_size():返回set容器能够容纳的最大元素个数,这个值取决于系统或者编译器的限制。
cpp 复制代码
int main() 
{
    set<int> mySet = { 1, 4, 2, 3, 5 };

    // 使用size()函数获取set的大小
    cout << "Set的大小:" << mySet.size() << endl;

    // 使用empty()函数判断set是否为空
    if (mySet.empty()) 
    {
        cout << "Set为空" << endl;
    }
    else 
    {
        cout << "Set不为空" << endl;
    }

    // 使用max_size()函数获取set的最大容量
    cout << "Set的最大容量:" << mySet.max_size() << endl;

    return 0;
}

4.5 set的常用函数

set容器提供了一些常用的函数来进行元素的插入、删除、查找等操作。下面是set容器的一些常用函数:

  1. 插入元素:

    insert(val):将元素val插入到set容器中。

    insert(first, last):将区间[first, last)内的元素插入到set容器中。

  2. 删除元素:

    erase(val):删除set容器中与val相等的元素。

    erase(iterator):删除指定迭代器指向的元素。

    erase(first, last):删除区间[first, last)内的元素。

    clear():清空set容器中的所有元素。

  3. 查找元素:

    find(val):在set容器中查找与val相等的第一个元素,如果找到返回指向该元素的迭代器,否则返回end()迭代器。

    count(val):返回set容器中与val相等的元素的个数,由于set容器中元素的唯一性,所以返回值只能是0或1。

    lower_bound(val):返回一个迭代器,指向set容器中第一个不小于val的元素,如果不存在这样的元素,则返回set容器的end()迭代器。

    upper_bound(val):返回一个迭代器,指向set容器中第一个大于val的元素,如果不存在这样的元素,则返回set容器的end()迭代器。

    equal_range(val):返回一个pair对象,包含一个迭代器范围,表示与val相等的元素在set容器中的位置。如果不存在这样的元素,则返回一个pair对象,两个迭代器都指向set容器的end()位置。

需要注意的是,set容器中的元素是按照一定的排序方式进行存储的,默认情况下是按照升序排列。另外,由于set容器中元素的唯一性,所以插入重复的元素时,只会插入一个。

cpp 复制代码
int main() 
{
    set<int> mySet;

    // 插入元素到set
    mySet.insert(10);
    mySet.insert(20);
    mySet.insert(30);

    // 查找元素
    set<int>::iterator it = mySet.find(20);
    if (it != mySet.end()) 
    {
        cout << "元素存在" << endl;
    }
    else 
    {
        cout << "元素不存在" << endl;
    }

    // 删除元素
    mySet.erase(20);

    // 获取set的大小
    cout << "set的大小为:" << mySet.size() << endl;

    // 清空set
    mySet.clear();

    // 判断set是否为空
    if (mySet.empty()) 
    {
        cout << "set为空" << endl;
    }
    else 
    {
        cout << "set不为空" << endl;
    }

    return 0;
}

5. multiset

multiset是C++ STL中的容器,它是一个有序的集合,可以存储多个相同的元素。

此外,multiset 查找冗余的数据时,返回的是中序遍历中,第一次出现的元素

multiset的特点包括:

  • 元素的插入是有序的,插入操作会将元素按照一定的顺序插入到容器中。
  • 允许存储重复的元素,即可以插入相同的元素多次。
  • multiset中的元素是自动排序的,默认按照元素的升序进行排序,也可以通过指定比较函数来按照其他方式进行排序。
  • 支持快速的插入和删除操作,时间复杂度为O(logN)。
cpp 复制代码
int main() 
{
    multiset<int> numbers;

    // 向multiset中插入元素
    numbers.insert(1);
    numbers.insert(3);
    numbers.insert(2);
    numbers.insert(2);

    cout << "multiset中的元素:" << endl;
    for (auto it = numbers.begin(); it != numbers.end(); ++it) 
    {
        cout << *it << " ";
    }
    cout << endl;

    // 查找multiset中的元素
    auto it = numbers.find(2);
    if (it != numbers.end()) 
    {
        cout << "找到元素2" << endl;
    }
    else 
    {
        cout << "未找到元素2" << endl;
    }

    // 删除multiset中的元素
    numbers.erase(2);

    cout << "删除元素2后的multiset:" << endl;
    for (auto it = numbers.begin(); it != numbers.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;

    return 0;
}

6. map

6.1 map的介绍

map容器:它是一个关联容器,用于存储键值对(key-value pairs)。其中的每个元素都由一个键(key)和一个值(value)组成。map容器中的元素按照键的顺序进行排序,并且每个键只能在map中出现一次。因此可以通过键快速查找和访问对应的值。map容器实际上是一个红黑树(red-black tree)实现的。

map的特点包括:

  1. 键唯一性:每个键只能在map中出现一次,如果插入相同键的元素,后面的插入会覆盖前面的。

  2. 按键排序:map中的元素按照键的大小进行排序,默认是按照升序排序。

  3. 二叉搜索树实现:map内部使用红黑树(Red-Black Tree)来实现元素的存储和排序,因此插入、查找和删除等操作的时间复杂度都是O(log n)。

  4. 动态大小:map的大小可以根据需要动态地增加或减小。

key: 键值对中key的类型

T: 键值对中value的类型

Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户 自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)

Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的 空间配置器

6.2 map的构造

在C++中,可以使用多种方式构造一个map对象。下面列举几种常用的构造方式:

  1. 默认构造函数:创建一个空的map对象。
cpp 复制代码
map<Key, Value> myMap;
  1. 初始化列表构造函数:通过初始化列表,创建一个包含多个键值对的map对象。
cpp 复制代码
map<int, string> myMap = { {1, "one"}, {2, "two"}, {3, "three"} };
  1. 使用迭代器构造函数:通过指定起始和结束迭代器,创建一个包含其他容器中元素的map对象。
cpp 复制代码
vector<pair<int, string>> vec = { {1, "one"}, {2, "two"}, {3, "three"} };
map<int, string> myMap(vec.begin(), vec.end());
  1. 复制构造函数:以另一个map对象作为参数,创建一个与其相同的map对象。
cpp 复制代码
map<int, string> anotherMap(myMap);
  1. 移动构造函数:以另一个map对象的右值引用作为参数,创建一个新的map对象,同时将原来的map对象置为空。
cpp 复制代码
map<int, string> newMap(move(myMap));

需要注意的是,在使用map构造函数时,键值对的顺序并不影响map中元素的插入顺序,因为map内部是按照键的排序顺序进行存储和访问的。

此外,使用map的构造函数创建新的map对象时,会根据键的类型进行比较和排序,默认是使用less<Key>来进行比较。如果键的类型没有定义比较函数,则需要通过自定义比较函数或提供自定义的比较类作为额外的模板参数来使用。

6.3 map的迭代器

map提供了两种类型的迭代器:正向迭代器和反向迭代器

正向迭代器:

  • begin():返回指向map中第一个元素的迭代器。
  • end():返回指向map中最后一个元素之后位置的迭代器,相当于尾后迭代器。
cpp 复制代码
int main()
{
	map<int, string> myMap = { {1, "one"}, {2, "two"}, {3, "three"} };

	auto it = myMap.begin();
	while (it != myMap.end())
	{
		cout << it->first << ": " << it->second << endl;
		++it;
	}

	return 0;
}

反向迭代器:

  • rbegin():返回指向map中最后一个元素的迭代器。
  • rend():返回指向map中第一个元素之前位置的迭代器,相当于尾前迭代器。
cpp 复制代码
int main()
{
	map<int, string> myMap = { {1, "one"}, {2, "two"}, {3, "three"} };

	auto rit = myMap.rbegin();
	while (rit != myMap.rend())
	{
		cout << rit->first << ": " << rit->second << endl;
		++rit;
	}

	return 0;
}

另外,C++11引入了范围-based for循环,可以更简洁地遍历map对象。

cpp 复制代码
int main()
{
	map<int, string> myMap = { {1, "one"}, {2, "two"}, {3, "three"} };


	for (const auto& pair : myMap) 
	{
		cout << pair.first << ": " << pair.second << endl;
	}

	return 0;
}

这种写法可以自动推导出迭代器的类型,并且使用引用避免了元素的复制。

6.4map的容量

获取容量信息:

  • size()函数:返回map中键-值对的数量。
  • empty()函数:如果map为空,则返回true;否则返回false。
cpp 复制代码
int main()
{
	map<int, string> myMap = { {1, "one"}, {2, "two"}, {3, "three"} };

	cout << "Size of map: " << myMap.size() << endl;
	cout << "Is map empty? " << (myMap.empty() ? "Yes" : "No") << endl;

	return 0;
}

6.5 map的operator[]

map的 operator[] 函数用于访问 map 中指定键的值。如果键存在,则返回对应的值;如果键不存在,则会插入一个新的键值对,并返回默认构造的值。

cpp 复制代码
int main() 
{
    map<int, string> map;

    // 插入键值对
    map[1] = "Alice";
    map[2] = "Bob";
    map[3] = "Charlie";

    // 访问指定键的值
    string name1 = map[1];
    cout << "键 1 对应的值是:" << name1 << endl;

    string name4 = map[4];
    cout << "键 4 对应的值是:" << name4 << endl;

    // 修改指定键的值
    map[1] = "Alex";

    // 输出所有键值对
    cout << "所有键值对:" << endl;
    for (const auto& pair : map) 
    {
        cout << "键: " << pair.first << " 值: " << pair.second << endl;
    }

    return 0;
}

6.6 map的常用函数

map是C++ STL提供的关联容器,提供了一系列的成员函数来操作和管理map。以下是一些map常用的函数:

  1. 插入元素:

    insert():将键值对插入到map中。

    emplace():在map中就地构造元素。

    emplace_hint():在指定位置之前插入元素。

  2. 访问元素:

    at():返回给定键对应的值,并进行边界检查。

    operator[]:根据键访问对应的值,如果键不存在,则插入对应的键值对。

    find():查找给定键的位置,并返回一个迭代器。

    count():返回给定键在map中出现的次数。

  3. 删除元素:

    erase():删除map中指定的元素或者一个范围内的元素。

    clear():清空map中的所有元素。

cpp 复制代码
int main() 
{
    map<string, int> scores;

    // 添加学生的成绩
    scores["Alice"] = 90;
    scores["Bob"] = 85;
    scores["Charlie"] = 95;
    scores["David"] = 80;

    // 更新学生的成绩
    scores["Alice"] += 5;

    // 查找学生的成绩
    string studentName = "Bob";
    if (scores.find(studentName) != scores.end()) 
    {
        cout << studentName << "的成绩是:" << scores[studentName] << endl;
    }
    else 
    {
        cout << "找不到" << studentName << "的成绩" << endl;
    }

    // 删除学生的成绩
    scores.erase("David");

    // 遍历输出所有学生的成绩
    cout << "所有学生的成绩:" << endl;
    for (const auto& pair : scores) 
    {
        cout << pair.first << "的成绩是:" << pair.second << endl;
    }

    return 0;
}

7. multimap

multimap是C++标准库中的一个关联容器,它允许存储一对键-值对,其中键可以重复。multimap内部会根据键的排序规则自动对元素进行排序,并且可以高效地进行插入、删除和查找操作。

map不同,multimap允许多个键相同的元素存在,因此它可以用于存储重复键的场景。multimap的实现基于红黑树,保证了元素的有序性,并提供了一系列函数来操作和访问multimap中的元素。

multimap的特点包括:

  • 元素有序:multimap中的元素按照键的排序规则自动进行排序,可以通过自定义比较函数来指定键的排序规则。
  • 允许重复键:multimap允许存储多个键相同的元素。
  • 动态大小:multimap可以动态地添加或删除元素,它会自动调整内部的存储空间。
  • 高效的插入和删除:multimap内部使用红黑树实现,可以在O(log n)的时间复杂度内进行插入、删除和查找操作。
  • 查找效率高:multimap提供了快速的查找操作,可以在O(log n)的时间复杂度内查找指定键的元素。

由于键可以重复,因此无法使用operator[]函数来直接访问元素,因为这样会产生歧义。

operator[]函数通常用于访问和修改容器中的元素。对于map容器来说,它的每个键只能对应一个值,因此可以使用operator[]通过键直接访问和修改对应的值。但是对于multimap容器,一个键可以对应多个值,如果使用operator[]来访问某个键,那么返回的应该是一个值的集合,而不是单个的值。这就会导致使用operator[]函数的结果不明确。

为了解决这个问题,multimap提供了equal_range函数来查找某个键对应的所有值的范围,然后可以通过迭代器遍历该范围内的所有值。

cpp 复制代码
int main() 
{
    // 创建一个multimap对象
    multimap<int, std::string> scores;

    // 插入元素
    scores.insert(std::make_pair(85, "Alice"));
    scores.insert(std::make_pair(92, "Bob"));
    scores.insert(std::make_pair(77, "Alice"));
    scores.insert(std::make_pair(92, "Charlie"));
    scores.insert(std::make_pair(80, "Alice"));

    // 遍历输出元素
    cout << "Multimap Elements:" << endl;
    for (const auto& score : scores) 
    {
        cout << "Score: " << score.first << ", Student: " << score.second << endl;
    }

    // 查找并输出分数为92的学生
    auto range = scores.equal_range(92);
    if (range.first != scores.end()) 
    {
        cout << "Students with score 92:" << endl;
        for (auto it = range.first; it != range.second; ++it) 
        {
            cout << "Student: " << it->second << endl;
        }
    }

    return 0;
}
相关推荐
AI街潜水的八角几秒前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
q5673152317 分钟前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
白榆maple25 分钟前
(蓝桥杯C/C++)——基础算法(下)
算法
JSU_曾是此间年少30 分钟前
数据结构——线性表与链表
数据结构·c++·算法
sjsjs1136 分钟前
【数据结构-合法括号字符串】【hard】【拼多多面试题】力扣32. 最长有效括号
数据结构·leetcode
许野平42 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
也无晴也无风雨1 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
狂奔solar1 小时前
yelp数据集上识别潜在的热门商家
开发语言·python
此生只爱蛋1 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
blammmp2 小时前
Java:数据结构-枚举
java·开发语言·数据结构