哈希表(Hash Table),也称为散列表,是一种常用的数据结构,用于实现键值对的存储和查找。它通过将键映射到一个索引位置来快速地访问和操作数据。
哈希表的基本思想是使用一个哈希函数将键映射到一个固定范围的整数,然后将这个整数作为索引来访问数组中的元素。这个数组通常被称为哈希表或哈希桶。当多个键映射到相同的索引位置时,称为哈希冲突,常用的解决冲突的方法有链地址法和开放地址法。
哈希表的优点包括:
然而,哈希表也存在一些缺点:
在C++中,可以使用std::unordered_map和std::unordered_set来实现哈希表。它们提供了高效的查找、插入和删除操作,并且可以存储任意类型的键值对或唯一元素。
哈希表的存储方式通常是通过数组来实现的,这个数组通常被称为哈希表或哈希桶。每个桶存储一个链表或者其他数据结构,用于解决哈希冲突。
具体来说,哈希表的存储方式可以分为以下几种:
无论使用哪种存储方式,哈希表的基本原理是通过哈希函数将键映射到索引位置,然后根据存储方式来处理哈希冲突。这样可以实现快速的查找、插入和删除操作,并且具有较低的冲突率和高效的空间利用率。
开放地址法(Open Addressing)是一种解决哈希冲突的方法,在发生冲突时,通过一定的规则找到下一个可用的空桶来存储冲突的键值对。开放地址法的核心思想是,当发生冲突时,不使用链表等数据结构来存储冲突的元素,而是将其存储在其他的空桶中。具体的规则可以包括线性探测、二次探测、双重哈希等。例如,线性探测是指当发生冲突时,顺序地检查下一个桶,直到找到一个空桶来存储冲突的元素。
负载因子(Load Factor)是指哈希表中已经存储的键值对数量与哈希表容量的比值。负载因子可以用来衡量哈希表的装载程度。通常情况下,负载因子越大,表示哈希表中存储的键值对越多,装载程度越高。负载因子的计算公式为:
负载因子 = 已存储的键值对数量 / 哈希表容量
负载因子的选择对哈希表的性能有影响。当负载因子较小时,哈希表中的空桶较多,查找、插入和删除操作的效率较高。但随着负载因子的增加,哈希冲突的概率也会增加,可能导致查找、插入和删除操作的效率下降。因此,合适的负载因子选择是重要的,一般来说,负载因子的取值范围为0.7到0.8之间,可以在空间利用率和性能之间做出平衡。当负载因子超过某个阈值时,可以考虑进行哈希表的扩容,以保持较低的负载因子。
闭散列(Closed Hashing)和开散列(Open Hashing)是两种不同的解决哈希冲突的方法。
闭散列,也称为封闭寻址法(Closed Addressing),是指当发生哈希冲突时,将冲突的键值对存储在哈希表的同一个桶中,通常使用链表或其他数据结构来存储冲突的元素。当需要查找、插入或删除一个键值对时,首先计算出它的哈希值,然后在对应的桶中查找或操作。闭散列的优点是可以存储任意数量的键值对,但当链表过长时,可能会导致查找效率下降。
开散列,也称为开放寻址法(Open Addressing),是指当发生哈希冲突时,通过一定的规则找到下一个可用的空桶来存储冲突的键值对。开散列的核心思想是,当发生冲突时,不使用链表等数据结构来存储冲突的元素,而是将其存储在其他的空桶中。具体的规则可以包括线性探测、二次探测、双重哈希等。开散列的优点是节省了链表的空间开销,但当哈希表装载因子较高时,可能会导致查找效率下降。
解决哈希冲突的常用方法包括:
链地址法(Chaining):当发生哈希冲突时,将冲突的键值对存储在同一个桶中,通过链表或其他数据结构连接冲突的元素。
开放地址法(Open Addressing):当发生哈希冲突时,通过一定的规则找到下一个可用的空桶来存储冲突的键值对。常见的开放地址法包括线性探测、二次探测、双重哈希等。
再哈希法(Rehashing):当发生哈希冲突时,使用另一个哈希函数计算出一个新的哈希值,然后将冲突的键值对存储在新的位置上。
建立公共溢出区(Overflow Area):当发生哈希冲突时,将冲突的键值对存储在一个公共的溢出区中,通过其他方式来记录溢出区的元素。
负载因子调整和扩容:通过调整负载因子的大小,或者在负载因子超过一定阈值时进行哈希表的扩容,以减少哈希冲突的概率。
这些方法各有优缺点,适用于不同的场景和需求。选择合适的解决方法需要考虑哈希表的装载因子、数据分布情况、性能要求等因素。
#pragma once
#include
namespace OpenAddress
{
enum State
{
EMPTY,
EXIST,
DELETE
};
template<class K, class V>
struct HashData
{
pair<K, V> _kv;
State _state = EMPTY;
};
template<class K, class V>
class HashTable
{
public:
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
return false;
// 负载因子超过0.7就扩容
//if ((double)_n / (double)_tables.size() >= 0.7)
if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
{
//size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
//vector newtables(newsize);
遍历旧表,重新映射到新表
//for (auto& data : _tables)
//{
// if (data._state == EXIST)
// {
// // 重新算在新表的位置
// size_t i = 1;
// size_t index = hashi;
// while (newtables[index]._state == EXIST)
// {
// index = hashi + i;
// index %= newtables.size();
// ++i;
// }
// newtables[index]._kv = data._kv;
// newtables[index]._state = EXIST;
// }
//}
//_tables.swap(newtables);
// 10:34继续
size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
HashTable<K, V> newht;
newht._tables.resize(newsize);
// 遍历旧表,重新映射到新表
for (auto& data : _tables)
{
if (data._state == EXIST)
{
newht.Insert(data._kv);
}
}
_tables.swap(newht._tables);
}
size_t hashi = kv.first % _tables.size();
// 线性探测
size_t i = 1;
size_t index = hashi;
while (_tables[index]._state == EXIST)
{
index = hashi + i;
index %= _tables.size();
++i;
}
_tables[index]._kv = kv;
_tables[index]._state = EXIST;
_n++;
return true;
}
HashData<K, V>* Find(const K& key)
{
if (_tables.size() == 0)
{
return false;
}
size_t hashi = key % _tables.size();
// 线性探测
size_t i = 1;
size_t index = hashi;
while (_tables[index]._state != EMPTY)
{
if (_tables[index]._state == EXIST
&& _tables[index]._kv.first == key)
{
return &_tables[index];
}
index = hashi + i;
index %= _tables.size();
++i;
// 如果已经查找一圈,那么说明全是存在+删除
if (index == hashi)
{
break;
}
}
return nullptr;
}
bool Erase(const K& key)
{
HashData<K, V>* ret = Find(key);
if (ret)
{
ret->_state = DELETE;
--_n;
return true;
}
else
{
return false;
}
}
private:
vector<HashData<K, V>> _tables;
size_t _n = 0; // 存储的数据个数
//HashData* tables;
//size_t _size;
//size_t _capacity;
};
void TestHashTable1()
{
int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
HashTable<int, int> ht;
for (auto e : a)
{
ht.Insert(make_pair(e, e));
}
ht.Insert(make_pair(15, 15));
if (ht.Find(13))
{
cout << "13在" << endl;
}
else
{
cout << "13不在" << endl;
}
ht.Erase(13);
if (ht.Find(13))
{
cout << "13在" << endl;
}
else
{
cout << "13不在" << endl;
}
}
}
namespace HashBucket
{
template<class K, class V>
struct HashNode
{
HashNode<K, V>* _next;
pair<K, V> _kv;
HashNode(const pair<K, V>& kv)
:_next(nullptr)
, _kv(kv)
{}
};
template<class K, class V>
class HashTable
{
typedef HashNode<K, V> Node;
public:
~HashTable()
{
for (auto& cur : _tables)
{
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
cur = nullptr;
}
}
Node* Find(const K& key)
{
if (_tables.size() == 0)
return nullptr;
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* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
// 负载因因子==1时扩容
if (_n == _tables.size())
{
/*size_t newsize = _tables.size() == 0 ? 10 : _tables.size()*2;
HashTable newht;
newht.resize(newsize);
for (auto cur : _tables)
{
while (cur)
{
newht.Insert(cur->_kv);
cur = cur->_next;
}
}
_tables.swap(newht._tables);*/
size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
vector<Node*> newtables(newsize, nullptr);
//for (Node*& cur : _tables)
for (auto& cur : _tables)
{
while (cur)
{
Node* next = cur->_next;
size_t hashi = cur->_kv.first % newtables.size();
// 头插到新表
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next;
}
}
_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;
}
private:
vector<Node*> _tables; // 指针数组
size_t _n = 0; // 存储有效数据个数
};
void TestHashTable1()
{
int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
HashTable<int, int> ht;
for (auto e : a)
{
ht.Insert(make_pair(e, e));
}
ht.Insert(make_pair(15, 15));
ht.Insert(make_pair(25, 25));
ht.Insert(make_pair(35, 35));
ht.Insert(make_pair(45, 45));
}
void TestHashTable2()
{
int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
HashTable<int, int> ht;
for (auto e : a)
{
ht.Insert(make_pair(e, e));
}
ht.Erase(12);
ht.Erase(3);
ht.Erase(33);
}
}
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
void test_unordered_set1()
{
unordered_set<int> s;
s.insert(1);
s.insert(3);
s.insert(2);
s.insert(7);
s.insert(2);
unordered_set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
}
void test_unordered_map()
{
string arr[] = { "", "", "ƻ", "", "ƻ", "ƻ", "", "ƻ", "㽶", "ƻ", "㽶", "" };
map<string, int> countMap;
for (auto& e : arr)
{
countMap[e]++;
}
for (auto& kv : countMap)
{
cout << kv.first << ":" << kv.second << endl;
}
}
//
//int main()
//{
// test_unordered_set1();
// test_unordered_map();
//
// return 0;
//}
//int main()
//{
// const size_t N = 1000000;
//
// unordered_set us;
// set s;
//
// vector v;
// v.reserve(N);
// srand(time(0));
// for (size_t i = 0; i < N; ++i)
// {
// v.push_back(rand());
// //v.push_back(rand()+i);
// //v.push_back(i);
// }
//
// size_t begin1 = clock();
// for (auto e : v)
// {
// s.insert(e);
// }
// size_t end1 = clock();
// cout << "set insert:" << end1 - begin1 << endl;
//
// size_t begin2 = clock();
// for (auto e : v)
// {
// us.insert(e);
// }
// size_t end2 = clock();
// cout << "unordered_set insert:" << end2 - begin2 << endl;
//
//
// size_t begin3 = clock();
// for (auto e : v)
// {
// s.find(e);
// }
// size_t end3 = clock();
// cout << "set find:" << end3 - begin3 << endl;
//
// size_t begin4 = clock();
// for (auto e : v)
// {
// us.find(e);
// }
// size_t end4 = clock();
// cout << "unordered_set find:" << end4 - begin4 << endl << endl;
//
// cout << s.size() << endl;
// cout << us.size() << endl << endl;;
//
// size_t begin5 = clock();
// for (auto e : v)
// {
// s.erase(e);
// }
// size_t end5 = clock();
// cout << "set erase:" << end5 - begin5 << endl;
//
// size_t begin6 = clock();
// for (auto e : v)
// {
// us.erase(e);
// }
// size_t end6 = clock();
// cout << "unordered_set erase:" << end6 - begin6 << endl << endl;
//
// return 0;
//}
#include "HashTable.h"
int main()
{
HashBucket::TestHashTable2();
return 0;
}