std::unordered_map是C++标准库中的一个关联容器,它可以存储一组键值对,并且支持快速的查找、插入和删除操作。
template,class Pred = std::equal_to,class Alloc = std::allocator > >class unordered_map;> class unordered_map
unordered_map
比较重要的构造函数如下:
std::unordered_map myMap;
创建一个空的unordered_map对象,键的类型为Key,值的类型为T。
std::unordered_map myMap(size_t n);
创建一个空的unordered_map对象,键的类型为Key,值的类型为T。这个构造函数会将unordered_map初始的桶数设置为n。
std::unordered_map myMap(size_t n, const Hash& hashFunc);
创建一个空的unordered_map对象,键的类型为Key,值的类型为T,并使用给定的哈希函数hashFunc。这个构造函数会将unordered_map初始的桶数设置为n。
插入操作:使用insert或emplace方法可以向unordered_map中插入一个元素,其中insert方法接受一个pair对象,emplace方法接受一个可变参数模板参数列表。如果unordered_map中已经存在相同的关键字,则插入操作会失败。
#include
#include int main() {std::unordered_map my_map;// 插入元素my_map.insert(std::make_pair("foo", 42));my_map.emplace("bar", 99);// 输出元素std::cout << "foo: " << my_map["foo"] << std::endl;std::cout << "bar: " << my_map["bar"] << std::endl;return 0;
}
使用erase方法可以删除unordered_map中的一个或多个元素。erase方法接受一个迭代器或一个范围,或者接受一个关键字作为参数。如果关键字不存在,则删除操作会失败。
#include
#include int main() {std::unordered_map my_map;// 插入元素my_map.insert(std::make_pair("foo", 42));my_map.emplace("bar", 99);// 删除元素my_map.erase("foo");// 输出元素std::cout << "bar: " << my_map["bar"] << std::endl;return 0;
}
使用find方法可以在unordered_map中查找一个元素,如果找到了,则返回指向该元素的迭代器,否则返回unordered_map::end()。如果想要判断unordered_map中是否存在某个关键字,则可以使用count方法。
#include
#include int main() {std::unordered_map my_map;// 插入元素my_map.insert(std::make_pair("foo", 42));my_map.emplace("bar", 99);// 查询元素auto iter = my_map.find("foo");if (iter != my_map.end()) {std::cout << "foo: " << iter->second << std::endl;}if (my_map.count("baz")) {std::cout << "baz exists in my_map" << std::endl;}return 0;
}
std::hash 是C++标准库中用于计算哈希值的模板类,用于将对象映射到哈希表中的桶(bucket)中。
std::hash的原型定义在头文件中,它是一个模板类,其原型如下:
namespace std {templatestruct hash {std::size_t operator()(const T& key) const;};
}
std::hash
是一个模板类,它接受一个类型参数T,用于指定需要哈希的对象类型。还提供了一个函数调用运算符(operator()
),用于计算对象的哈希值。
perator()
接受一个常量引用key,用于指定需要计算哈希值的对象。它返回一个std::size_t
类型的哈希值,表示对象在哈希表中的桶位置。
使用方法可以简单概括为以下几个步骤:
std::hash
对象,并将需要哈希的对象作为参数传递给该对象的函数调用运算符(operator()
)。std::size_t
类型的std::hash
对象的operator()
函数返回的哈希值。#include
#include
#include int main() {std::string str = "Hello, world!";std::hash hasher;std::size_t hash_value = hasher(str);std::cout << "The hash value of \"" << str << "\" is: " << hash_value << std::endl;return 0;
}
实现unordered_map的自定义键值类型,需以下步骤:
operator==
操作符。// 自定义键类型
class Person {
public:std::string name;int age;bool operator==(const Person& other) const {return name == other.name && age == other.age;}
};// 自定义值类型
class PhoneNumber {
public:std::string number;
};
在自定义的键类型中,重载operator==()
,进行两个键进行比较。
哈希函数的实现有三种方式:
// 实现哈希函数对象
struct PersonHasher {std::size_t operator()(const Person& person) const {std::size_t nameHash = std::hash()(person.name);std::size_t ageHash = std::hash()(person.age);return nameHash ^ (ageHash << 1);}
};std::unordered_map phoneBook; //不需要把哈希函数传入构造器// 添加键值对
phoneBook[{"Alice", 30}] = {"123-456-7890"};
phoneBook[{"Bob", 40}] = {"234-567-8901"};
// 查找键值对
auto aliceNumber = phoneBook.find({"Alice", 30});
if (aliceNumber != phoneBook.end()) {std::cout << "Alice's phone number is " << aliceNumber->second.number << std::endl;
}
// 实现普通的哈希函数
size_t PersonHash(const Person& person) {std::size_t nameHash = std::hash()(person.name);std::size_t ageHash = std::hash()(person.age);return nameHash ^ (ageHash << 1);
}
std::unordered_map> phoneBook(100, PersonHashFun);
这里phoneBook(100, PersonHashFun)
用到的是std::unordered_map
构造函数中的初始桶数和哈希函数构造函数。
std::function
定义了一个函数对象类型:接收一个const Person&
为参数,并返回一个size_t
类型的函数或者函数对象。这里也可以采用decltype
来声明这个函数对象类型:decltype(&PersonHashFun)
。
std::unordered_map> phoneBook(100, [] (const Person& person) -> size_t {std::size_t nameHash = std::hash()(person.name);std::size_t ageHash = std::hash()(person.age);return nameHash ^ (ageHash << 1);
} );
这里就不能采用decltype
来声明这个函数对象类型了。
unordered_map的底层实现是哈希表,其具体实现细节如下:
哈希函数:unordered_map使用哈希函数将关键字映射到桶中,从而实现快速的查询和插入操作。C++标准库中提供了多个哈希函数,包括std::hash、std::hash_combine和std::hash_range等。用户也可以自定义哈希函数,只需要满足一定的要求,比如对于相同的关键字,哈希函数返回的值必须相同。
冲突处理:由于哈希函数的不完美性,可能会出现不同的关键字映射到同一个桶中的情况,这被称为哈希冲突。
unordered_map解决冲突的方法主要有以下几种:
链地址法(Chaining):当发生哈希冲突时,将冲突的元素放入同一个桶中,以链表的形式串接起来。这种方法的优点是简单易实现,缺点是链表可能会很长,导致查询时间复杂度变高。
开放地址法(Open Addressing):当发生哈希冲突时,依次探查哈希表中的其他位置,直到找到一个空的位置为止。这种方法的优点是查询效率高,缺点是需要保证哈希表中有足够的空闲位置。
建立更好的哈希函数:合适的哈希函数可以减少哈希冲突的发生,从而提高查询效率。可以考虑选择更复杂的哈希函数,如 SHA-1 等。
桶的大小:unordered_map会自动调整桶的大小,以保证哈希表的负载因子不超过某个预设值。负载因子是指哈希表中元素个数与桶的个数之比。当负载因子过高时,哈希冲突的概率会增大,影响性能。
迭代器:unordered_map的迭代器是前向迭代器,可以用于遍历所有元素。需要注意的是,由于哈希表的无序性,迭代器的顺序并不一定与元素的插入顺序相同。
std::unordered_map
是一个哈希表实现的关联容器,它具有以下优点:
但是,std::unordered_map
也存在以下一些缺点:
总体来说,std::unordered_map是一个高效的关联容器,适用于大规模数据的处理,但是需要合理设置桶的数量和负载因子,并注意处理哈希冲突和迭代器失效的问题。
std::unordered_map是一个哈希表实现的关联容器,它的内部实现包含一个桶(bucket)数组,每个桶中存放一个链表或红黑树。
当往unordered_map中添加元素时,它会首先将元素的键(key)通过哈希函数映射到某个桶上。如果该桶为空,则直接在该桶中插入新的键值对;如果该桶已经有元素,则遍历该桶中的链表或红黑树,找到合适的位置插入新的键值对。
为了保证unordered_map的性能,需要在创建unordered_map对象时指定桶的数量,桶的数量会影响哈希冲突的概率和查找元素的速度。如果桶的数量太少,会导致哈希冲突概率增加,导致链表或红黑树的长度增加,查找元素的时间复杂度变高;如果桶的数量太多,会浪费内存。
unordered_map的桶的增加策略一般是:
当元素的数量达到桶的负载因子(load factor)时,会重新分配桶的数量(桶的数量会按照原有桶的数量乘以2的方式进行扩容,即以2倍增长。但是,具体的增长策略也可以通过修改std::unordered_map的max_load_factor成员变量来进行调整。),并将所有元素重新哈希到新的桶中。
具体来说,当元素数量达到桶数量与负载因子的乘积时,会触发桶的重新分配。默认情况下,std::unordered_map的负载因子是0.75,即当元素数量达到桶数量的0.75倍时会触发桶的重新分配。重新分配桶的过程比较耗时,因此应该尽量避免频繁的桶的重新分配。