【哈希】利用相比于闭散列插入数据时的空间利用率和搜索数据时效率较高的开散列(哈希碰撞的解决方法)进行哈希表的简单模拟实现

目录

前言

一、哈希冲突的解决方法开散列是什么?

哈希桶的介绍

二、哈希表的基本结构

三、哈希表的插入数据接口模拟实现

四、哈希表的查找数据接口模拟实现

五、哈希表的删除数据接口模拟实现

六、哈希表的析构函数接口模拟实现

七、哈希表的简单调试结果


前言

开散列是解决哈希冲突的一种方法,用该方法实现的哈希表比闭散列实现的哈希表在搜索,查找,删除数据方面的效率和插入数据的空间利用率方面都胜一筹。

一、哈希冲突的解决方法开散列是什么?

开散列法又叫链地址法或者是开链法,首先对关键码集合用哈希函数(一般都是利用除留余数法)计算散列地址,具有相同地址的关键码归于同一个桶(其实就是一个单链表)里,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点地址存储在哈希表中(也就是说此时的哈希表可以被看作为一个指针数组)。
8cfa9f6e9a984f20a5b39698ce7e40f8.png

哈希桶的介绍

哈希桶是一种常用的数据结构,用于解决哈希冲突的问题。在哈希表中,每个键值对被映射到一个特定的桶中,而哈希桶就是这些桶的集合。当多个键值对被映射到同一个桶时,就会发生哈希冲突。哈希冲突是指不同的键值对经过哈希函数计算后得到相同的哈希值,导致它们被映射到同一个桶中。解决哈希冲突的方法有很多种,其中一种常见的方法就是使用哈希桶。在哈希桶中,每个桶都是一个链表或者其他数据结构,用于存储哈希冲突的键值对。当发生哈希冲突时,新的键值对会被插入到对应的桶中,形成一个链表。当需要查找某个键值对时,哈希函数会计算出对应的桶,然后在该桶中进行线性搜索,直到找到目标键值对或者搜索到链表的末尾。

二、哈希表的基本结构

HashTable.h

#include<iostream>
#include<vector>
#include<string>
using namespace std;

namespace keke
{
	template<class K, class V>
	class HashNode
	{
		HashNode(const pair<K, V>& kv)
			:_kv(kv)
			, _next(nullptr)
		{}
		pair<K, V> _kv;
		HashNode* _next;
	};

	template<class k, class V>
	class HashTable
	{
    public:
		typedef HashNode<K, V> Node;
        HashTable()
        {
        _tables.resize(10);//在HashTable类对象初始化时,vector<Node*> _tables开10个空间
        }
		bool Insert(const pair<K, V>& kv)
		{

		}
		HashNode* Find(const K& key)
		{

		}
		bool Erase(const K& key)
		{

		}
		~HashTable()
		{

		}
	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};
}

三、哈希表的插入数据接口模拟实现

bool Insert(const pair<K, V>& kv)
{
	if (Find(kv.first))//检查插入元素是否重复(哈希表中的元素不可以重复)
		return false;
	if (1 == _n % _tables.size())//负载因子为1的时候进行扩容
	{
		vector<Node*> newtables;//重新开一个vector<Node*>为什么要重开一个vector<Node*>呢?为什么不直接在原有的空间基础上直接
								//2倍扩容?这是因为如果原来的空间是10,插入关键码是3的数据,利用除留余数法算出该数据所要插入
								//哈希表中的位置,同理插入13,接着对哈希表空间进行2倍扩容,如果在Find(13)时就会出现表里本来有
								//关键码13所在的数据但是却没有找到,因为Find(13)的时候,Find函数会先算出关键码13在表里所在的
								//位置,13%_tables.size(),_tables.size()不一样。
		newtables.resize(_tables.size() * 2);//对新vector表进行2倍扩容

		for (auto e : _tables)//遍历旧表将其元素插入到新表,但是这里不能复用Insert函数了
		{
			Node* cur = e;
			while (cur)
			{
				Node* next = cur->_next;//将cur的下一个节点的地址保存下来
				size_t hashi = cur->_kv.first % newtables.size();//算出旧表中的元素在新表中的位置
				//在新表相应位置中头插旧表中的数据
				cur->_next = newtables[hashi];
				newtables[hashi] = cur;
				cur = next;
			}
			e = nullptr;
		}
		_tables.swap(newtables);
	}
	Node* newnode = new Node(kv);
	size_t hashi = kv.first % _tables.size();
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	_n++;
	return true;
}

四、哈希表的查找数据接口模拟实现

Node* Find(const K& key)//这里利用的是键值对中的关键码来查找相应的键值对是否在哈希表中
{
	size_t hashi = key % _tables.size();
	Node* cur = _tables[hashi];
	while (cur)
	{
		if (key == cur->_kv.first)
		{
			return cur;
		}
		cur = cur->_next;
	}
	return nullptr;
}

五、哈希表的删除数据接口模拟实现

bool Erase(const K& key)
{
	size_t hashi = key % _tables.size();
	Node* cur = _tables[hashi];
	Node* prev = nullptr;
	while (cur)
	{
		if (key == cur->_kv.first)
		{
			if (nullptr == prev)
				_tables[hashi] = cur->_next;
			else
				prev->_next = cur->_next;
			delete cur;
			cur = nullptr;
			return true;
		}
		prev = cur;
		cur = cur->_next;
	}
	return false;
}

六、哈希表的析构函数接口模拟实现

~HashTable()
{
	for (size_t i = 0;i < _tables.size();i++)
	{
		Node* cur = _tables[i];
		while (cur)
		{
			Node* next = cur->_next;
			delete cur;
			cur = nullptr;
			cur = next;
		}
	}
}

七、哈希表的简单调试结果

Test.cpp

#include "HashTable.h"
void Test_HashBucket1()
{
	keke::HashTable<int, string> ht;
	ht.Insert(make_pair(2, "CSDN"));

}
int main()
{
	Test_HashBucket1();
	return 0;
}

eb2951b26c5b4a11b342dd06e00b167e.png

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值