1)unordered系列关联式容器
在C++11中,STL提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同(unordered_map和unordered_set,unordered_multimap和unordered_multiset)。
其中,unordered_map介绍如下:
- unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
- 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
- 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
- unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
- unordered_map实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
- 它的迭代器至少是前向迭代器。
2)哈希的概念
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( log2(n) ),顺序结构以及平衡树的搜索的效率取决于搜索过程中元素的比较次数。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。当向该结构中:
- 插入元素:根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。
- 搜索元素:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称
为哈希表(Hash Table)(或者称散列表)。也就是说,哈希是一种算法思想,哈希表是应用这种思想构建出来的一种数据结构。
3)哈希冲突
不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
常见的哈希函数:1. 直接定址法(常用);2. 除留余数法(常用);3. 平方取中法;4. 折叠法;5. 随机数法;6. 数学分析法。
解决哈希冲突的两种常见方法是:闭散列和开散列。
- 闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。(开放定址法:1. 线性探测;2. 二次探测)
- 开散列:开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。(哈希桶)
4)哈希表的实现
4.1 开放定址法
namespace open_address // 闭散列 开放定址法
{
enum Status
{
EMPTY,
EXIST,
DELETE
};
template<class K, class V>
struct HashData
{
std::pair<K, V> _kv;
Status _s; // 状态
};
template <class K, class V>
class HashTable
{
public:
HashTable()
{
_tables.resize(10);
}
bool Insert(const std::pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
// 负载因子,即空间占用率,达到0.7就扩容
if (_n*10 / _tables.size() == 7)
{
size_t newSize = _tables.size() * 2;
HashTable<K, V> newHT;
newHT._tables.resize(newSize);
//遍历旧表
for (int i = 0; i < _tables.size(); i++)
{
if (_tables[i]._s == EXIST)
{
newHT.Insert(_tables[i]._kv);
}
}
_tables.swap(newHT._tables);
}
// "开放定址法" :a.线性探测 b.二次探测
size_t hashi = kv.first % _tables.size(); // 下标
while (_tables[hashi]._s == EXIST)
{
hashi++;
hashi %= _tables.size();
}
_tables[hashi]._kv = kv;
_tables[hashi]._s = EXIST;
++_n;
return true;
}
HashData<K, V>* Find(const K& key)
{
size_t hashi = key % _tables.size();
while (_tables[hashi]._s != EMPTY)
{
if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key)
{
return &_tables[hashi];
}
hashi++;
hashi %= _tables.size();
}
return nullptr;
}
bool Erase(const K& key) // 伪删除
{
HashData<K, V>* ret = Find(key);
if (ret)
{
ret->_s = DELETE;
--_n;
return true;
}
else
{
return false;
}
}
void Print()
{
for (size_t i = 0; i < _tables.size(); i++)
{
if (_tables[i]._s == EXIST)
{
printf("[%d]->%d\n", i, _tables[i]._kv.first);
}
else if (_tables[i]._s == EMPTY)
{
printf("[%d]->Empty\n", i);
}
else
{
printf("[%d]->Delete\n", i);
}
}
}
private:
std::vector<HashData<K, V>> _tables;
size_t _n = 0; // 存储的关键字的个数
};
}
4.2 哈希桶
namespace hash_bucket // 开散列 哈希桶
{
template<class K, class V>
struct HashNode
{
HashNode* next;
std::pair<K, V> _kv;
HashNode(const std::pair<K, V> kv)
:_kv(kv)
,next(nullptr)
{}
};
template<class K, class V>
class HashTable
{
typedef HashNode<K, V> Node;
public:
HashTable()
{
_tables.resize(10);
}
~HashTable()
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->next;
delete cur;
cur = next;
}
_tables[i] = nullptr;
}
}
bool Insert(const std::pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
//负载因子最大到1
if (_n == _tables.size())
{
std::vector<Node*> newTables;
newTables.resize(_tables.size() * 2, nullptr);
// 遍历旧表
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->next; // 先存一下next
// 挪动到映射的新表
size_t hashi = cur->_kv.first % newTables.size();
cur->next = newTables[hashi]; // 头插
newTables[hashi] = cur;
cur = next; // 继续往下
}
_tables[i] = nullptr;
}
_tables.swap(newTables);
}
size_t hashi = kv.first % _tables.size();
Node* newnode = new Node(kv);
// 头插
newnode->next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return true;
}
Node* Find(const K& key)
{
size_t hashi = key % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->next;
}
return nullptr;
}
bool Erase(const K& key)
{
size_t hashi = key % _tables.size();
Node* cur = _tables[hashi];
Node* prev = nullptr;
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == nullptr)
{
_tables[hashi] = cur->next;
}
else
{
prev->next = cur->next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->next;
}
return false;
}
private:
std::vector<HashNode<K, V>*> _tables;
size_t _n = 0;
};
}