目錄
什麼是哈希表?
在常見的線性表(順序表, 鏈表, 棧, 隊列), 二叉搜索樹, AVL樹, 紅黑樹等這些數據結構中, 想要查找某一個元素時, 免不了的要
進行遍歷比較. 在時間複雜度上, 線性結構爲O(n), 二叉搜索樹在O(log₂N)~O(n), AVL和紅黑樹打倒O(log₂N). 雖然AVL和紅黑
樹這兩個平衡樹的時間複雜度已經達到了O(log₂N), 但當數據量特別龐大時, 還是比較慢的. 而哈希表在查找方面就顯得非常
高效了.
哈希的思想和我們常用的數組有着異曲同工之處, 數組中知道要想訪問某個元素, 只需要知道這個元素的下標即可, 通過下標
就可以直接訪問這個元素. 因此可以這樣訪問的效率可達到O(1) .
在哈希結構中, 存儲的是鍵值對Key-Value, 通過建立Key與Value的映射關係, 我們可以直接通過Key值, 訪問到Value,
就如同數組中, 通過下標訪問到元素的值, 所以哈希表在不考慮哈希衝突的情況下, 查詢效率爲O(1). (後面說什麼是哈希衝
突). 再來看其哈希表的概念也就好理解了.
散列表 (Hash table, 也叫哈希表), 是根據關鍵碼值Key而直接進行訪問對應數據Value的一種數據結構也就是說, 它通過把關
鍵碼值Key映射到表中一個位置來訪問數據Value, 以加快查找的速度. 這個映射函數叫做散列函數, 存放數據的數組叫做散
列表 . 在C++ 11後的STL中的unordered系列的關聯式容器底層實現都是哈希表, 包括 unordered_map, unordered_set
unordered_multimap, unordered_multiset .
哈希函數的功能簡單說就是: 地址 = HashFun(Key), 通過這個地址, 就能訪問到Key所對應的Value或者知道這組Key-Value
需要存在哪個位置, 下面來詳細看哈希函數.
哈希函數
當向哈希表中:
插入元素時, 根據待插入元素的關鍵碼Key, 用哈希函數計算出(HashFunc(Key))該元素的哈希地址(存儲位置)並按此位置進
行存放.
查找元素時, 對元素的關鍵碼進行同樣的計算HashFunc(Key),把求得的函數值當做元素的哈希地址 在哈希表中按此位置取
元素比較, 若關鍵碼相等, 則查找成功
哈希結構中, 能根據Key計算出哈希地址的的函數就是哈希函數 .
舉個栗子:
對於數據集合 { (1, 10), (7, 20), (6, 30), (4, 40), (5, 50) }; 小括號元素是(Key, Value)
HashFunc(Key) = key % capacity ; 這裏的Key是整數, 無需轉換(言外之意是Key還能是其他數據類型), capacity 假設爲 10, 則有,
例如插入(1, 10)時, key % capacity = 1 % 10 = 1, 所以(1, 10)存儲在了哈希地址爲1的位置 .
那如果再插入(11, 56)呢, key % capacity = 11 % 10 = 1. 我們發現, 當插入(11, 56)時, 哈希地址爲1的位置已經有了(1, 10).
那麼,稱這種現象爲 哈希衝突(或哈希碰撞). 即, 具有不同關鍵碼的元素(也就是不同元素)通過哈希函數得到了同一個哈希地址.
哈希衝突是無法完全避免的, 也就是說是一定會存在的, 但是可以儘量減少的, 方法就是設計一個更加合理的哈希函數.
但就算哈希函數再合理也會有一定概率會發生哈希衝突, 那麼當哈希衝突發生了該怎麼辦呢?
解決辦法常見的有兩種: 閉散列( 開放定址法 )和開散列( 鏈地址法或開鏈法 ).
閉散列和開散列下面再說, 先來看看哈希函數的設計方法 .
首先, 哈希函數的設計必須遵循下面幾個準則:
1. 哈希函數的定義域必須包括需要存儲的全部關鍵碼, 散列表有m個地址時, 其值域必須在0到m-1之間.
2. 哈希函數計算出來的地址能均勻分佈在整個空間中(減少哈希衝突).
3. 哈希函數應該比較簡單. (如果需要大量複雜的計算, 反而拖慢了效率)
常見方法
1. 直接定製法--(常用)
取關鍵字的某個線性函數爲散列地址:HashFunc(Key)= A*Key + B
- 優點:簡單、均勻
- 缺點:需要事先知道關鍵字的分佈情況 使用
- 場景:適合查找比較小且連續的情況
2. 除留餘數法--(常用)
設散列表中允許的地址數爲m, 取一個不大於m, 但最接近或者等於m的質數p作爲除數, 按照哈希函數:
HashFunc(key) = key% p (p<=m), 將關鍵碼轉換成哈希地址.
值得注意的是, 當p是質數(素數)時, 除留餘數法發生哈希衝突的概率會大大減少 .
3. 平方取中法--(瞭解)
假設關鍵字爲1234,對它平方就是1522756,抽取中間的3位227作爲哈希地址 .
再比如關鍵字爲4321,對它平方就18671041,抽取中間的3位671(或710)作爲哈希地址.
平方取中法比較適合:不知道關鍵字的分佈,而位數又不是很大的情況
4. 摺疊法--(瞭解)
摺疊法是將關鍵字從左到右分割成位數相等的幾部分(最後一部分位數可以短些),然後將這幾部分疊加求和,並按散列表表長,
取後幾位作爲散列地址。
摺疊法適合事先不需要知道關鍵字的分佈,適合關鍵字位數比較多的情況
5. 隨機數法--(瞭解)
選擇一個隨機函數,取關鍵字的隨機函數值爲它的哈希地址,即H(key) = random(key),其中random爲隨機數函數.
通常應用於關鍵字長度不等時採用此法
6. 數學分析法--(瞭解)
有學生的生日數據如下:
年.月.日
75.10.03
75.11.23
76.03.02
76.07.12
75.04.21
76.02.15
...
經分析,第一位,第二位,第三位重複的可能性大,取這三位造成衝突的機會增加,所以儘量不取前三位,取後三位比較好 .
最後, 還是要強調一點, 哈希函數設計的越精妙,產生哈希衝突的可能性就越低,但是無法避免哈希衝突.
1. 閉散列
閉散列, 也叫開放定址法, 當發生哈希衝突時, 如果哈希表未被裝滿, 說明在哈希表中必然還有空位置, 那麼可以把要存的元素
存放到衝突位置中的 “下一個” 空位置中去. 那如何尋找"下一個"空位置呢?
方法有兩種, 線性探測和二次探測, 先來看線性探測 .
線性探測
顧名思義, 線性探測是線性的, 從發生衝突的位置開始往後尋找空位置, 存到找到的第一個空位置. 當找到哈希表的末尾
還沒找到時, 從開頭往後再找.
插入
接着上面的例子, 再插入(11, 56), key % capacity = 11 % 10 = 1. 從衝突位置往後找, 找到第一個空位置則填入 .
刪除
採用閉散列處理哈希衝突時,不能隨便物理刪除哈希表中已有的元素,若直接刪除元素會影響其他元素的搜索. 比如刪除元素
(1, 10), 如果直接刪除掉, (11, 5) 查找起來可能會受影響. 因此線性探測採用標記的僞刪除法來刪除一個元素. 即, 哈希表每個空間
給個標記, EMPTY此位置空, EXIST此位置已經有元素, DELETE元素已經刪除.
enum State {
EMPTY,
EXIST,
DELETE
};
例如現在已經刪掉了(1, 10), 又要刪除(11, 5), 如下:
哈希函數通過11計算出的哈希地址還是1, 當找到1位置時, 發現原來的元素已經刪了, 說明原來這個位置存在過元素, 那就有這種可
能, 當插入Key值爲11的元素時, 可能發生了哈希衝突, 所以可能找"下一個" 空位(EMPTY)再存儲, 所以還會向後尋找, 直到找到並
刪除或者碰到下一個空位置EMPTY, 則就是沒找到.
優點 : 實現非常簡單
缺點: 一旦發生哈希衝突, 所有的衝突連在一起, 容易產生數據 “堆積”, 即:不同關鍵碼佔據了可利用的空位置,使得尋找某關鍵碼
的位置需要許多次比較,導致搜索效率降低. 如何緩解呢?二次探測可以緩解這個問題.
二次探測
線性探測的缺點是數據容易"堆積", 這與其找 "下一個"空位置的方法有關, 線性探測是從衝突發生位置依次往後找. 所以容易堆積.
而二次探測改變了找空位的方法, 即 Hi = (H0 + i²)% m, 或者Hi = (H0 - i²)% m . 其中: i = 1,2,3…, H0是通過散列函數Hash(x)對元
素的關鍵碼 key 進行計算得到的位置,m是表的大小 .
那麼問題來了, 線性探測或是二次探測要是找不到下一個空位置呢, 或者說存滿了呢?
閉散列的擴容
其實是一定會找到的, 爲什麼呢? 原因如下:
散列表的載荷因子 α = 存儲的元素個數 / 散列表的長度
α 越大, 表明當前散列表中的元素越多, 產生哈希衝突的可能性也就越大, 所以說爲了儘量避免哈希衝突, α 的大小是需要控制
的. 對於閉散列來說, 載荷因子 α 必須控制在0.8 一下, 當超過0.8後, 哈希衝突的發生機率會大幅增加. 因此一些採用閉散列
的Hash庫,如java的系統庫, α 就爲 0.75, 當超過0.75時必須擴容.
所以說, 當插入一個新元素時, 至少還有20%的空間沒有利用, 所以一定會找到"下一個"空位置 .
對於二次探測來說, 當表的長度爲質數(素數)且表裝載因子 α 不超過0.5時, 每次哈希衝突, 二次探測的位置, 都不會重複, 比
起線性探測, 效率更高, 但空閒的空間比線性探測更多, 空間利用率更低.
那麼如何擴容呢?
當insert一個元素時, 函數內部首先要檢測器載荷因子是否小於限定, 如小於則不需要擴容, 如不小於, 則擴容, 另外申請一個
更大的空間, 將舊錶中的元素一個一個重新insert. 所以說閉散列擴容是非常耗時的 .
其實閉散列最大的缺陷就是空間利用率比較低,這也是哈希的缺陷.
說了這麼多, 其實在C++11之後的STL中的unordered系列的四個容器, 底層雖然是哈希, 但卻不是閉散列實現的, 底層實現是開散
列. 所以先實現一個簡單的閉散列.
兩個小問題
1. Key非整形
哈希函數只能接受整型的key值, 例如除留餘數法, 但其實Key-Value鍵值對Key和Value類型可以是任意的, 那麼當Key不是整型
時, 又該如何處理呢?
這時候就可以我們自己來定義一個方法, 將非整型的Key值轉換成唯一對應的無符號整型, 將這個方法給到我們的哈希結構, 那麼怎
麼讓哈希結構獲取到我們的自定義方法呢? 我們可以將自定義方法以仿函數的形式或函數指針的形式作爲模板參數傳給哈希結構.
例如:
template<class T> // 整形數據不需要轉化, 直接返回
class DefHashF {
public:
size_t operator()(const T& val) {
return val;
}
};
class StrInt {// key爲字符串類型,需要將其轉化爲整形
public:
size_t operator()(const string& s) {
const char* str = s.c_str();
unsigned int seed = 131; // 31 131 1313 13131 131313
unsigned int hash = 0;
while (*str) {
hash = hash * seed + (*str++);
}
return (hash & 0x7FFFFFFF);
}
};
//在哈希結構內部, 就可以直接使用模板參數HF將key轉換成無符號整型
template<class K, class V, class HF>
class HashTable {
// ……
private:
size_t HashFunc(const K& key) {
return HF()(key)%_ht.capacity();
}
};
當Key的類型是string時, 只需要在第三個模板參數傳入StrInt
2. 對於哈希函數常用的除留餘數法, 如何快速獲取下一個素數
前面說到, 對於除留餘數法, 模數時素數時, 哈希衝突的概率更小. 所以散列表的長度都是素數, 當需要擴容時, 擴容的大小就爲下一
個素數的大小, 如果每次都計算得出素數, 未免太麻煩, 那麼我們在寫代碼時可以直接將能用到的素數都給出來, 當發生擴容時直接
取用就行了, 如下 :
template<class K, class V, class HF>
class HashTable {
// ……
private:
static size_t s_m_primeTable[30];
// ……
};
size_t HashTable<K, V, SW>::s_m_primeTable[30] = {
11, 23, 47, 89, 179,
353, 709, 1409, 2819, 5639,
11273, 22531, 45061, 90121, 180233,
360457, 720899, 1441807, 2883593, 5767169,
11534351, 23068673, 46137359, 92274737, 184549429,
369098771, 738197549, 1476395029, 2952790016ul, 429496729ul
};
閉散列的簡單實現
Hash.h
#pragma once
#include<vector>
using namespace std;
enum State {
EMPTY,
EXIST,
DELETE
};
class dealInt {
public:
int operator()(const int key) {
return key;
}
};
template <class K, class V, class SW = dealInt>
class HashTable {
struct elem {//哈希表元素, pair包含鍵值與數據, m_state爲標誌位
pair<K, V> m_val;
State m_state;
elem(const K& key = K(), const V& val = V(), State state = EMPTY) :
m_val(key, val),
m_state(state)
{}
};
vector <elem> m_table;
size_t m_size;
static size_t s_m_primeTable[30];
int m_prime;
//並沒有記錄哈希表的capacity的值, 因爲m_table的size值就爲其容量, 詳見構造函數
size_t HashFun(const K& key) {//哈希函數,使用除留餘數法
return SW()(key) % capacity();//仿函數先求出能夠計算的整型(因爲key值不一定是整型)
}
void reserve() {
vector<elem> tmp;
m_table.swap(tmp);
m_table.resize(s_m_primeTable[++m_prime]);
m_size = 0;//交換後的m_table的爲空
for (auto& e : tmp) {
if (e.m_state == EXIST) {
insert(e.m_val);
}
}
}
public:
HashTable(size_t capacity = s_m_primeTable[0]) :
m_table(capacity),
m_size(0),
m_prime(0)
{}
size_t capacity() {
return m_table.size();
}
bool insert(const pair<K, V>& val) {
if (m_size * 10 / capacity() > 7) {//數據所佔總容量70%以上時, 需要擴容
//因爲此時哈希衝突發生的概率變得非常大, 所以需要擴容
reserve();
}
int k = HashFun(val.first);
while (m_table[k].m_state == EXIST) {//若發生衝突, 此處運用線性探測法解決
//即, 依次往後找, 知道找到空位置
if (m_table[k].m_val == val) {//若已有相同的, 即插入失敗
return false;
}
++k;
if (k == capacity()) {//當找到最後(最後已經判斷完), 需要重頭再來
k = 0;
}//此處不會出現重複循環判斷的, 因爲在70%時就會擴容, 所以一定會有空閒的位置
}
m_table[k].m_val = val;
m_table[k].m_state = EXIST;
++m_size;
return true;
}
int find(const K& key) {
int k = HashFun(key);
while (m_table[k].m_state != EMPTY) {//如果等於空直接跳出循環, 沒找到返回-1
if (m_table[k].m_state == EXIST && m_table[k].m_val.first == key) {
return k;
}
++k;
if (k == capacity()) {
k = 0;
}
}
return -1;
}
bool erase(const K& key) {
int k = find(key);//先找有沒有
if (k != -1) {//有
--m_size;
m_table[k].m_state = DELETE;
return true;
}
return false;//沒有則刪除失敗
}
size_t size() {
return m_size;
}
bool empty() {
return m_size == 0;
}
void Swap(const HashTable& ht) {
m_table.swap(ht.m_table);
size_t tmp = m_size;
m_size = ht.m_size;
ht.m_size = tmp;
}
void clear() {
m_size = 0;
m_table.clear();
m_table.resize(s_m_primeTable[0]);
m_prime = 0;
}
};
template <class K, class V, class SW>
size_t HashTable<K, V, SW>::s_m_primeTable[30] = {
11, 23, 47, 89, 179,
353, 709, 1409, 2819, 5639,
11273, 22531, 45061, 90121, 180233,
360457, 720899, 1441807, 2883593, 5767169,
11534351, 23068673, 46137359, 92274737, 184549429,
369098771, 738197549, 1476395029, 2952790016ul, 429496729ul
};
測試入口 main.cpp, 由於只是簡單實現, 我們通過調試來看是否完成了基本的功能.
#include<iostream>
#include"Hash.h"
using namespace std;
int main() {
HashTable<int, int> ht;
ht.insert(pair<int, int>(1, 5));
ht.insert(pair<int, int>(2, 7));
ht.insert(pair<int, int>(5, 6));
ht.insert(pair<int, int>(6, 8));
ht.insert(pair<int, int>(12, 9));
ht.insert(pair<int, int>(16, 4));
ht.insert(pair<int, int>(23, 11));
ht.insert(pair<int, int>(27, 13));
ht.insert(pair<int, int>(33, 13));
ht.insert(pair<int, int>(36, 13));
ht.insert(pair<int, int>(27, 13));
ht.insert(pair<int, int>(33, 13));
ht.insert(pair<int, int>(36, 13));
ht.erase(33);
int n = ht.find(27);
int m = ht.find(222);
ht.clear();
ht.insert(pair<int, int>(1, 5));
ht.insert(pair<int, int>(2, 7));
ht.insert(pair<int, int>(5, 6));
ht.insert(pair<int, int>(6, 8));
ht.insert(pair<int, int>(12, 9));
ht.insert(pair<int, int>(16, 4));
ht.insert(pair<int, int>(23, 11));
ht.insert(pair<int, int>(27, 13));
ht.insert(pair<int, int>(33, 13));
ht.insert(pair<int, int>(36, 13));
ht.insert(pair<int, int>(27, 13));
ht.insert(pair<int, int>(33, 13));
ht.insert(pair<int, int>(36, 13));
system("pause");
return 0;
}
2.開散列
開散列概念: 開散列法又叫鏈地址法(開鏈法), 首先對關鍵碼集合用哈希函數計算哈希地址, 具有相同地址的關鍵碼歸於同
一子集合, 每一個子集合稱爲一個桶, 各個桶中的元素通過一個單鏈錶鏈接起來, 各鏈表的頭存儲在哈希表中 .
如下圖結構 :
HashFunc(key) = key % capacity , capacity = 10;
圖1 圖2
插入數據集合 { (1, 10), (7, 20), (6, 30), (4, 40), (5, 50) }如上圖1. 再插入新元素(11, 5), 如上圖2
開散列的增容
桶的個數是一定的,隨着元素的不斷插入,每個桶中元素的個數不斷增多,極端情況下,可能會導致一個桶中鏈表節點非
常多,這會影響的哈希表的性能,因此在一定條件下需要對哈希表進行增容,那這個條件要怎麼確認呢?開散列最好的情
況是: 每個哈希桶中剛好掛一個節點,此時再繼續插入元素時,每一次都會發生哈希衝突,因此,在元素個數剛好等於桶的
個數時,可以給哈希表增容。
當擴容後, 舊的數據要重新一個一個的insert到新的哈希桶中, 比較耗時.
開散列與閉散列比較
開散列中增加了一個指針數組, 每個節點中又增加了指向下一個節點的指針, 似乎增加了存儲開銷. 事實上, 由於閉散列必須
保持大量的空閒空間以確保搜索效率,如閉散列要求載荷因子a <= 0.7~0.8, 而表項所佔空間又比指針大的多,所以使用鏈
地址法反而比開地址法節省存儲空間.
有迭代器的開散列實現
HashBucket.h
#pragma once
#include<vector>
using namespace std;
template <class V>
class HashBucketNode {//元素節點
public:
V m_val;
HashBucketNode * m_next;
HashBucketNode(const V& val = V()) :
m_val(val),
m_next(nullptr)
{}
template<class K, class V, class KeyOfVal, class HF>
friend class Iterator;
template<class K, class V, class KeyOfVal, class HF>
friend class HashBucket;
};
class dealInt {
public:
int operator()(const int& key) {
return key;
}
};
// K:關鍵碼類型
// V: 不同容器V的類型不同,如果是unordered_map,V代表一個鍵值對,如果是unordered_set,V 爲 K
// KeyOfValue: 因爲V的類型不同,通過value取key的方式就不同, 所以可能需要傳入自定義的方法
// HF: 哈希函數仿函數對象類型,哈希函數使用除留餘數法,需要將Key轉換爲整形數字才能取模
template<class K, class V, class KeyOfVal, class HF = dealInt>
class Iterator {
public:
HashBucket<K, V, KeyOfVal, HF>* m_hbpos;//直到桶在哪兒, 方便找上一個或下一個桶
HashBucketNode<V>* m_node;//具體節點
Iterator(HashBucket<K, V, KeyOfVal, HF>* hbpos = nullptr, HashBucketNode<V>* node = nullptr) :
m_hbpos(hbpos),
m_node(node)
{}
Iterator(const Iterator& it) :
m_hbpos(it.m_hbpos),
m_node(it.m_node)
{}
V& operator*() {
return m_node->m_val;
}
V* operator->() {
return &(m_node->m_val);
}
Iterator operator++() {
HashBucketNode<V>* t = m_node;
m_node = m_node->m_next;
if (!m_node) {//爲空
size_t tmp = m_hbpos->HashFunc(KeyOfVal()(t->m_val)) + 1;//下一個桶的下標
for (; tmp < m_hbpos->capacity(); ++tmp) {
if (m_hbpos->m_table[tmp]) {
m_node = m_hbpos->m_table[tmp];
break;
}
}
}
return *this;
}
Iterator operator++(int) {
Iterator tmp = *this;
this->operator++();
return tmp;
}
bool operator==(const Iterator& data) const {
return m_node == data.m_node;
}
bool operator!=(const Iterator& data) const {
return m_node != data.m_node;
}
};
template<class K, class V, class KeyOfVal, class HF = dealInt>
class HashBucket {
vector<HashBucketNode<V>*> m_table;
size_t m_size;
static size_t s_m_primeTable[30];
size_t m_primePos;
template<class K, class V, class KeyOfVal, class HF>
friend class Iterator;
typedef Iterator<K, V, KeyOfVal, HF> Iterator;
size_t HashFunc(const K& key) {
return HF()(key) % capacity();
}
void checkCapacity() {//檢查是否需要擴容, 若需要, 則擴容
int oldcapacity = capacity();
if (oldcapacity == m_size) {//此時哈希衝突發生概率100%, 此時擴容
vector<HashBucketNode<V>*> tmp(s_m_primeTable[++m_primePos]);
m_table.swap(tmp);
m_size = 0;
for (auto e : tmp) {
for (; e; e = e->m_next) {
insert(e->m_val);
}
}
}
}
public:
HashBucket(const size_t capacity = s_m_primeTable[0]) :
m_table(capacity, nullptr),
m_size(0),
m_primePos(0)
{}
~HashBucket() {
clear();
}
size_t capacity() {
return m_table.size();
//return s_m_primeTable[m_primePos];
}
Iterator begin() {
for (size_t i = 0; i < capacity(); ++i) {
if (m_table[i]) {
return Iterator(this, m_table[i]);
}
}
return Iterator(this, nullptr);
}
Iterator end() {
return Iterator(this, nullptr);
}
pair<Iterator, bool> insert(const V& val) {
checkCapacity();//首先檢查是否需要擴容
int k = HashFunc(KeyOfVal()(val));
HashBucketNode<V>* cur = m_table[k];
while (cur) {
if (KeyOfVal()(cur->m_val) == KeyOfVal()(val)) {
return pair<Iterator, bool>(Iterator(this, cur), false);
}
cur = cur->m_next;
}
cur = new HashBucketNode<V>(val);
cur->m_next = m_table[k];
m_table[k] = cur;
++m_size;
return pair<Iterator, bool>(Iterator(this, m_table[k]), true);//因爲是頭插, 所以返回頭
}
Iterator find(const K& Keyval) {
int k = HashFunc(Keyval);
HashBucketNode<V>* cur;
for (cur = m_table[k]; cur; cur = cur->m_next) {
if (KeyOfVal()(cur->m_val) == Keyval) {
break;
}
}
return Iterator(this, cur);
}
pair<Iterator, bool> erase(const K& Keyval) {
Iterator f = find(Keyval);
int n = HashFunc(Keyval);
if (f.m_node) {
Iterator tmp = f;
if (m_table[n] == f.m_node) {
m_table[n] = m_table[n]->m_next;
}
else {
for (HashBucketNode<V>* cur = m_table[n]; cur->m_next; cur = cur->m_next) {
if (cur->m_next == f.m_node) {
cur->m_next = cur->m_next->m_next;
break;
}
}
}
++tmp;
delete f.m_node;
--m_size;
return pair<Iterator, bool>(tmp, true);
}
return pair<Iterator, bool>(Iterator(this, nullptr), false);
}
size_t size() {
return m_size;
}
bool empty() {
return 0 == m_size;
}
void clear() {
HashBucketNode<V>* tmp;
m_size = 0;
for (auto head : m_table) {
while (head) {
tmp = head;
head = head->m_next;
delete tmp;
}
}
for (auto& e : m_table) {
e = nullptr;
}
}
size_t bucket_count()const {//返回哈希桶中桶的個數
size_t count = 0;
for (auto& i : m_table) {
if (i != nullptr) {
++count;
}
}
return count;
}
size_t bucket_size(size_t n)const {//返回n號桶中的元素個數
size_t count = 0;
for (HashBucketNode<V>* i = m_table[n]; i; i = i->m_next) {
++count;
}
return count;
}
size_t bucket(const K& key) {//返回key對應幾號桶
return HashFunc(key);
}
};
template<class K, class V, class KeyofValue, class HF>
size_t HashBucket<K, V, KeyofValue, HF>::s_m_primeTable[30] = {
11, 23, 47, 89, 179,
353, 709, 1409, 2819, 5639,
11273, 22531, 45061, 90121, 180233,
360457, 720899, 1441807, 2883593, 5767169,
11534351, 23068673, 46137359, 92274737, 184549429,
369098771, 738197549, 1476395029, 2952790016ul, 429496729ul
};
測試main.cpp
#include"HashBucket.h"
using namespace std;
class KeyofValueint {
public:
int operator()(int key) {
return key;
}
};
void test() {
cout << "test1:\n";
HashBucket<int, int, KeyofValueint> hb;
hb.insert(6);
Iterator<int, int, KeyofValueint> q = hb.insert(9).first;
hb.insert(17);
hb.insert(16);
hb.insert(19);
hb.insert(27);
hb.insert(61);
hb.insert(98);
hb.insert(26);
hb.insert(63);
hb.insert(39);
hb.insert(28);
pair<Iterator<int, int, KeyofValueint>, bool> p = hb.insert(100);
pair<Iterator<int, int, KeyofValueint>, bool> p2 = hb.insert(100);
cout << *p.first << endl;
p = hb.erase(9);
p2 = hb.erase(10101);
cout << *p.first << endl << endl;
for (auto & e : hb) {
cout << e << endl;
}
}
int main() {
test();
system("pause");
return 0;
}
開散列實現unordered_map
寫在另一篇博客中, 戳鏈接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/104361724
哈希的應用
先寫到這, 還會更新