哈希---开散列

上一篇讲解了关于哈希冲突的产生及其解决方式之一的 闭散列。
本篇主要讲解 开散列

开散列

**开散列:**开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

在这里插入图片描述在这里插入图片描述
从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

开散列实现


template<class V>
struct HashBucketNode
{
	HashBucketNode(const V& data)
		: _pNext(nullptr), _data(data)
	{}
	HashBucketNode<V>* _pNext;
	V _data;
};
// 所实现的哈希桶中key是唯一的
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, 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;
}
PNode* Find(const V& data);
size_t Size()const;
bool Empty()const;
void Clear();
bool BucketCount()const;
void Swap(HashBucket<V, HF>& ht;
~HashBucket();
private:
	size_t HashFunc(const V& data);
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);
				pCur->_pNext = newHt._ht[bucketNo];
				newHt._ht[bucketNo] = pCur;
				pCur = _ht[bucketIdx];
			}
		}
		newHt._size = _size;
		this->Swap(newHt);
	}
}

开散列与闭散列比较

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值