C++數(shù)據(jù)結(jié)構(gòu)之哈希算法詳解
1.哈希映射
1.1哈希的概念
在順序結(jié)構(gòu)以及平衡樹(shù)中,元素關(guān)鍵碼與其存儲(chǔ)位置之間沒(méi)有對(duì)應(yīng)的關(guān)系,因此在查找一個(gè)元素時(shí),必須要經(jīng)過(guò)關(guān)鍵碼的多次比較。順序查找時(shí)間復(fù)雜度為O(N),平衡樹(shù)中為樹(shù)的高度,即O(logN),搜索的效率決于搜索過(guò)程中元素的比較次數(shù)。
理想的搜索方法:可以不經(jīng)過(guò)任何比較,一次直接從表中得到要搜索的元素。 如果構(gòu)造一種存儲(chǔ)結(jié)構(gòu),通過(guò)某種函數(shù)(hashFunc)使元素的存儲(chǔ)位置與它的關(guān)鍵碼之間能夠建立一一映射的關(guān)系,那么在查找時(shí)通過(guò)該函數(shù)可以很快找到該元素。
當(dāng)向該結(jié)構(gòu)中:
- 插入元素:根據(jù)待插入元素的關(guān)鍵碼,以此函數(shù)計(jì)算出該元素的存儲(chǔ)位置并按此位置進(jìn)行存放
- 搜索元素:對(duì)元素的關(guān)鍵碼進(jìn)行同樣的計(jì)算,把求得的函數(shù)值當(dāng)做元素的存儲(chǔ)位置,在結(jié)構(gòu)中按此位置取元素比較,若關(guān)鍵碼相等,則搜索成功
該方式即為哈希(散列)方法,哈希方法中使用的轉(zhuǎn)換函數(shù)稱(chēng)為哈希(散列)函數(shù),構(gòu)造出來(lái)的結(jié)構(gòu)稱(chēng)為哈希表(Hash Table)(或者稱(chēng)散列表)。
unordered_map和unordered_set的底層就是哈希來(lái)實(shí)現(xiàn)的,它是無(wú)序的。
Hash(key)=key%capacity。

聰明的小伙伴已經(jīng)找到矛盾了,如果說(shuō)再添加一個(gè)數(shù)據(jù)5,那他存那?
1.2哈希沖突
對(duì)于兩個(gè)數(shù)據(jù)元素的關(guān)鍵字Ki和Kj(i != j),有 Ki!=Kj,但有:Hash(Ki) == Hash(Kj),即:不同關(guān)鍵字通過(guò)相同哈希函數(shù)計(jì)算出相同的哈希地址,該種現(xiàn)象稱(chēng)為哈希沖突或哈希碰撞。把具有不同關(guān)鍵碼而具有相同哈希地址的數(shù)據(jù)元素稱(chēng)為“同義詞”
所以這種方式建立起來(lái)的映射就十分的不合理,所以我們要改進(jìn)。
哈希設(shè)計(jì)的原則:
- 哈希函數(shù)的定義域必須包括需要存儲(chǔ)的全部關(guān)鍵碼,而如果散列表允許有m個(gè)地址時(shí),其值域必須在0到m-1之間。
- 哈希函數(shù)計(jì)算出來(lái)的地址能均勻分布在整個(gè)空間中。
- 哈希函數(shù)應(yīng)比較簡(jiǎn)單。
1.3哈希函數(shù)
1.31直接定值法
取關(guān)鍵字的某個(gè)線(xiàn)性函數(shù)為散列地址:Hash(key)= A*key + B
優(yōu)點(diǎn):簡(jiǎn)單,速度快,節(jié)省空間,查找key O(1)的時(shí)間復(fù)雜度
缺點(diǎn):當(dāng)數(shù)據(jù)范圍大時(shí)會(huì)浪費(fèi)空間,不能處理浮點(diǎn)數(shù),字符串?dāng)?shù)據(jù)
使用場(chǎng)景:適用于整數(shù),數(shù)據(jù)范圍比較集中
例如計(jì)數(shù)排序,統(tǒng)計(jì)字符串中出現(xiàn)的用26個(gè)英文字符統(tǒng)計(jì),給數(shù)組分配26個(gè)空間,遍歷到的字符是誰(shuí),就把相應(yīng)的元素值++

1.32除留余數(shù)法
把數(shù)據(jù)映射到有限的空間里面。設(shè)散列表中允許的地址數(shù)為m,取一個(gè)不大于m,但最接近或者等于m的質(zhì)數(shù)p作為除數(shù),按照哈希函數(shù):Hash(key) = key% p(p<=m),將key轉(zhuǎn)換成哈希地址。
哈希函數(shù)設(shè)計(jì)的越精妙,產(chǎn)生哈希沖突的可能性就越低,但是無(wú)法避免哈希沖突。
看1.1節(jié)的例子
解決哈希沖突最常用的方法是閉散列和開(kāi)散列。
2.解決哈希沖突
2.1閉散列法
閉散列:也叫開(kāi)放定址法,當(dāng)發(fā)生哈希沖突時(shí),如果哈希表未被裝滿(mǎn),說(shuō)明在哈希表中必然還有空位置,那么可以把key存放到?jīng)_突位置中的“下一個(gè)” 空位置中去。
但是咋去找下一個(gè)空位置才是最關(guān)鍵的?
2.11線(xiàn)性探測(cè)
線(xiàn)性探測(cè):從發(fā)生沖突的位置開(kāi)始,依次向后探測(cè),直到尋找到下一個(gè)空位置為止。

插入:通過(guò)哈希函數(shù)獲取待插入元素在哈希表中的位置。如果該位置中沒(méi)有元素則直接插入新元素,如果該位置中有元素發(fā)生哈希沖突,使用線(xiàn)性探測(cè)找到下一個(gè)空位置,插入新元素。
刪除:采用閉散列處理哈希沖突時(shí),不能隨便物理刪除哈希表中已有的元素,否則會(huì)影響其他元素的搜索。比如刪除元素1,如果直接刪除掉,11查找起來(lái)可能會(huì)受影響。因此線(xiàn)性探測(cè)采用標(biāo)記的偽刪除法來(lái)刪除一個(gè)元素。
給每個(gè)位置一個(gè)標(biāo)記,用空、存在、刪除3種狀態(tài)來(lái)區(qū)分。
- 負(fù)載因子 = 存儲(chǔ)的有效數(shù)據(jù)個(gè)數(shù)/空間的大小 。
- 負(fù)載因子越大,沖突的概率越高,增刪查改效率越低。
- 負(fù)載因子越小,沖突的概率越低,增刪查改的效率越高,但是空間利用率低,浪費(fèi)多。
- 負(fù)載因子 <1,就能保證發(fā)生哈希沖突時(shí)一定能找到空位置。
線(xiàn)性探測(cè)的優(yōu)缺點(diǎn):
- 線(xiàn)性探測(cè)優(yōu)點(diǎn):實(shí)現(xiàn)非常簡(jiǎn)單。
- 線(xiàn)性探測(cè)缺點(diǎn):一旦發(fā)生哈希沖突,所有的沖突連在一起,容易產(chǎn)生數(shù)據(jù)“堆積”,即:不同關(guān)鍵碼占據(jù)了可利用的空位置,使得尋找某關(guān)鍵碼的位置需要許多次比較,導(dǎo)致搜索效率降低。
2.12二次探測(cè)
二次探測(cè)改進(jìn)了一些線(xiàn)性探測(cè),但是也就那樣,這里我就不給太多畫(huà)面了。
所謂的二次探測(cè)我們可以理解為飛躍式,線(xiàn)性是一個(gè)一個(gè)找空位置,二次就是跳著找。
方法:hash(key) + i^2
hash(11)=11%10+0*2=1,但是1的位置被占了,所以變成hash(11)+1*2,如果這個(gè)位置是空的就放進(jìn)去,不是的話(huà),i繼續(xù)加。
3代碼實(shí)現(xiàn)
3.1狀態(tài)
狀態(tài):這里需要三種狀態(tài):空,已占用,已刪除
如果只用有/沒(méi)有來(lái)代表狀態(tài),那刪除一個(gè)數(shù)據(jù)后,這個(gè)位置就是空的,那就不會(huì)再遍歷了,但是它后面還有數(shù)據(jù)的話(huà)就存在問(wèn)題了,所以我們用已刪除這個(gè)狀態(tài)來(lái)表示的話(huà),還可以遍歷后面的數(shù)據(jù)。
#pragma once
#include<vector>
#include<iostream>
using namespace std;
namespace CloseHash
{
enum State
{
EMPTY, //0 空
EXIST, //1 存在
DELETE, // 2 已刪除
};
}3.2創(chuàng)建哈希節(jié)點(diǎn)類(lèi)
template<class K,class V>
struct HashData
{
pair<K, V> _kv;//數(shù)據(jù)
State _state = State::EMPTY;//狀態(tài) --空
};
template<class K, class V>//添加仿函數(shù)便于把其他類(lèi)型的數(shù)據(jù)轉(zhuǎn)換為整型數(shù)據(jù)
class HashTable
{
public:
//相關(guān)功能的實(shí)現(xiàn)……
private:
vector<HashData<K, V>> _table;//哈希表
size_t _n = 0;//存儲(chǔ)哈希表中有效數(shù)據(jù)的個(gè)數(shù)
};
查找:當(dāng)數(shù)據(jù)是1時(shí),直接映射到下標(biāo)1處,此時(shí)該位置的狀態(tài)是EXIST,數(shù)據(jù)是11時(shí),映射到下標(biāo)1處,但是已經(jīng)有1了所以++往后找空位置找到后狀態(tài)更新為EXIST。
刪除:刪除1,找11,當(dāng)刪除1后,狀態(tài)更新為DELETE,查找11時(shí)下標(biāo)發(fā)現(xiàn)狀態(tài)是DELETE時(shí)會(huì)繼續(xù)往后移動(dòng),然后找到11.
當(dāng)插入的數(shù)據(jù)較多,而哈希表較短時(shí),就要考慮到擴(kuò)容,但是哈希的擴(kuò)容不簡(jiǎn)單,因?yàn)橐粩U(kuò)容,下標(biāo)就變了,那很多數(shù)據(jù)的映射后的位置就變了。

現(xiàn)在的11在下標(biāo)11處,就不在2處了。
所以我們?cè)谏厦嫣岬搅?strong>負(fù)載因子: 填入表中的元素個(gè)數(shù) / 散列表的長(zhǎng)度
由于散列表的長(zhǎng)度一定,所以負(fù)載因子和表中的元素個(gè)數(shù)成正比。元素個(gè)數(shù)越多,哈希沖突越大,所以我們一般將負(fù)載因子定在0.7~0.8,在代碼中我們就定在0.7。
插入時(shí)我們?cè)俳榻B。
3.3數(shù)據(jù)插入
插入的詳細(xì)步驟:
去除重復(fù):
- 插入的值可能 已經(jīng)存在,所以用用Find先進(jìn)行查找
- 找到就返回false,沒(méi)找到在插入。
空間擴(kuò)容:
- 如果表是空的就給10個(gè)空間,否則擴(kuò)大2倍。
- 建立一個(gè)新哈希表,把舊表的值插入到新表
探測(cè)找位
- 如果位置的狀態(tài)是EXIST,繼續(xù)往后移
- 找到空位置后,插入,狀態(tài)變?yōu)榇嬖?,_n++。
//查找
bool Insert(const pair<K, V>& kv)
{
size_t hashi = kv.first % _tables.size(); //1.遇到空就停止了
//線(xiàn)性探測(cè)
while (_tables[hashi]._state == EXIST)
{
hashi++;
hashi %= _tables.size(); //2. 可能出表
}
_tables[hashi]._kv = kv;
_tables[hashi]._state = EXIST;
++_n;
return true;
}在1.0版本中我注意兩個(gè)細(xì)節(jié):
hashi為啥要模size而不是capacity?
計(jì)算機(jī)開(kāi)辟了20個(gè)空間,只存儲(chǔ)10個(gè)數(shù)據(jù),但是只能讓計(jì)算機(jī)在前十個(gè)空間存,要不一旦用空的空間,遍歷時(shí)遇到后就不在往后進(jìn)行了。所以開(kāi)的空間一般和數(shù)據(jù)個(gè)數(shù)一致。
while循環(huán)中為啥要加一步hashi%=_table.size()?
比如我們開(kāi)了20個(gè)空間,下標(biāo)16以后都存滿(mǎn)了但是前面還有位置,但是我們存一個(gè)37,那他就一直找位置,直到找到19,然后就出這個(gè)哈希表了,所以要讓他返回到表上再到下標(biāo)靠前的位置去找。
接下來(lái)就要開(kāi)辟空間了。但是這個(gè)可不簡(jiǎn)單。
當(dāng)插入的數(shù)據(jù)較多,而哈希表較短時(shí),就要考慮到擴(kuò)容,但是哈希的擴(kuò)容不簡(jiǎn)單,因?yàn)橐粩U(kuò)容,下標(biāo)就變了,那很多數(shù)據(jù)的映射后的位置就變了。

現(xiàn)在的11在下標(biāo)11處,就不在2處了。
所以我們?cè)谏厦嫣岬搅?strong>負(fù)載因子: 填入表中的元素個(gè)數(shù) / 散列表的長(zhǎng)度
由于散列表的長(zhǎng)度一定,所以負(fù)載因子和表中的元素個(gè)數(shù)成正比。元素個(gè)數(shù)越多,哈希沖突越大,所以我們一般將負(fù)載因子定在0.7~0.8,在代碼中我們就定在0.7。
在這里我們就要重新建一個(gè)哈希表。
//查找
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first)) //插入的值已經(jīng)存在
{
return false;
}
//擴(kuò)容
if (_tables.size() == 0 || 10 * _n / _tables.size() >= 7) //負(fù)載因子
{
size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2; //擴(kuò)容
HashTable<K, V> newHT;
newHT._tables.resize(newSize); //擴(kuò)容后用一個(gè)新表
//遍歷舊表,把舊表每個(gè)存在的元素插入newHT
for (auto& e : _tables)
{
if (e._state == EXIST)
{
newHT.Insert(e._kv);
}
}
newHT._tables.swap(_tables);//建立映射關(guān)系后交換
}
size_t hashi = kv.first % _tables.size(); //1.遇到空就停止了
//線(xiàn)性探測(cè)
while (_tables[hashi]._state == EXIST)
{
hashi++;
hashi %= _tables.size(); //2. 可能出表
}
_tables[hashi]._kv = kv;
_tables[hashi]._state = EXIST;
++_n;
return true;
}如果哈希表中已經(jīng)有插入的值時(shí),我們就要去除冗雜,用find函數(shù)。
3.4查找與刪除
查找的大致思路和插入的很接近,這里就不在重復(fù)了。
//查找
HashData<K, V>* Find(const K& key)
{
if (_tables.size() == 0)
{
return nullptr;
}
size_t hashi = key % _tables.size();
while (_tables[hashi]._state != EMPTY)
{
if (_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->_state = DELETE;
--_n;
}
else
{
return false;
}
}刪除時(shí),我們直接把要?jiǎng)h除的數(shù)據(jù)狀態(tài)改成DELETE就行了,甚至內(nèi)部的數(shù)據(jù)都不用刪除。
3.5仿函數(shù)
這里的取模用的都是整數(shù),那如果數(shù)據(jù)是浮點(diǎn)型?更甚至是字符串怎么搞?所依我們就要用到仿函數(shù)來(lái)進(jìn)行類(lèi)型轉(zhuǎn)換。
//利用仿函數(shù)將數(shù)據(jù)類(lèi)型轉(zhuǎn)換為整型
template<class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
//模板的特化
template<>
struct HashFunc<string> //如果是字符串,直接調(diào)用特化模板
{
size_t operator()(const string& key)
{
size_t hash = 0;
for (auto ch : key)
{
hash = hash * 131 + ch;//把所有字符的ascii碼值累計(jì)加起來(lái)
}
return hash;
}
};完整cpp表
#pragma once
#include<vector>
#include<iostream>
using namespace std;
enum State
{
EMPTY, //0 空
EXIST, //1 存在
DELETE, // 2 已刪除
};
template<class K, class V>
struct HashData
{
pair<K, V> _kv;//數(shù)據(jù)
State _state = State::EMPTY;//狀態(tài) --空
};
//利用仿函數(shù)將數(shù)據(jù)類(lèi)型轉(zhuǎn)換為整型
template<class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
//模板的特化
template<>
struct HashFunc<string>
{
size_t operator()(const string& key) //字符串直接使用
{
size_t hash = 0;
for (auto ch : key)
{
hash = hash * 131 + ch;//把所有字符的ascii碼值累計(jì)加起來(lái)
}
return hash;
}
};
template<class K, class V,class Hash = HashFunc<K>>
class HashTable
{
public:
//插入
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first)) //插入的值已經(jīng)存在
{
return false;
}
//擴(kuò)容
if (_tables.size() == 0 || 10 * _n / _tables.size() >= 7) //負(fù)載因子
{
size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2; //擴(kuò)容
HashTable<K, V> newHT;
newHT._tables.resize(newSize); //擴(kuò)容后用一個(gè)新表
//遍歷舊表,把舊表每個(gè)存在的元素插入newHT
for (auto& e : _tables)
{
if (e._state == EXIST)
{
newHT.Insert(e._kv);
}
}
newHT._tables.swap(_tables);//建立映射關(guān)系后交換
}
Hash hf;
size_t start = hf(kv.first);//取出鍵值對(duì)的key,并且避免了負(fù)數(shù)的情況,借用仿函數(shù)確保是整型數(shù)據(jù)
start %= _tables.size();
size_t hashi = start;
size_t i = 1;
//1.遇到空就停止了
//線(xiàn)性探測(cè)
while (_tables[hashi]._state == EXIST)
{
hashi++;
hashi %= _tables.size(); //2. 可能出表
}
_tables[hashi]._kv = kv;
_tables[hashi]._state = EXIST;
++_n;
return true;
}
//查找
HashData<K, V>* Find(const K& key)
{
if (_tables.size() == 0)
{
return nullptr;
}
Hash hf;
size_t start = hf(key);//通過(guò)仿函數(shù)把其它類(lèi)型數(shù)據(jù)轉(zhuǎn)為整型數(shù)據(jù)
start %= _tables.size();
size_t hashi = start;
size_t i = 1;
while (_tables[hashi]._state != EMPTY)
{
if (_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->_state = DELETE;
--_n;
}
else
{
return false;
}
}
private:
vector<HashData<K, V>> _tables;//哈希表
size_t _n = 0;//存儲(chǔ)哈希表中有效數(shù)據(jù)的個(gè)數(shù)
};
void testHash1()
{
int a[] = { 1,11,4,15,26,7 };
HashTable<int, int> ht;
for (auto e : a)
{
ht.Insert(make_pair(e, e));
}
}4.開(kāi)散列哈希桶
4.1概念
開(kāi)散列也叫拉鏈法:先對(duì)所有key用散列函數(shù)計(jì)算散列地址,把有相同地址的key每個(gè)key都作為一個(gè)桶,通過(guò)單鏈表鏈接在哈希表中。
此時(shí)的表里面存儲(chǔ)一個(gè)鏈表指針,就是把沖突的數(shù)據(jù)通過(guò)鏈表的形式掛起來(lái)。
它的算法公式:hash(key)=key%capacity

這里的插入可以是頭插也可以是尾插,插入時(shí)是無(wú)序的。
也就是說(shuō)哈希桶的根本是一個(gè)指針數(shù)組,哈希桶的每一個(gè)位置存的都是一個(gè)鏈表指針。
這個(gè)指針數(shù)組里的每一個(gè)元素都是結(jié)點(diǎn)指針,并且頭插的效率比較高。
4.2仿函數(shù)
這次我們先弄模板來(lái)將其他類(lèi)型轉(zhuǎn)換為size_t。
namespace OpenHash
{
template<class K>
struct Hash
{
size_t operator()(const K& key)
{
return key;
}
};
// 特化
template<>
struct Hash < string >
{
size_t operator()(const string& s)
{
// BKDR Hash
size_t value = 0;
for (auto ch : s)
{
value += ch;
value *= 131;
}
return value;
}
};
}4.3哈希桶結(jié)點(diǎn)構(gòu)建
因?yàn)槭侵羔様?shù)組,所以結(jié)點(diǎn)中的成員變量多了一個(gè)指向下一個(gè)桶的指針。
//結(jié)點(diǎn)類(lèi)
template<class K, class V>
struct HashNode
{
pair<K, V> _kv;
HashNode<K, V>* _next;
//構(gòu)造函數(shù)
HashNode(const pair<K, V>& kv)
:_kv(kv)
, _next(nullptr)
{}
};
//哈希表的類(lèi)
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
//相關(guān)功能的實(shí)現(xiàn)……
private:
//指針數(shù)組
vector<Node*> _tables;
size_t _n = 0;//記錄有效數(shù)據(jù)的個(gè)數(shù)
};4.4哈希桶的查找和刪除
這里的查找\刪除操作和上面的如出一轍,但是哈希桶的存儲(chǔ)是鏈表的形式,所以會(huì)和鏈表的相關(guān)操作很接近。
//查找
Node* Find(const K& key)
{
//防止后續(xù)除0錯(cuò)誤
if (_tables.size() == 0)
{
return nullptr;
}
//構(gòu)建仿函數(shù)
HashFunc hf;
//找到對(duì)應(yīng)的映射下標(biāo)位置
size_t hashi = hf(key);
hashi %= _tables.size();
Node* cur = _tables[hashi];
//在此位置的鏈表中進(jìn)行遍歷查找
while (cur)
{
if (cur->_kv.first == key)
{
//找到了
return cur;
}
cur = cur->_next;
}
//遍歷結(jié)束,沒(méi)有找到,返回nullptr
return nullptr;
}
//刪除
bool Erase(const K& key)
{
if (_tables.size() == 0)
{
return nullptr;
}
size_t hashi = key % _tables.size();
Node* cur = _tables[hashi];
Node* prev = nullptr;
while (cur)
{
if (cur->_kv.first == key)
{
//1.頭刪
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else //中間位置刪除
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}4.5哈希桶的插入
去除重復(fù):
- 插入的值可能 已經(jīng)存在,所以用用Find先進(jìn)行查找
- 找到就返回false,沒(méi)找到再進(jìn)行插入。
空間擴(kuò)容
- 如果負(fù)載因子==1就進(jìn)行擴(kuò)容。
- 建立一個(gè)新哈希表,把舊表的值插入到新表。
- 再把新表交換到舊表那里。
但是在把舊表映射到新表時(shí)要釋放掉舊表,vector類(lèi)型會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù),然而存儲(chǔ)的數(shù)據(jù)是Node*類(lèi)型的,是內(nèi)置類(lèi)型,不會(huì)自動(dòng)釋放,結(jié)果就是哈希表釋放了但是表中存的數(shù)據(jù)沒(méi)釋放,所以我們要手寫(xiě)一個(gè)析構(gòu)函數(shù)。
頭插操作
- 根仿函數(shù)找到合適映射位置
- 進(jìn)行頭插操作并更新桶內(nèi)數(shù)據(jù)個(gè)數(shù)。
所以我們首先寫(xiě)一個(gè)析構(gòu)函數(shù):
//析構(gòu)函數(shù)
~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 pair<K, V>& kv)
{
//1、去除重復(fù)
if (Find(kv.first))
{
return false;
}
//2、負(fù)載因子 == 1就擴(kuò)容
if (_tables.size() == _n)
{
size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
for (size_t i = 0; i < _tables.size(); i++)//遍歷舊表
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
size_t hashi = hf(cur->_kv.first) % newSize;//確認(rèn)映射到新表的位置
//頭插
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
_tables[i] = nullptr;
}
newTable.swap(_tables);
}
//3、頭插
//構(gòu)建仿函數(shù),把數(shù)據(jù)類(lèi)型轉(zhuǎn)為整型,便于后續(xù)建立映射關(guān)系
HashFunc hf;
size_t hashi = hf(kv.first);
hashi %= _tables.size();
//頭插到對(duì)應(yīng)的桶即可
Node* newnode = new Node(kv);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return true;
}到此這篇關(guān)于C++數(shù)據(jù)結(jié)構(gòu)之哈希算法詳解的文章就介紹到這了,更多相關(guān)C++哈希算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文詳解C++中動(dòng)態(tài)內(nèi)存管理
這篇文章主要介紹了一文詳解C++中動(dòng)態(tài)內(nèi)存管理,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)孩子沒(méi)需要的朋友可以才可以參考一下2022-07-07
Visual Studio Code 2020安裝教程及CPP環(huán)境配置(教程圖解)
這篇文章主要介紹了Visual Studio Code 2020安裝教程、CPP環(huán)境配置,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03
C++?opencv利用grabCut算法實(shí)現(xiàn)摳圖示例
這篇文章主要為大家介紹了C++?opencv利用grabCut算法實(shí)現(xiàn)摳圖的代碼示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
C/C++的堆棧內(nèi)存分配的實(shí)現(xiàn)
內(nèi)存管理是至關(guān)重要的一個(gè)方面,堆和棧是C語(yǔ)言中重要的內(nèi)存分配方式,本文主要介紹了C/C++的堆棧內(nèi)存分配的實(shí)現(xiàn),詳細(xì)的介紹了這兩者在管理方式、性能和使用場(chǎng)景,感興趣的可以了解一下2024-07-07
c/c++那些你一定會(huì)出錯(cuò)的數(shù)組筆試題匯總
這篇文章主要給大家匯總介紹了關(guān)于c/c++那些你一定會(huì)出錯(cuò)的數(shù)組筆試題,除了基本數(shù)據(jù)類(lèi)型之外,其余的都作為類(lèi)對(duì)象,包括數(shù)組,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10
C語(yǔ)言開(kāi)發(fā)簡(jiǎn)易版掃雷小游戲
本文給大家分享的是一個(gè)使用C語(yǔ)言開(kāi)發(fā)的命令行下的簡(jiǎn)易版掃雷小游戲,本身沒(méi)有什么太多的技術(shù)含量,只不過(guò)是筆者的處女作,所以還是推薦給大家,希望對(duì)大家學(xué)習(xí)C能夠有所幫助。2015-12-12

