哈希第二弹:避免哈希冲突详解及其代码粗略实现哈希表

前引: 

我们在哈希第一弹中提到了避免哈希冲突的三种方法,在本篇博文中我们对他们进行更加详细的了解

哈希冲突的解决方法
闭散列(开放定址法):
 当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把关键字存放到冲突位置中的“下一个”空位置中去。

            如何寻找下一个空位置呢?

   线性探测:
           从发生冲突的位置开始,依次向后探测,直到寻找下一个空位置为止

  二次探测:
         从发生冲突的位置开始,找下一个空位置,查找空位置的方法为 (+i^2)%m,i=1,2,3........;m是哈希表大小,是经过关键字计算后的出的哈希地址

开散列(链地址法、开链法)
 对关键字集合用散列函数计算散列地址,具有相同地址的关键码归于同一个子集合,每一个子集和称为一个桶,各个桶中的元素通过一个单链表连接起来,各链表的头结点存储在哈希表中。
————————————————
版权声明:本文为CSDN博主「书生Qyuan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43519514/article/details/107101794
 

正文:

闭散列之线性探测

我们在之前了解了线性探测如何解决哈希冲突,实现和理解都很简单,通过哈希函数算出所在位置,如果没有存放数据,则直接存放;如果存放了数据,则去找下一个没有存放数据的位置,进行存放该数据。但这里好像又出现了一个比较头疼的问题,看下图,散列表在插入一个5,经过计算我们应该插入5的位置,但是此时444占据了这个位置,我们就需要找下一个位置了。这可以完成,且不是太大问题。关键是我们要查找的时候,就有可能要遍历整张表,效率可能为O(N),此时我们设为哈希表的意义就不太大了。

而出现这个问题,最主要的是一旦某处发生了哈希冲突,所有的冲突可能连在一起;不同关键码占据了可利用的空位置,是寻找某关键码的位置需要许多次比较,导致搜索效率降低。

闭散列之二次探测

线性探测使产生冲突的数据堆积在一起,这与线性探测一次查找下一个空位置有关。二次探测,为了解决这个问题采用了不同的查找下一个空位置的方法。但由此也产生了一个问题:空间利用率不太高。

 

总之,闭散列的这两种方法都有一定的缺陷,下面我们通过粗略的实现,来理解一下;

闭散列哈希表的代码实现

不过,在代码的实现之前我们先达成一些共识:怎么插入数据、怎么查找数据、怎么删除数据;我们也主要实现这三个功能。

插入数据比较简单:我们先通过哈希函数算出散列地址,这个位置没有存放有效数据,我们就可以直接存放数据,倘若此处有数据了,我们往后找空位置,存放数据。

怎么查找数据呢?如果我们遍历整张表的话,那么效率极低,我们也就完全没有必要实现哈希表了,直接vector搞定;所以,为了效率我们不能遍历整张表。那么怎么办呢?我们刚才插入的时候,就往后找空位置插入,这就意味着如果我们通过哈希函数计算出散列地址,而该地址没有存放我们想要的数据,我们就可以往后找,如果找的了空位置呢?肯定这张表中没有我们想要的数据,查找也就可以到此结束了。

怎么删除数据呢?还是通过哈希函数算出散列地址,直接删除吗?这个想法,对我们来说是绝对不可能的方案。倘若我们这样删除了,按照刚才我们查找的想法,查找与之冲突的元素时,得知此处为空,我们便会认为那个数据没有在此表中,而实际可能在后面。如下图,我们删除了44,在查找444的时候就会出现问题。所以我们在删除的时候,关键的一步是建立数据的状态,具体时间什么样,我们代码中见

namespace CloseHash{
  //标记当前表中位置的状态 Empty表示这个位置没有数据,Delete表示该位置的数据已经被删除了,Exit表示当前位置存有数据
	enum status{
		Empty,
		Delete,
		Exit,
	};


	template<class T>
	struct HashData{
		T _data;
		status _status;
	};


	template <class K,class T,class KeyofValue>
	class Hashtable{
		typedef HashData<T>  Data;
	private:
		std::vector<Data> _table; //哈希表,用来存放数据
		size_t    _num=0 ;   //记录哈希表中有效数据的个数
	public:
		//插入的时候我们什么时候需要进行扩容呢?等到满的时候是不可能的?
		//我们需要一个空的位置,进行判断是否还要继续进行查找
		//因此我们需要一个负载因子,等负载因子到达一个值或高于这个值的时候,我们就进行扩容
		bool insert(const T& data)
		{
			KeyofValue kofv;
			if ( _table.size() == 0 ||  (_num * 10) / _table.size() >= 7    )
			{
				std::vector<Data> newtable;
				size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;
				newtable.resize(newsize);
				for (size_t i = 0; i < _table.size(); ++i)
				{
					if (_table[i]._status == Exit)
					{
						size_t newIndex = kofv(_table[i]._data) % newsize;
						while (newtable[newIndex]._status == Exit)
						{
							++newIndex;
							if (newIndex == newtable.size())
							{
								newIndex = 0;
							}
						}
						newtable[newIndex] = _table[i];
					}
				}
				_table.swap(newtable);
			}

             //线性探测
			size_t index = kofv(data) % _table.size();
			
				while (_table[index]._status == Exit)
				{
					if (kofv(_table[index]._data) == kofv(data))
						return false;
					++index;
					if (index == _table.size())
						index = 0;
				}
				_table[index]._data = data;
				_table[index]._status = Exit;
				++_num;
				return true;

		}

		T* find(const T& data)
		{
			KeyofValue kofv;
			size_t index = kofv(data) % _table.size();
			while (_table[index]._status != Empty)
			{
				if (_table[index]._status != Empty&&kofv(_table[index]._data) == kofv(data))
				{
					if (_table[index]._status == Exit)
						return &(_table[index]._data);
					else if (_table[index]._status == Delete)
						return nullptr;
				}
					
				else
				{
					++index;
					if (index == _table.size())
						index = 0;
				}

			}
			return nullptr;
		}

		bool erase(const T& data)
		{
			Data* ret = find(data);
			if (ret != nullptr)
			{
				ret->_staus = Delete;
				--_num;
				return true;
			}
			return false;

		}
	
	};
}

 

template <class K>
	struct KeyofValue{
		const K& operator() (const K& key)
		{
			return key;
		}
	};

	template <class K>
	class unordered_set{
	public:
		bool insert(const K& key)
		{
			return _hash.insert(key);
		}
		bool erase(const K& key)
		{
			return _hash.erase(key);
		}
		K* find(const K& key)
		{
			 return  _hash.find(key);

		}
	private:
		CloseHash::Hashtable<K, K, KeyofValue<K>>  _hash;
	};
	void test_set()
	{
		unordered_set<int>  us;
		us.insert(1);
		us.insert(11);
		us.insert(111);
		us.insert(11111);
		us.insert(4);
		us.insert(2);

	}

测试线性探测,观察优缺

 

缺点:

一旦某处发生了哈希冲突,所有的冲突可能连在一起;不同关键码占据了可利用的空位置,是寻找某关键码的位置需要许多次比较,导致搜索效率降低。

测试二次探测

发现二次探测一定程度上改善了线性探测的缺点。但由于查找位置的方法,造成了一定量的空间浪费。

 

代码修改部分: 

bool insert(const T& data)
		{
			KeyofValue kofv;
			if ( _table.size() == 0 ||  (_num * 10) / _table.size() >= 7    )
			{
				std::vector<Data> newtable;
				size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;
				newtable.resize(newsize);
				for (size_t i = 0; i < _table.size(); ++i)
				{
					if (_table[i]._status == Exit)
					{
						size_t newIndex = kofv(_table[i]._data) % newsize;
						while (newtable[newIndex]._status == Exit)
						{
							++newIndex;
							if (newIndex == newtable.size())
							{
								newIndex = 0;
							}
						}
						newtable[newIndex] = _table[i];
					}
				}
				_table.swap(newtable);
			}


           //二次探测
			size_t index = kofv(data) % _table.size();
			int i = 1;
				while (_table[index]._status == Exit)
				{
					if (kofv(_table[index]._data) == kofv(data))
						return false;
					index = index + i ^ 2;
					i++;
					index %= _table.size();
				}
				_table[index]._data = data;
				_table[index]._status = Exit;
				++_num;
				return true;

		}

开散列的情况我们留在哈希第三弹!

本片博文就此,就结束了!

 注:如果本篇博客有任何错误和建议,欢迎伙伴们留言,你快说句话啊!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值