前言
大家好吖,歡迎來到 YY 滴 數(shù)據(jù)結(jié)構(gòu) 系列 ,熱烈歡迎! 本章主要內(nèi)容面向接觸過C++的老鐵
主要內(nèi)容含:
歡迎訂閱 YY滴C++專欄!更多干貨持續(xù)更新!以下是傳送門!
- YY的《C++》專欄
- YY的《C++11》專欄
- YY的《Linux》專欄
- YY的《數(shù)據(jù)結(jié)構(gòu)》專欄
- YY的《C語言基礎(chǔ)》專欄
- YY的《初學(xué)者易錯點》專欄
- YY的《小小知識點》專欄
一.哈希(散列)的基本概念
1.哈希(散列)的基本概念
理想的搜索方法:不經(jīng)過任何比較, 一次 直接從表中得到要搜索的元素。
如果構(gòu)造一種存儲結(jié)構(gòu), 通過某種函數(shù)(hashFunc)使元素的存儲位置與它的關(guān)鍵碼之間能夠建立一一映射的關(guān)系 ,那么在查找時通過該函數(shù)可以很快找到該元素
該方式即為 哈希(散列)方法 ,哈希方法中使用的轉(zhuǎn)換函數(shù)稱為哈希(散列)函數(shù),構(gòu)造出來的結(jié)構(gòu)稱 為哈希表(Hash Table)(或者稱散列表)
2.哈希表的簡單基本例子
例如:數(shù)據(jù)集合{1,7,6,4,5,9}; 哈希函數(shù)設(shè)置為:
hash(key) = key % capacity;
- capacity為存儲元素底層空間總的大小
![]()
- 用該方法進行搜索不必進行多次關(guān)鍵碼的比較,因此搜索的速度比較快
- 但是 當插入44時 ,就會出現(xiàn)問題: 不同值映射到相同位置 ,這就是第二部分要講的" 哈希沖突問題 "
二.哈希沖突(哈希碰撞)
- 一句話理解哈希沖突: 不同值映射到相同位置
- 官方解釋:對于兩個數(shù)據(jù)元素的關(guān)鍵字 k i k_i ki?和 k j k_j kj?(i != j),有 k i k_i ki? != k j k_j kj?,但有:Hash( k i k_i ki?) == Hash( k j k_j kj?),即:不同關(guān)鍵字通過相同哈希哈數(shù)計算出相同的哈希地址,該種現(xiàn)象稱為哈希沖突或哈希碰撞。
- 把具有不同關(guān)鍵碼而具有相同哈希地址的數(shù)據(jù)元素稱為“同義詞”。
- 引起哈希沖突的一個原因可能是: 哈希函數(shù)設(shè)計不夠合理 。這就是第二部分要講的"哈希函數(shù)"
三.哈希函數(shù)
- 我們先要確定一點:哈希函數(shù)設(shè)計的越精妙,產(chǎn)生哈希沖突的可能性就越低,但是 無法避免哈希沖突
1.哈希函數(shù)設(shè)計原則
- 哈希函數(shù)的定義域必須包括需要存儲的全部關(guān)鍵碼,而如果散列表允許有m個地址時,其值域必須在0到m-1之間
- 哈希函數(shù)計算出來的地址能均勻分布在整個空間中
- 哈希函數(shù)應(yīng)該比較簡單
2.常用的兩種哈希函數(shù)
【1】 直接定址法–(常用)
取關(guān)鍵字的某個線性函數(shù)為散列地址:
Hash(Key)= A*Key + B
- 優(yōu)點:簡單、均勻
- 缺點:需要事先知道關(guān)鍵字的分布情況
- 使用場景:適合查找比較小且連續(xù)的情況
【2】除留余數(shù)法–(常用)
設(shè)散列表中允許的地址數(shù)為m,取一個不大于m,但最接近或者等于m的質(zhì)數(shù)p作為除數(shù),
按照哈希函數(shù)Hash(key) = key% p(p<=m)
, 將關(guān)鍵碼轉(zhuǎn)換成哈希地址
【※】哈希表中的荷載因子
四.解決哈希沖突法一:閉散列-“開放地址法”
- 一句話理解閉散列: 當前位置被占用了,按規(guī)則找到下一個位置(占用別人應(yīng)該在的位置)
- 閉散列:也叫 開放定址法 ,當發(fā)生哈希沖突時,如果哈希表未被裝滿,說明在哈希表中必然還有
空位置,那么可以把key存放到?jīng)_突位置中的 “下一個” 空位置中去 。- 那如何尋找下一個空位置呢?—— 線性探測+二次探測
1. 線性探測&二次探測
- 一句話理解 線性探測: 從發(fā)生沖突的位置開始,依次向后探測,直到尋找到下一個空位置為止 ,示例如下所示:
![]()
- 線性探測缺點: 一旦發(fā)生 哈希沖突 ,所有的沖突連在一起,容易產(chǎn)生 數(shù)據(jù)“堆積”
- 即:不同關(guān)鍵碼占據(jù)了可利用的空位置,使得尋找某關(guān)鍵碼的位置需要許多次比較,導(dǎo)致搜索效率降
低。如何緩解呢?- 二次探測 是為了避免該問題而生;找下一個空位置的方法為: H i H_i Hi? = ( H 0 H_0 H0? + i 2 i^2 i2 )% m, 或者: H i H_i Hi? = ( H 0 H_0 H0? - i 2 i^2 i2 )% m。其中: i = 1,2,3…, H 0 H_0 H0?是通過散列函數(shù)Hash(x)對元素的關(guān)鍵碼 key 進行計算得到的位置,m是表的大小。示例如下所示:
![]()
2.閉散列哈希中的基本狀態(tài)
每一個元素格子可以分成三種狀態(tài):
- EXIST(存在)
- EMPTY(空)
- DELETE(已被刪除)
enum STATE
{
EXIST,
EMPTY,
DELETE
};
3.閉散列哈希的基本結(jié)構(gòu)
template<class K, class V>
struct HashData
{
pair<K, V> _kv;
STATE _state = EMPTY;
};
vector<HashData<K, V>> _table;
size_t _n = 0; // 存儲有效數(shù)據(jù)的個數(shù)————可用于識別荷載因子
4.線性探測中處理"查找"
代碼如下所示:
HashData<const K, V>* Find(const K& key)
{
// 線性探測
HashFunc hf;
size_t hashi = hf(key) % _table.size();
while (_table[hashi]._state != EMPTY)
{
if (_table[hashi]._state == EXIST
&& _table[hashi]._kv.first == key)
{
return (HashData<const K, V>*) & _table[hashi];
}
++hashi;
hashi %= _table.size();
}
5.線性探測中處理"插入"
【1】注意閉散列擴容問題
插入過程基本程序設(shè)計思路:
- 當荷載因子達到某個臨界值
_n * 10 / _table.size() >= 7
,進入擴容- 擴容過程: 1.設(shè)置新表大小 2.創(chuàng)建新表 3.遍歷舊表的數(shù)據(jù)插入到新表即可 4.交換新表舊表首元素地址
- 正常插入過程遵循線性探測:
1.通過哈希函數(shù)找到相應(yīng)映射的下表(hashi)
2.但遇到當前hashi已經(jīng)被占據(jù)時_table[hashi]._state == EXIST
,
進行 二次探測hashi %= _table.size();
重新尋找新hashi
3.找到狀態(tài)為EMPTY的hashi時,存入數(shù)據(jù),調(diào)整狀態(tài)_table[hashi]._kv = kv; _table[hashi]._state = EXIST; ++_n;
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
// 擴容
//if ((double)_n / (double)_table.size() >= 0.7)
if (_n * 10 / _table.size() >= 7) //荷載因子
{
size_t newSize = _table.size() * 2;
// 遍歷舊表,重新映射到新表
HashTable<K, V, HashFunc> newHT;
newHT._table.resize(newSize);
// 遍歷舊表的數(shù)據(jù)插入到新表即可
for (size_t i = 0; i < _table.size(); i++)
{
if (_table[i]._state == EXIST)
{
newHT.Insert(_table[i]._kv);
}
}
_table.swap(newHT._table);
}
// 線性探測
HashFunc hf;
size_t hashi = hf(kv.first) % _table.size();
while (_table[hashi]._state == EXIST)
{
++hashi;
hashi %= _table.size();
}
_table[hashi]._kv = kv;
_table[hashi]._state = EXIST;
++_n;
return true;
}
6.線性探測中處理"刪除"
刪除過程基本程序設(shè)計思路:
- 利用查找函數(shù)find接收返回值
- 將該hashi下表對應(yīng)的狀態(tài)設(shè)置成DELETE即可
// 按需編譯
bool Erase(const K& key)
{
HashData<const K, V>* ret = Find(key);
if (ret)
{
ret->_state = DELETE;
--_n;
return true;
}
return false;
}
7. 閉散列適應(yīng)多種類型轉(zhuǎn)換————“仿函數(shù)”&“類模板特化”&“仿函數(shù)在類模板中充當默認模板實參的應(yīng)用”
【1】仿函數(shù)
- 一句話解釋 仿函數(shù) :用一個類重載
()
,讓其實現(xiàn)函數(shù)的功能
- 仿函數(shù)在類模板中的應(yīng)用傳送門:傳送門
- 使用仿函數(shù)的基本操作:重載()——operator()
template<class K>
struct DefaultHashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
【2】類模板特化
- 使用仿函數(shù)的進階操作:讓閉散列適應(yīng)多種類型轉(zhuǎn)換
- 場景舉例:正常情況下,我們輸入int double,他都會通過仿函數(shù)重載的()轉(zhuǎn)換成對應(yīng)的ASCLL碼值,但是當傳入的是字符串則會出現(xiàn)問題,因此我們需要把類模板 特化一下————
struct DefaultHashFunc<string>
- 特化在類模板中的應(yīng)用傳送門:傳送門
template<>
struct DefaultHashFunc<string>
{
size_t operator()(const string& str)
{
// BKDR
size_t hash = 0;
for (auto ch : str)
{
hash *= 131;
hash += ch;
}
return hash;
}
};
【3】仿函數(shù)在類模板中充當默認實參的應(yīng)用
- 仿函數(shù)在類模板中充當默認模板實參 傳送門:傳送門
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
//調(diào)用時
HashFunc hf;
size_t hashi = hf(kv.first) % _table.size();
...};
8.閉散列哈希完整代碼展示
template<class K>
struct DefaultHashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template<>
struct DefaultHashFunc<string>
{
size_t operator()(const string& str)
{
// BKDR
size_t hash = 0;
for (auto ch : str)
{
hash *= 131;
hash += ch;
}
return hash;
}
};
namespace open_address
{
enum STATE
{
EXIST,
EMPTY,
DELETE
};
template<class K, class V>
struct HashData
{
pair<K, V> _kv;
STATE _state = EMPTY;
};
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
public:
HashTable()
{
_table.resize(10);
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
// 擴容
//if ((double)_n / (double)_table.size() >= 0.7)
if (_n * 10 / _table.size() >= 7)
{
size_t newSize = _table.size() * 2;
// 遍歷舊表,重新映射到新表
HashTable<K, V, HashFunc> newHT;
newHT._table.resize(newSize);
// 遍歷舊表的數(shù)據(jù)插入到新表即可
for (size_t i = 0; i < _table.size(); i++)
{
if (_table[i]._state == EXIST)
{
newHT.Insert(_table[i]._kv);
}
}
_table.swap(newHT._table);
}
// 線性探測
HashFunc hf;
size_t hashi = hf(kv.first) % _table.size();
while (_table[hashi]._state == EXIST)
{
++hashi;
hashi %= _table.size();
}
_table[hashi]._kv = kv;
_table[hashi]._state = EXIST;
++_n;
return true;
}
HashData<const K, V>* Find(const K& key)
{
// 線性探測
HashFunc hf;
size_t hashi = hf(key) % _table.size();
while (_table[hashi]._state != EMPTY)
{
if (_table[hashi]._state == EXIST
&& _table[hashi]._kv.first == key)
{
return (HashData<const K, V>*) & _table[hashi];
}
++hashi;
hashi %= _table.size();
}
return nullptr;
}
// 按需編譯
bool Erase(const K& key)
{
HashData<const K, V>* ret = Find(key);
if (ret)
{
ret->_state = DELETE;
--_n;
return true;
}
return false;
}
private:
vector<HashData<K, V>> _table;
size_t _n = 0; // 存儲有效數(shù)據(jù)的個數(shù)
};
}
9.閉散列缺點
- 發(fā)生哈希沖突后,幾個位置映射的值會 相互影響
五.解決哈希沖突法二:開散列-“鏈地址法(開鏈法)”-“哈希桶”
1. 開散列概念
- 開散列法又叫 鏈地址法(開鏈法) ,首先對關(guān)鍵碼集合用散列函數(shù)計算散列地址,具有相同地址的關(guān)鍵碼歸于同一子集合, 每一個子集合稱為一個桶 ,各個桶中的元素通過一個 單鏈表 鏈接起來,各鏈表的頭結(jié)點存儲在哈希表中。
- 開散列中每個桶中放的都是發(fā)生 哈希沖突 的元素。
2.開散列哈?;拘问?/h3>
//哈希桶
namespace hash_bucket
{
HashTable...//哈希表
HashNode...//節(jié)點
vector<Node*> _table; // 指針數(shù)組
size_t _n = 0; // 存儲了多少個有效數(shù)據(jù)
}
//哈希表
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
....};
//節(jié)點
template<class K, class V>
struct HashNode
{
pair<K, V> _kv;
HashNode<K, V>* _next;
HashNode(const pair<K, V>& kv)
:_kv(kv)
,_next(nullptr)
{}
};
3.開散列插入
【1】哈希桶基本插入問題
- 采取 頭插方式
文章來源:http://www.zghlxwxcb.cn/news/detail-756509.html
// 頭插
Node* newnode = new Node(kv);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
【2】注意閉散列擴容問題
引入:文章來源地址http://www.zghlxwxcb.cn/news/detail-756509.html
- 如果不擴容,不斷插入某些桶越來越長效率得不到保障,負載因子適當放大一些,一般負載因子控制在1,平均下來就是每一個桶一個數(shù)據(jù)
// 負載因子到1就擴容
if (_n == _table.size())
{
size_t newSize = _table.size()*2;
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
if(...)//剩余操作
}
【3】注意閉散列擴容后的操作
- 注意:這里我們采用 遍歷舊表,順手牽羊,把節(jié)點牽下來掛到新表的方式
- 原因:重新開空間開節(jié)點,消耗大,效率低
// 遍歷舊表,順手牽羊,把節(jié)點牽下來掛到新表
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];//舊表
while (cur)
{
Node* next = cur->_next;//保存下一個地址
//找到新的對應(yīng)位置
size_t hashi = hf(cur->_kv.first) % newSize; //hf為仿函數(shù),在開散列相關(guān)章可以查看
//頭插到新表
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
//把舊表置空
_table[i] = nullptr;
}
//最后交換新舊表首元素地址
_table.swap(newTable);
【4】閉散列完整插入操作
bool Insert(const pair<K, V>& kv)
{
if(Find(kv.first))
{
return false;
}
HashFunc hf;
// 負載因子到1就擴容
if (_n == _table.size())
{
size_t newSize = _table.size()*2;
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
// 遍歷舊表,順手牽羊,把節(jié)點牽下來掛到新表
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
// 頭插到新表
size_t hashi = hf(cur->_kv.first) % newSize;
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newTable);
}
size_t hashi = hf(kv.first) % _table.size();
// 頭插
Node* newnode = new Node(kv);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
++_n;
return true;
}
4. 開散列查找操作
Node* Find(const K& key)
{
HashFunc hf;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->_next;
}
return nullptr;
}
5. 開散列哈希完整代碼展示
namespace hash_bucket
{
template<class K, class V>
struct HashNode
{
pair<K, V> _kv;
HashNode<K, V>* _next;
HashNode(const pair<K, V>& kv)
:_kv(kv)
,_next(nullptr)
{}
};
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
HashTable()
{
_table.resize(10, nullptr);
}
~HashTable()
{
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_table[i] = nullptr;
}
}
bool Insert(const pair<K, V>& kv)
{
if(Find(kv.first))
{
return false;
}
HashFunc hf;
// 負載因子到1就擴容
if (_n == _table.size())
{
size_t newSize = _table.size()*2;
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
// 遍歷舊表,順手牽羊,把節(jié)點牽下來掛到新表
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
// 頭插到新表
size_t hashi = hf(cur->_kv.first) % newSize;
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newTable);
}
size_t hashi = hf(kv.first) % _table.size();
// 頭插
Node* newnode = new Node(kv);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
++_n;
return true;
}
Node* Find(const K& key)
{
HashFunc hf;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
HashFunc hf;
size_t hashi = hf(key) % _table.size();
Node* prev = nullptr;
Node* cur = _table[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == nullptr)
{
_table[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
void Print()
{
for (size_t i = 0; i < _table.size(); i++)
{
printf("[%d]->", i);
Node* cur = _table[i];
while (cur)
{
cout << cur->_kv.first <<":"<< cur->_kv.second<< "->";
cur = cur->_next;
}
printf("NULL\n");
}
cout << endl;
}
private:
vector<Node*> _table; // 指針數(shù)組
size_t _n = 0; // 存儲了多少個有效數(shù)據(jù)
};
}
六.哈希表的使用
#include"HashTable.h"
int main()
{
HashTable<int, int> ht;
int a[] = { 1,111,4,7,15,25,44,9 };
for (auto e : a)
{
ht.Insert(make_pair(e, e));
}
ht.Erase(15);
auto ret = ht.Find(4);
//ret->_kv.first = 41;
ret->_kv.second = 400;
//HashTable<string, string, StringHashFunc> dict;
HashTable<string, string> dict;
dict.Insert(make_pair("sort", "排序"));
dict.Insert(make_pair("left", "xxx"));
auto dret = dict.Find("left");
//dret->_kv.first = "xx";//不可修改,const
dret->_kv.second = "左邊";
string s1("xxx");
string s2("xxx");
DefaultHashFunc<string> hf;
cout << hf(s1) << endl;
cout << hf(s2) << endl;
cout << hf("bacd") << endl;
cout << hf("abcd") << endl;
cout << hf("abbe") << endl;
cout << hf("https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=STATE&fenlei=256&rsv_pq=0xdd48647300054f47&rsv_t=1cd5rO%2BE6TJzo6qf9QKcibznhQ9J3lFwGEzmkc0Goazr3HuQSIIc2zD78Pt0&rqlang=en&rsv_enter=1&rsv_dl=tb&rsv_sug3=2&rsv_n=2&rsv_sug1=1&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&prefixsug=STAT%2526gt%253B&rsp=5&inputT=656&rsv_sug4=796") << endl;
return 0;
}
//哈希桶
namespace hash_bucket
{
HashTable...//哈希表
HashNode...//節(jié)點
vector<Node*> _table; // 指針數(shù)組
size_t _n = 0; // 存儲了多少個有效數(shù)據(jù)
}
//哈希表
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
....};
//節(jié)點
template<class K, class V>
struct HashNode
{
pair<K, V> _kv;
HashNode<K, V>* _next;
HashNode(const pair<K, V>& kv)
:_kv(kv)
,_next(nullptr)
{}
};
- 采取 頭插方式
文章來源:http://www.zghlxwxcb.cn/news/detail-756509.html
// 頭插
Node* newnode = new Node(kv);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
引入:文章來源地址http://www.zghlxwxcb.cn/news/detail-756509.html
- 如果不擴容,不斷插入某些桶越來越長效率得不到保障,負載因子適當放大一些,一般負載因子控制在1,平均下來就是每一個桶一個數(shù)據(jù)
// 負載因子到1就擴容
if (_n == _table.size())
{
size_t newSize = _table.size()*2;
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
if(...)//剩余操作
}
- 注意:這里我們采用 遍歷舊表,順手牽羊,把節(jié)點牽下來掛到新表的方式
- 原因:重新開空間開節(jié)點,消耗大,效率低
// 遍歷舊表,順手牽羊,把節(jié)點牽下來掛到新表
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];//舊表
while (cur)
{
Node* next = cur->_next;//保存下一個地址
//找到新的對應(yīng)位置
size_t hashi = hf(cur->_kv.first) % newSize; //hf為仿函數(shù),在開散列相關(guān)章可以查看
//頭插到新表
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
//把舊表置空
_table[i] = nullptr;
}
//最后交換新舊表首元素地址
_table.swap(newTable);
bool Insert(const pair<K, V>& kv)
{
if(Find(kv.first))
{
return false;
}
HashFunc hf;
// 負載因子到1就擴容
if (_n == _table.size())
{
size_t newSize = _table.size()*2;
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
// 遍歷舊表,順手牽羊,把節(jié)點牽下來掛到新表
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
// 頭插到新表
size_t hashi = hf(cur->_kv.first) % newSize;
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newTable);
}
size_t hashi = hf(kv.first) % _table.size();
// 頭插
Node* newnode = new Node(kv);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
++_n;
return true;
}
Node* Find(const K& key)
{
HashFunc hf;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->_next;
}
return nullptr;
}
namespace hash_bucket
{
template<class K, class V>
struct HashNode
{
pair<K, V> _kv;
HashNode<K, V>* _next;
HashNode(const pair<K, V>& kv)
:_kv(kv)
,_next(nullptr)
{}
};
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
HashTable()
{
_table.resize(10, nullptr);
}
~HashTable()
{
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_table[i] = nullptr;
}
}
bool Insert(const pair<K, V>& kv)
{
if(Find(kv.first))
{
return false;
}
HashFunc hf;
// 負載因子到1就擴容
if (_n == _table.size())
{
size_t newSize = _table.size()*2;
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
// 遍歷舊表,順手牽羊,把節(jié)點牽下來掛到新表
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
// 頭插到新表
size_t hashi = hf(cur->_kv.first) % newSize;
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newTable);
}
size_t hashi = hf(kv.first) % _table.size();
// 頭插
Node* newnode = new Node(kv);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
++_n;
return true;
}
Node* Find(const K& key)
{
HashFunc hf;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
HashFunc hf;
size_t hashi = hf(key) % _table.size();
Node* prev = nullptr;
Node* cur = _table[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == nullptr)
{
_table[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
void Print()
{
for (size_t i = 0; i < _table.size(); i++)
{
printf("[%d]->", i);
Node* cur = _table[i];
while (cur)
{
cout << cur->_kv.first <<":"<< cur->_kv.second<< "->";
cur = cur->_next;
}
printf("NULL\n");
}
cout << endl;
}
private:
vector<Node*> _table; // 指針數(shù)組
size_t _n = 0; // 存儲了多少個有效數(shù)據(jù)
};
}
#include"HashTable.h"
int main()
{
HashTable<int, int> ht;
int a[] = { 1,111,4,7,15,25,44,9 };
for (auto e : a)
{
ht.Insert(make_pair(e, e));
}
ht.Erase(15);
auto ret = ht.Find(4);
//ret->_kv.first = 41;
ret->_kv.second = 400;
//HashTable<string, string, StringHashFunc> dict;
HashTable<string, string> dict;
dict.Insert(make_pair("sort", "排序"));
dict.Insert(make_pair("left", "xxx"));
auto dret = dict.Find("left");
//dret->_kv.first = "xx";//不可修改,const
dret->_kv.second = "左邊";
string s1("xxx");
string s2("xxx");
DefaultHashFunc<string> hf;
cout << hf(s1) << endl;
cout << hf(s2) << endl;
cout << hf("bacd") << endl;
cout << hf("abcd") << endl;
cout << hf("abbe") << endl;
cout << hf("https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=STATE&fenlei=256&rsv_pq=0xdd48647300054f47&rsv_t=1cd5rO%2BE6TJzo6qf9QKcibznhQ9J3lFwGEzmkc0Goazr3HuQSIIc2zD78Pt0&rqlang=en&rsv_enter=1&rsv_dl=tb&rsv_sug3=2&rsv_n=2&rsv_sug1=1&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&prefixsug=STAT%2526gt%253B&rsp=5&inputT=656&rsv_sug4=796") << endl;
return 0;
}
到了這里,關(guān)于【數(shù)據(jù)結(jié)構(gòu)】萬字一文手把手解讀哈希————(開/閉散列)解決哈希沖突完整詳解(6)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!