閉散列:
也叫開放定址法,當發生哈希衝突時,如果哈希表未被裝滿,說明在哈希表中必然還有空位置,那麼可以把key存放到衝突位置中的“下一個” 空位置中去。
#include <iostream>
using namespace std;
// 哈希表每個空間給個標記
// EMPTY此位置空, EXIST此位置已經有元素, DELETE元素已經刪除
enum State
{
EMPTY
, EXIST
, DELETE
};
// 哈希函數採用處理餘數法,被模的key必須要爲整形纔可以處理,此處提供將key轉化爲整形的方法
// 整形數據不需要轉化
/*template<class T>
class DefHashF
{
public:
size_t operator()(const T& val)
{
return val;
}
};
// key爲字符串類型,需要將其轉化爲整形
class Str2Int
{
public:
size_t operator()(const string& str)
{
return BKDRHash(str.c_str());
}
size_t BKDRHash(const char * str)
{
unsigned int seed = 131; // 31 131 1313 13131 131313
unsigned int hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return (hash & 0x7FFFFFFF);
}
};*/
//線性探測實現
// 爲了實現簡單,此哈希表中我們將比較直接與元素綁定在一起
template<class K, class V>
class HashTable
{
struct Elem
{
pair<K, V> _val;
State _state;
};
public:
HashTable(size_t capacity = 3)
: _ht(capacity)
, _size(0)
{
for (size_t i = 0; i < capacity; ++i)
_ht[i]._state = EMPTY;
}
bool Insert(const pair<K, V>& val)
{
// 檢測哈希表底層空間是否充足
// _CheckCapacity();
size_t hashAddr = HashFunc(key);
// size_t startAddr = hashAddr;
while (_ht[hashAddr]._state != EMPTY)
{
if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first == key)
return false;
hashAddr++;
// hashAddr %= _ht.capacity();
if (hashAddr == _ht.capacity())
hashAddr = 0;
/*
// 轉一圈也沒有找到,注意:動態哈希表,該種情況可以不用考慮,哈希表中元素個數到達
一定的數量,哈希衝突概率會增大,需要擴容來降低哈希衝突,因此哈希表中元素是不會存滿的
if(hashAddr == startAddr)
return false;
*/
}
// 插入元素
_ht[hashAddr]._state = EXIST;
_ht[hashAddr]._val = val;
_size++;
return true;
}
int Find(const K& key)
{
size_t hashAddr = HashFunc(key);
while (_ht[hashAddr]._state != EMPTY)
{
if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first == key)
return hashAddr;
hashAddr++;
}
return hashAddr;
}
bool Erase(const K& key)
{
int index = Find(key);
if (-1 != index)
{
_ht[index]._state = DELETE;
_size++;
return true;
}
return false;
}
size_t Size()const
{
return _size;
}
bool Empty() const
{
return 0 == _size;
}
void Swap(HashTable<K, V, HF>& ht)
{
swap(_ht, ht._ht);
swap(_size, ht._size);
}
private:
size_t HashFunc(const K& key)
{
return key % _ht.capacity();
}
/*private:
size_t HashFunc(const K& key)
{
return HF()(key) % _ht.capacity();
}
*/
private:
vector<Elem> _ht;
size_t _size;
};
線性探測優點:實現非常簡單,
線性探測缺點:一旦發生哈希衝突,所有的衝突連在一起,容易產生數據“堆積”,即:不同關鍵碼佔據了可利用的空位置,使得尋找某關鍵碼的位置需要許多次比較,導致搜索效率降低。
二次探測
線性探測的缺陷是產生衝突的數據堆積在一塊,這與其找下一個空位置有關係,因爲找空位置的方式就 是挨着往後逐個去找,因此二次探測爲了避免該問題,找下一個空位置的方法爲: = ( + )% m, 或者: = ( - )% m。其中:i = 1,2,3…, 是通過散列函數Hash(x)對元素的關鍵碼 key 進行 計算得到的位置,m是表的大小。
當表的長度爲質數且表裝載因子a不超過0.5時,新的表項一定能夠插入,而且任何一個位置都不會被探查兩次。因此只要表中有一半的空位置,就不會存在表滿的問題。在搜索時可以不考慮表裝 滿的情況,但在插入時必須確保表的裝載因子a不超過0.5,如果超出必須考慮增容。
開散列
開散列概念 開散列法又叫鏈地址法(開鏈法),首先對關鍵碼集合用散列函數計算散列地址,具有相同地址的關鍵碼 歸於同一子集合,每一個子集合稱爲一個桶,各個桶中的元素通過一個單鏈錶鏈接起來,各鏈表的頭結 點存儲在哈希表中
開散列實現
#include <iostream>
using namespace std;
// 哈希表每個空間給個標記
// EMPTY此位置空, EXIST此位置已經有元素, DELETE元素已經刪除
enum State
{
EMPTY
, EXIST
, DELETE
};
// 哈希桶中元素是用鏈表串接起來的,因此先給出哈希桶的結構
template<class V>
struct HashBucketNode
{
HashBucketNode(const V& data)
: _pNext(nullptr)
, _data(data)
{}
HashBucketNode<V>* _pNext;
V _data;
};
template<class V, class HF = DefHashF<T> >
class HashBucket
{
typedef HashBucketNode<V> Node;
typedef Node* PNode;
public:
HashBucket(size_t capacity = 3)
: _size(0)
{
_ht.resize(GetNextPrime(capacity), nullptr);
}
// 哈希桶中的元素不能重複
PNode* Insert(const V& data)
{
// 確認是否需要擴容。。。
// _CheckCapacity();
// 1. 計算元素所在的桶號
size_t bucketNo = HashFunc(data);
// 2. 檢測該元素是否在桶中
PNode pCur = _ht[bucketNo];
while (pCur)
{
if (pCur->_data == data)
return pCur;
pCur = pCur->_pNext;
}
// 3. 插入新元素
pCur = new Node(data);
// 採用頭插法插入,效率高
pCur->_pNext = _ht[bucketNo];
_ht[bucketNo] = pCur;
_size++;
return pCur;
}
// 刪除哈希桶中爲data的元素(data不會重複),返回刪除元素的下一個節點
PNode* Erase(const V& data)
{
size_t bucketNo = HashFunc(data);
PNode pCur = _ht[bucketNo];
PNode pPrev = nullptr;
pNode pRet = nullptr;
while (pCur)
{
if (pCur->_data == data)
{
// 頭刪
if (pCur == _ht[bucketNo])
{
_ht[bucketNo] = pCur->_pNext;
}
比特科技
else
{
pPrev->_pNext = pCur->_pNext;
}
pRet = pCur->_pNext;
delete pCur;
_size--;
return pRet;
}
}
return nullptr;
}
// 查找data是否在哈希桶中
PNode* Find(const V& data)
{
size_t bucketNo = HashFunc(data);
PNode pCur = _ht[bucketNo];
while (pCur)
{
if (pCur->_data == data)
return pCur;
pCur = pCur->_pNext;
}
return nullptr;
}
size_t Size()const
{
return _size;
}
bool Empty()const
{
return 0 == _size;
}
void Clear()
{
for (size_t bucketNo = 0; bucketNo < _ht.capacity(); ++bucketNo)
{
PNode pCur = _ht[bucketNo];
while (pCur)
{
_ht[bucketNo] = pCur->_pNext;
delete pCur;
pCur = _ht[bucketNo];
}
}
_size = 0;
}
bool BucketCount()const
{
return _ht.capacity();
}
void Swap(HashBucket<V, HF>& ht)
{
swap(_ht, ht._ht);
swap(_size, ht._size);
}
~HashBucket()
{
Clear();
}
private:
size_t HashFunc(const V& data)
{
return HF()(data) % _ht.BucketCount();
}
private:
vector<PNode*> _ht;
size_t _size; // 哈希表中有效元素的個數
};
開散列增容
桶的個數是一定的,隨着元素的不斷插入,每個桶中元素的個數不斷增多,極端情況下,可能會導致一 個桶中鏈表節點非常多,會影響的哈希表的性能,因此在一定條件下需要對哈希表進行增容
開散列最好的情況是:每個哈希桶中剛好掛一個節點,再繼續插入元素時,每一次都會發生哈希衝突,因此,在元素個數剛好等於桶的個數時,可以給哈希表增容。
/*void _CheckCapacity()
{
size_t bucketCount = BucketCount();
if (_size == bucketCount)
{
HashBucket<V, HF> newHt(bucketCount);
for (size_t bucketIdx = 0; bucketIdx < bucketCount; ++bucketIdx)
{
PNode pCur = _ht[bucketIdx];
while (pCur)
{
// 將該節點從原哈希表中拆出來
_ht[bucketIdx] = pCur->_pNext;
// 將該節點插入到新哈希表中
// 計算該節點在新哈希表中的桶號
size_t bucketNo = newHt.HashFunc(pCur->_data);
// 找節點在新桶中的位置
PNode pPos = newHt._ht[bucketNo];
while (pPos)
{
if (pPos->_data == pCur->_data)
break;
pPos = pPos->_pNext;
}
// 將節點插入到新哈希表中
if (nullptr == pPos)
{
pCur->_pNext = newHt._ht[bucketNo];
newHt._ht[bucketNo] = pCur;
}
else
{
pCur->_pNext = pPos->next;
pPos->_pNext = pCur;
}
// 獲取原哈希表bucketIdx桶中下一個節點
pCur = _ht[bucketIdx];
}
}
newHt._size = _size;
this->Swap(newHt);
}
}
*/