目录
一. unordered_map unordered_set 和 map set的区别
一. unordered_map unordered_set 和 map set的区别
1. map set底层采取的红黑树的结构,unordered_xxx 底层数据结构是哈希表。unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
2. Java中对应的容器名为 HashMap HashSet TreeMap TreeSet,命名方面比C++好了很多。主要是早期C++并没有实现哈希结构的容器(C++11之前),也就是unordered系列,在C++11中新增了unordered_map,unordered_set,unordered_multimap,unordered_multiset,故因为历史命名问题,取了这样的名字。
3. 它们的使用大体上几乎一致。显著的差别是:
a、map和set为双向迭代器,unordered_xxx和是单向迭代器。
b、map和set存储为有序存储(红黑树结构,中序遍历有序),unordered_xxx为无序存储(哈希表的结构致使)
4. 性能差异:采取哈希表的unordered系列容器在大量数据的增删查改效率更优,尤其是查(搜索)
二. 哈希
1. 哈希,哈希表,哈希函数。
哈希的思想是:将元素的关键码与元素在哈希表中的存储位置构建某种函数关系,使得查找时,可以通过这个函数,直接获取元素的存储位置。对比顺序表和搜索树,顺序表需要将关键码一一对比,时间复杂度为O(N),而搜索树这样的结构,时间复杂度也是(log_2 N),取决于元素个数,树的高度,同样需要若干次比较。而哈希可以使得查找元素的存储位置不需要比较,直接通过函数获取。
将这个转换函数成为哈希函数(散列函数),得到的元素的存储位置成为哈希地址(也就是在哈希表中的下标,哈希表一般为顺序表结构),将构造出的数据结构成为哈希表(散列表)。
2. 哈希冲突。
难免有某些元素的不同关键码通过同一个哈希函数转换后得到的哈希地址相同,这种现象称为哈希冲突或哈希碰撞。
故,要想构造出理想的哈希存储结构,必须解决哈希冲突,合理安排那些关键码不同,而哈希地址相同的元素。
3. 哈希函数补充
哈希函数有很多种,也就是将元素的关键码,转换为元素存储位置的函数。哈希函数的设计越精妙,产生哈希冲突的概率越低,但是无法完全避免哈希冲突。
下方哈希表的实现代码中,采取的哈希函数为除留余数法,即将关键码取模哈希表的长度,获取哈希地址。
3. 解决哈希冲突的两大方法:闭散列,开散列
闭散列
闭散列:也叫开放定址法。核心思想比较简单:若某两个元素发生哈希冲突,第二个得到该哈希地址的元素插入时,将该元素存放到哈希表中发生哈希冲突的“下一个”空位置中去。其实也就是再找一个空位置,然后插入。
如何寻找下一个“空位置”的方法有两种,1. 线性探测,即从哈希地址处起,逐个位置进行判断,直到找到空位置。2. 二次探测:第一次看hashAdd+0^2下标处有没有被占用(设哈希地址为hashAdd。其实也就是看获取的哈希地址处有没有没被占用),第二次看hashAdd+1^2,第三次看hashAdd+2^2,直到找到没有被占用的位置。
相比于线性探测,二次探测只是将发生哈希冲突的元素的存储位置分离一些,不像线性探测一样连续存储,能一定程度减少哈希冲突带来的性能损耗,但是不能根本解决问题。
闭散列实现代码:
// 开散列(链地址法)解决哈希表中的哈希冲突
namespace OpenHashing
{
// 这里仅仅是实现哈希表的最简单逻辑,Find等函数的实现没有考虑unordered_map等容器
template <class K, class V>
struct HashNode
{
HashNode<K,V>(const pair<K,V>& kv)
:_kv(kv)
{ }
pair<K, V> _kv;
HashNode* _next = nullptr;
};
template <class K>
struct HashAddressConvert
{
size_t operator()(const K& key) {
return key;
}
};
// 模板特化
template <>
struct HashAddressConvert<string>
{
size_t operator()(const string& str) {
size_t sum = 0;
for(auto&ch:str)
{
sum*=131;
sum+=ch;
}
return sum;
}
};
template <class K, class V, class HDC = HashAddressConvert<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
~HashTable() {
for(auto& ptr : _table) {
Node* cur = ptr;
while(cur) {
Node* next = cur->_next;
delete cur;
cur = next;
}
ptr = nullptr;
}
}
bool Insert(const pair<K, V>& kv) {
if(Find(kv.first)) {