哈希衝突-----閉散列與開散列

閉散列:

也叫開放定址法,當發生哈希衝突時,如果哈希表未被裝滿,說明在哈希表中必然還有空位置,那麼可以把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);
		}
	}
	*/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章