Bootstrap

C++ map:高效的键值对存储与查找机制

在 C++ 中,map 是一个非常重要的标准库容器,它允许以键值对的形式存储数据,并通过键快速查找对应的值。map 是一种有序容器,其内部元素根据键值自动排序。它提供了高效的插入、删除和查找操作,通常是基于红黑树(或其他自平衡二叉搜索树)来实现的。

1. map 的基础概念

map 是 C++ STL(标准模板库)中的一个关联容器,它存储键值对(key-value pair)。每个元素由一个键(key)和一个值(value)组成,并且键是唯一的。

主要特点:

- 键唯一:每个键只能在 map 中出现一次。

- 自动排序:元素根据键值自动排序,默认按升序排列。可以自定义排序规则。

- 高效查找:查找操作的时间复杂度通常是 O(log n),这是因为 map 底层使用了自平衡二叉搜索树(如红黑树)。

2. map的构造函数

(1)空构造函数
map的空构造函数创建一个空的map容器,初始化时可以指定自定义的比较函数和分配器。

explicit map(const key_compare& comp = key_compare(),
             const allocator_type& alloc = allocator_type());

comp:用于比较键的自定义比较函数,默认使用key_compare
alloc:自定义的分配器,默认使用allocator_type

 // 使用默认构造函数创建空的map
    map<int, string> myMap;
    
    // 使用自定义比较函数创建map
    map<int, string, greater<int>> customMap;

    // 使用自定义分配器
    map<int, string, less<int>, allocator<pair<const int, string>>> allocMap;

(2)使用分配器构造
该构造函数接受一个自定义分配器来创建一个空的map

explicit map(const allocator_type& alloc);
   // 使用自定义分配器创建map
    map<int, string, less<int>, allocator<pair<const int, string>>> allocMap;

    allocMap[1] = "one";
    allocMap[2] = "two";

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

(3)区间构造
使用输入迭代器指定的范围来构造map,并且可以指定比较函数和分配器。

template <class InputIterator>
map(InputIterator first, InputIterator last,
    const key_compare& comp = key_compare(),
    const allocator_type& alloc = allocator_type());

first和last:指定区间的开始和结束迭代器。
comp和alloc:分别指定自定义的比较函数和分配器,默认为默认构造函数。

vector<pair<int, string>> vec = {
  
  {1, "one"}, {2, "two"}, {3, "three"}};

    // 使用区间构造函数创建map
    map<int, string> myMap(vec.begin(), vec.end());

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

(4)拷贝构造
拷贝构造函数用于根据另一个map对象创建新对象。

map(const map& x);
map(const map& x, const allocator_type& alloc);

x:要拷贝的map对象。

  map<int, string> originalMap;
    originalMap[1] = "one";
    originalMap[2] = "two";

    // 使用拷贝构造函数创建map
    map<int, string> copiedMap(originalMap);

(5)初始化列表构造
通过初始化列表来构造map,并且可以指定比较函数和分配器。

map(initializer_list<value_type> il,
    const key_compare& comp = key_compare(),
    const allocator_type& alloc = allocator_type());

il:初始化列表,用来直接初始化map中的元素。

 // 使用初始化列表构造map
    map<int, string> myMap = {
  
  {1, "one"}, {2, "two"}, {3, "three"}};

3. map的迭代器

在 C++ 中,map 是一个关联容器,它将键(key)映射到值(value)。对于 map 容器的迭代操作,可以使用迭代器(iterator)来访问 map 中的元素。map 的迭代器是双向迭代器,允许你沿着容器进行前向和后向迭代。

1. 基本的迭代方式

map 的迭代器是一个 pair<const Key, T> 类型的对象,其中 first 是键(const Key),second 是值(T)。因此,你可以通过迭代器访问每一个元素的键和值。

#include <iostream>
#include <map>
using namespace std;

int main() {
    map<int, string> myMap;
    myMap[1] = "one";
    myMap[2] = "two";
    myMap[3] = "three";

    // 使用迭代器遍历 map
    for (auto it = myMap.begin(); it != myMap.end(); ++it) {
        cout << "Key: " << it->first << ", Value: " << it->second << endl;
    }

    return 0;
}

使用范围基于的 for 循环

#include <iostream>
#include <map>
using namespace std;

int main() {
    map<int, string> myMap;
    myMap[1] = "one";
    myMap[2] = "two";
    myMap[3] = "three";
    // 使用范围基于的 for 循环遍历 map
    for (const auto& pair : myMap) {
        cout << "Key: " << pair.first << ", Value: " << pair.second << endl;
    }
    return 0;
}

2. 反向迭代器(Reverse Iterator)

map 也支持反向迭代器,即 rbegin()rend()。这使得你可以从 map 的最后一个元素开始向前遍历。

#include <iostream>
#include <map>
using namespace std;

int main() {
    map<int, string> myMap;
    myMap[1] = "one";
    myMap[2] = "two";
    myMap[3] = "three";

    // 使用反向迭代器遍历 map
    for (auto rit = myMap.rbegin(); rit != myMap.rend(); ++rit) {
        cout << "Key: " << rit->first << ", Value: " << rit->second << endl;
    }

    return 0;
}

3. 插入和删除元素时的迭代器注意事项

  • 插入新元素:map 容器会自动根据键的排序插入元素。
  • 删除元素:删除元素时,迭代器会失效,特别是删除当前元素时,后续元素的位置会发生变化。要小心使用。
#include <iostream>
#include <map>
using namespace std;

int main() {
    map<int, string> myMap;
    myMap[1] = "one";
    myMap[2] = "two";
    myMap[3] = "three";

    // 删除元素
    auto it = myMap.find(2);  // 找到键为 2 的元素
    if (it != myMap.end()) {
        myMap.erase(it);  // 删除该元素
    }

    // 遍历 map
    for (auto it = myMap.begin(); it != myMap.end(); ++it) {
        cout << "Key: " << it->first << ", Value: " << it->second << endl;
    }

    return 0;
}

4. 容量相关和元素访问

4.1 容量相关成员函数

(1)empty

- 作用:检查 map 容器是否为空。

- 返回值:返回一个布尔值,如果容器为空返回 true,否则返回 false

(2)size

- 作用:返回容器中元素的数量。

- 返回值:返回容器中元素的个数。

(3)max_size

- 作用:返回容器可能容纳的最大元素数量。

- 返回值:返回一个 size_type 类型的值,表示容器能够容纳的最大元素数。注意:这个值并不一定表示实际可以存储的大小,具体值依赖于系统的实现。

4.2 Element Access(元素访问)

(1)operator[]

- 作用:通过键访问 map 中的元素。如果键存在,返回对应的值;如果键不存在,则插入一个新的键值对,键为提供的键,值为默认构造的值。

- 返回值:返回与指定键关联的元素。如果键不存在,则会插入一个新的键值对并返回其对应的值。

- 时间复杂度:最坏情况下为 O(log n)(由于 map 是基于红黑树实现的)。

(2)at

- 作用:通过键访问 map 中的元素。如果键存在,返回对应的值;如果键不存在,抛出 out_of_range 异常。

- 返回值:返回与指定键关联的元素。

- 时间复杂度:最坏情况下为 O(log n)(由于 map 是基于红黑树实现的)。

- 注意:与 operator[] 不同的是,at 不会插入新元素,而是直接抛出异常。

#include <iostream>
#include <map>
using namespace std;

int main() {
    map<int, string> myMap;

    // 添加元素
    myMap[1] = "one";
    myMap[2] = "two";
    myMap[3] = "three";

    // empty
    cout << "Is the map empty? " << (myMap.empty() ? "Yes" : "No") << endl;

    // size
    cout << "Size of map: " << myMap.size() << endl;

    // max_size
    cout << "Max size of map: " << myMap.max_size() << endl;

    // operator[]
    cout << "Element with key 2: " << myMap[2] << endl;

    // at
    try {
        cout << "Element with key 3: " << myMap.at(3) << endl;
        // 尝试访问不存在的元素
        cout << "Element with key 4: " << myMap.at(4) << endl;
    } catch (const out_of_range& e) {
        cout << "Error: " << e.what() << endl;
    }

    return 0;
}

总结:

  • empty 用于检查 map 是否为空。
  • size 返回容器中元素的数量。
  • max_size 返回容器可以容纳的最大元素数量。
  • operator[] 用于访问或插入元素,如果元素不存在则会插入新的元素。
  • at 用于访问元素,如果元素不存在会抛出异常。

5.  map修饰相关成员变量

5.1 find

1. 插入单个元素

pair<iterator,bool> insert(const value_type& val);
template <class P> pair<iterator,bool> insert(P&& val);
  • 描述:插入一个单独的元素 val,并返回一个 pair,其中 pair.first 是指向插入元素的迭代器,pair.second 是一个布尔值,表示插入是否成功。
    • 如果 val 的键已存在于 map 中,插入失败,pair.secondfalse
    • 如果 val 的键不存在于 map 中,则插入成功,pair.secondtrue
  • 使用左值(const value_type& val:插入一个左值元素,会触发拷贝构造。
  • 使用右值(P&& val:插入一个右值元素,触发移动构造,这通常比拷贝构造更高效。
map<int, string> myMap;
pair<map<int, string>::iterator, bool> result = myMap.insert({1, "one"});
if (result.second) 
{
    cout << "Element inserted!" << endl;
} else {
    cout << "Element already exists!" << endl;
}

2. 带位置提示的插入

iterator insert(const_iterator position, const value_type& val);
template <class P> iterator insert(const_iterator position, P&& val);
  • 描述:使用给定的 position(插入位置提示)来插入一个元素 valposition 是一个迭代器,指向 map 中的一个位置。此版本的插入操作可能比不带位置提示的插入更高效,因为它可以减少在插入时可能需要的元素移动。
  • 返回值:返回一个迭代器,指向插入的元素。如果插入失败(键已存在),则返回一个指向原始元素的迭代器。
map<int, string> myMap;
auto it = myMap.insert(myMap.begin(), {1, "one"});

如果插入位置已知(例如想在开头或特定位置插入元素),使用带位置提示的插入能提高效率。

3. 区间插入

template <class InputIterator>
void insert(InputIterator first, InputIterator last);
  • 描述:通过提供的输入迭代器区间 [first, last) 插入一系列元素。这种方法适用于将另一个容器或数组的元素批量插入到 map 中。
  • 返回值:此插入方法没有返回值,它直接修改原始 map
map<int, string> myMap;
vector<pair<int, string>> vec = {
  
  {1, "one"}, {2, "two"}};
myMap.insert(vec.begin(), vec.end());

这里使用区间插入将一个 vector 的元素批量插入到 map 中。

4. 初始化列表插入

void insert(initializer_list<value_type> il);
  • 描述:使用初始化列表插入一组元素。初始化列表提供了一个简便的语法来直接创建和插入多个键值对元素。
  • 返回值:与其他插入方法一样,这个版本的 insert 不返回值,它直接将元素插入到 map 中。

示例

map<int, string> myMap;
myMap.insert({
  
  {1, "one"}, {2, "two"}, {3, "three"}});

总结

这四种插入方法都可以用于向 map 中添加元素,但它们适用于不同的场景:

  • 单个元素插入适合添加一个元素,支持左值和右值插入。
  • 带位置提示的插入适合在已知位置插入元素,效率更高。
  • 区间插入适合批量插入元素,常用于将其他容器的元素插入到 map 中。
  • 初始化列表插入则是一种简洁的批量插入方式,适合直接插入多个元素。

5.2 erase

1. 根据位置删除元素

iterator erase(const_iterator position);
  • 描述:根据给定的迭代器 position 删除 map 中的元素。
  • 返回值:返回一个指向删除元素后紧随其后的元素的迭代器。如果删除的是 map 中的最后一个元素,则返回 end() 迭代器。
  • 使用场景:当你知道要删除元素的位置时,可以使用这种方法。
map<int, string> myMap;
myMap[1] = "one";
myMap[2] = "two";
myMap[3] = "three";

auto it = myMap.find(2); // 查找要删除的元素
if (it != myMap.end()) {
    myMap.erase(it); // 删除元素
}

2. 根据键删除元素

size_type erase(const key_type& k);
  • 描述:根据指定的键 k 删除 map 中的元素。如果 map 中包含该键值对,则删除,并返回删除的元素数量(对于 map 来说,最多删除一个元素)。
  • 返回值:返回一个 size_type(通常是 size_t 类型),表示成功删除的元素个数。对于 map 来说,结果要么是 0(表示没有找到该键值对),要么是 1(表示删除成功)。
  • 使用场景:当你知道要删除的元素的键时,可以使用这种方法。
map<int, string> myMap;
myMap[1] = "one";
myMap[2] = "two";
myMap[3] = "three";

size_t count = myMap.erase(2); // 删除键为 2 的元素
cout << "Deleted " << count << " element(s)." << endl;

3. 根据迭代器区间删除元素

iterator erase(const_iterator first, const_iterator last);
  • 描述:根据给定的迭代器区间 [first, last) 删除 map 中的所有元素。区间的开始是 first,结束是 last,包括 first 所指向的元素,但不包括 last 所指向的元素。
  • 返回值:返回一个迭代器,指向删除区间之后的第一个元素(如果删除了最后一个元素,则返回 end() 迭代器)。
  • 使用场景:当你需要删除一段连续的元素时,可以使用这种方法。

示例

map<int, string> myMap;
myMap[1] = "one";
myMap[2] = "two";
myMap[3] = "three";
myMap[4] = "four";

auto first = myMap.find(2);
auto last = myMap.find(4);

myMap.erase(first, last); // 删除从键 2 到键 4 的元素(不包括键 4)

5.3 其它操作

1. swap

void swap(map& other);

交换当前 map 容器与另一个 map 容器的内容。调用此函数后,当前容器和目标容器的元素会互换,它们的内容、大小以及其他状态都会被交换。

map<int, string> map1 = {
  
  {1, "one"}, {2, "two"}};
map<int, string> map2 = {
  
  {3, "three"}, {4, "four"}};

map1.swap(map2); // 交换 map1 和 map2 的内容

// 此时 map1 中的元素为 {3, "three"}, {4, "four"}
// map2 中的元素为 {1, "one"}, {2, "two"}

2. clear

void clear();

清空 map 容器中的所有元素。调用此函数后,map 将变为空容器。

map<int, string> myMap = {
  
  {1, "one"}, {2, "two"}};
myMap.clear(); // 清空所有元素

// 此时 myMap 变为空容器

3. emplace

template <class... Args>
pair<iterator, bool> emplace(Args&&... args);

构造并插入一个新的元素到 map 中。emplace 通过直接在容器中构造元素,避免了不必要的临时对象的创建,因此它比 insert 更高效。它返回一个 pair,包含一个迭代器和一个布尔值,迭代器指向插入元素的位置,布尔值表示插入是否成功(true 表示成功,false 表示键已经存在)。

map<int, string> myMap;
myMap.emplace(1, "one"); // 直接在 map 中构造并插入元素

// 另一种方式,使用多参数构造:
myMap.emplace(std::make_pair(2, "two"));

4. emplace_hint

iterator emplace_hint(const_iterator hint, Args&&... args);

类似于 emplace,但是此函数接受一个迭代器 hint,作为一个位置提示。提示的位置可以帮助 map 更高效地插入元素,尤其是当你已经知道插入位置时。该函数会在提示位置附近尝试插入元素,但不一定会在该位置插入。 

map<int, string> myMap = {
  
  {1, "one"}, {3, "three"}};
auto it = myMap.find(3);
myMap.emplace_hint(it, 2, "two"); // 插入 (2, "two"),提示位置为迭代器 it

// 此时 myMap 的内容为 {1, "one"}, {2, "two"}, {3, "three"}

6. map操作成员变量

1. find

iterator find(const key_type& k);
const_iterator find(const key_type& k) const;
  • 描述:该函数查找并返回与指定键 k 相关联的元素的迭代器。如果找到了该元素,返回指向该元素的迭代器;如果没有找到,返回 end() 迭代器。
map<int, string> myMap = {
  
  {1, "one"}, {2, "two"}, {3, "three"}};
auto it = myMap.find(2); // 查找键为 2 的元素

if (it != myMap.end()) {
    cout << "Found: " << it->second << endl; // 输出 "Found: two"
} else {
    cout << "Not found" << endl;
}

2. count

size_type count(const key_type& k) const;
  • 描述:该函数返回与指定键 k 相关联的元素的个数。由于 map 中每个键是唯一的,因此结果要么是 0(没有找到键)要么是 1(找到了键)。
  • 时间复杂度为 O(log n)
map<int, string> myMap = {
  
  {1, "one"}, {2, "two"}};
cout << myMap.count(2) << endl; // 输出 1,因为键 2 存在
cout << myMap.count(3) << endl; // 输出 0,因为键 3 不存在

3. lower_bound

iterator lower_bound(const key_type& k);
const_iterator lower_bound(const key_type& k) const;
  • 描述:该函数返回指向 map 中第一个不小于指定键 k 的元素的迭代器。也就是说,它返回的是一个迭代器,该元素的键大于或等于 k。如果找到了 k,则返回指向该元素的迭代器;如果 k 比所有元素都大,则返回 end()
  • 使用场景:你可以使用 lower_bound 来查找一个键所在位置的插入位置,或者查找第一个大于或等于给定键的位置。
map<int, string> myMap = {
  
  {1, "one"}, {3, "three"}, {5, "five"}};
auto it = myMap.lower_bound(3); // 返回第一个大于或等于 3 的元素

cout << it->first << " : " << it->second << endl; // 输出 "3 : three"

4. upper_bound

iterator upper_bound(const key_type& k);
const_iterator upper_bound(const key_type& k) const;
  • 描述:该函数返回指向 map 中第一个大于指定键 k 的元素的迭代器。如果没有找到大于 k 的元素,它将返回 end()
  • 使用场景upper_bound 常用于查找第一个比给定键大的元素,适用于某些区间查找操作。
map<int, string> myMap = {
  
  {1, "one"}, {3, "three"}, {5, "five"}};
auto it = myMap.upper_bound(3); // 返回第一个大于 3 的元素

cout << it->first << " : " << it->second << endl; // 输出 "5 : five"

5. equal_range

pair<iterator, iterator> equal_range(const key_type& k);
pair<const_iterator, const_iterator> equal_range(const key_type& k) const;
  • 描述:该函数返回一个 pair,其中包含两个迭代器:
    • 第一个迭代器指向第一个大于或等于 k 的元素(即 lower_bound(k) 的结果)。
    • 第二个迭代器指向第一个大于 k 的元素(即 upper_bound(k) 的结果)。 这两个迭代器指定了所有等于 k 的元素的范围。如果没有找到相等的元素,则第一个迭代器等于第二个迭代器。
  • 使用场景equal_range 可以用于查找所有等于某个键的元素范围,尽管在 map 中每个键都是唯一的,因此该范围通常包含一个元素。
map<int, string> myMap = {
  
  {1, "one"}, {3, "three"}, {5, "five"}};
auto range = myMap.equal_range(3); // 获取键为 3 的元素范围

cout << "lower_bound: " << range.first->second << endl; // 输出 "three"
cout << "upper_bound: " << range.second->second << endl; // 输出 "five"

这些函数的时间复杂度通常为 O(log n),因为 map 的底层实现是平衡二叉树(如红黑树)。

;