数据结构学习——浅谈哈希表开散列和闭散列

写在前面

       顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( ),搜索的效率取决于搜索过程中元素的比较次数。
       理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:
插入元素
根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
搜索元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功
         该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

哈希冲突

对于两个数据元素的关键字 和 (i != j),有 != ,但有:Hash(i ) == Hash(j ),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。发生哈希冲突该如何处理呢?

哈希冲突解决

解决哈希冲突两种常见的方法是:闭散列和开散列

闭散列的实现

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?

类的成员变量

private:
	vector<elem> _ht;
	int _size;
/////////////////////////////////////////

enum  state{
	empty,
	exist,
	deleted

};

typedef struct elem{

		pair<K, V> val;
		state sta;

	}elem;

//1.闭散列
#include<map>
#include<vector>
#include<utility>
#include<iostream>
using namespace std;



const int PRIMECOUNT = 28;
const size_t primeList[PRIMECOUNT] =
{
	53ul, 97ul, 193ul, 389ul, 769ul,
	1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
	49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
	1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
	50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
	1610612741ul, 3221225473ul, 4294967291ul
};

enum  state{
	empty,
	exist,
	deleted

};


template<class K,class V>

class hash_1{

typedef struct elem{

		pair<K, V> val;
		state sta;

	}elem;


public:

	hash_1(int n = 3)
		:_size(0)
		, _ht(n)
	{
		for (int i = 0; i < _ht.capacity(); ++i){
		
			_ht[i].sta = empty;
		}
	
	}
	
	bool insert(const K& key){
	
		check_capacity();

		size_t _hashaddr = hash_func(key);
		pair<K, V>_val = { key, _hashaddr };     //pair类型的变量初始化
		size_t _start = _hashaddr;
		while (_ht[_hashaddr].sta == exist)
		{
			if (_ht[_hashaddr].sta == exist && _ht[_hashaddr].val.first == key)
				return false;

			_hashaddr++;
			if (_hashaddr == _ht.capacity())
				_hashaddr == 0;
		
			if (_hashaddr == _start)
				return false;
		    	
		
		}
	

	//如果为空,可以直接插入
		_ht[_hashaddr].val = _val;
		_ht[_hashaddr].sta = exist;
		++_size;
		return true;
	}

	void check_capacity(){
	 
		//增容的条件是: α>=0.7
		if (_size*10/_ht.capacity()>=7){
			hash_1<K, V>newht(getnextprime(_ht.capacity()));
             
			for (int i = 0; i < _ht.capacity(); ++i){
				if (_ht[i].sta == exist){
				
					newht.insert(_ht[i].val.first);
				}
				
			
			}

			Swap(newht);
		
		}
	
	
	}

	int getnextprime(size_t n){
	   
		for (int i = 0; i < PRIMECOUNT; ++i){
			if (primeList[i]>n)
				return primeList[i];
		}
		
		return 0;
	
	}

	



	int find(const K& key){
	//如果找到了,返回下标;没有找到就打印“不存在”;
		int _hashaddr = hash_func(key);
		size_t start = _hashaddr;
	
		while ( _ht[_hashaddr].sta != empty ){
		
			if (_ht[_hashaddr].sta == exist && _ht[_hashaddr].val.first == key)
				return _hashaddr;
		
			++_hashaddr;

			if (_hashaddr == _ht.capacity()){
				_hashaddr = 0;
			}
			if (_hashaddr == start)
			{
				cout << "不存在" << endl;
				return -1;
			}
		
		
		}
		cout << "不存在" << endl;
		return -1;
	}

	void erase(const K& key){
		
		int index = find(key);
		if (index != -1){

			_ht[index].sta = deleted;
			++_size;

		}
        
		return;
	
	}
	void Swap(hash_1<K, V>& ht){
		swap(_ht, ht._ht);
		swap(_size, ht._size);
		

	}


private:	
 size_t hash_func(const K& key){

		return key % _ht.capacity();
	}


private:
	vector<elem> _ht;
	int _size;
	
};

/////////////////////////////////////////华丽分割////////////////////////////////////////////////////////////

开散列的实现

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
在这里插入图片描述
类的成员变量

private:
		vector<Node *> _ht;                    
		size_t _size;        
///////////////////////////////////////////////

template<class v>

struct node{

	node(const v& data)
	:_val(data),
	_pnext(nullptr)
	
	{}

	int _val;
	node<v>* _pnext;

};

    typedef node<V> Node;
	typedef node* PNode;


//哈希---开散列--哈希桶

//开散列的实现

template<class v>

struct node{

	node(const v& data)
	:_val(data),
	_pnext(nullptr)
	
	{}

	int _val;
	node<v>* _pnext;

};



template<class V, class HF = DefHashF<T> >
class HashBucket
{
	typedef node<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!=nullptr && pCur->_val!=data)
		{
			
			pPrev = pCur;
			pCur = pCur->_pnext;
			
		}

		//要删除的节点不存在
		if (pCur == nullptr)
			return nullptr;
		//如果是头删
		if (pPrev == nullptr){	
		_ht[bucketNo] = pCur->_pnext;
		return _ht[bucketNo];
		
		}

		//数据存在,并且非首元素 
		pPrev->_pnext = pCur->_pnext;
		return pPrev->_pnext;
	
	}

	// 查找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)
			{
				PNode pNext = pCur->_pNext;
				delete pCur;
				pCur = pNext;
			}
		}

		_size = 0;
	}

	bool BucketCount()const
	{
		return _ht.capacity();
	}


	
	void Swap(HashBucket<V, HF>& ht)
	{
		swap(_ht, ht._ht);
		swap(_size, ht._size);
	}

	~HashBucket()
	{
		Clear();
	}


	/*桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可能会导致一
		个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希表进行增容,那该条件
		怎么确认呢?开散列最好的情况是:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发
		生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。*/

	void _CheckCapacity(){

		size_t bucketCount = BucketCount();

		if (_size == bucketCount){

			//增容
			HashBucket<V, HF> newHt(getnextprime(_ht.capacity()));
			for (int i = 0; i < newHt._ht.capacity; ++i)
				newHt._ht[i] = nullptr;


			for (int j = 0; j < _ht.capacity(); ++j){

				PNode cur = _ht[j];
				int hashNo = -1;
				while (cur){

					//取旧哈希桶i号桶的第一个节点
					_ht[j] = cur->_pnext;		//头删
					//计算当前节点在新空间的桶号
					hashNo = newHt.HashFunc(cur->_val);	//计算在那个哈希桶中
					//头插法将该节点插入新空间
					cur->_pnext = newHt[hashNo];//连接新桶中的内容
					newHt[hashNo] = cur;//将给定的第一个节点放入新空间
					//取旧哈希桶i号桶的第next个节点
					cur = ht[j]//cur取原链表中的下一个节点
				}
			}

		}
		newHt._size = _size;
		this->Swap(newHt);
	}

	


	private:
		size_t HashFunc(const V& data)
		{
			return HF()(data) % _ht.BucketCount();
		}

	private:
		vector<Node *> _ht;                    
		size_t _size;                             // 哈希表中有效元素的个数

	};

开散列与比散列比较
应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a <= 0.7,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间。

猜你喜欢

转载自blog.csdn.net/tonglin12138/article/details/92777272