C++哈希表的实现

这篇具有很好参考价值的文章主要介绍了C++哈希表的实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.unordered系列容器的介绍

C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表
C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表

二.哈希介绍

1.哈希概念

C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表

2.哈希函数的常见设计

C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表

3.哈希冲突

C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表

4.哈希函数的设计原则

C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表

三.解决哈希冲突

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

1.闭散列(开放定址法)

C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表
因为线性探测跟二次探测很像,所以这里就只实现线性探测了

1.线性探测

1.动图演示

C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表
C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表

2.注意事项

C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表

3.代码的注意事项

1.仿函数的问题:
(1).因为string类型不能进行取模运算,因此给string类型增加一个仿函数
该仿函数可以将string转为整型,整型可以进行取模运算
因此这就相当于二层映射
string -> int -> 哈希表中的下标
(2)
因为这里要考虑到顺序问题,比如"abc",“acb”
或者ASCII码值相等的问题:“aad”,“abc”
所以很多大佬设计了很多字符串哈希算法
各种字符串Hash函数
大家感兴趣的话可以看这篇博客当中的介绍

(3)因为string类型的哈希映射太常用了,
所以这里使用了模板特化,以免每次要存放string时都要指名传入string的哈希函数

这里的哈希函数只返回了整形值,计算下标时一定不要忘了对哈希表大小取模
否则就会有vector的越界错误,直接assert断言暴力报错了

哈希表是Key-Value模型
哈希下标是按照Key来计算的

//仿函数
//整型的hash函数
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
//模板特化
//string的哈希函数
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		// BKDR字符串哈希函数
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 131;
			hash += e;
		}
		return hash;
	}
};

template<class K, class V, class Hash = HashFunc<K>>
class HashTable
....
4.代码实现
namespace open_address
{
	enum Status
	{
		EMPTY,
		EXIST,
		DELETE
	};

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		Status _s;         //状态
	};

	//仿函数
	//整型的hash函数
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	//模板特化
	//string的哈希函数
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			// BKDR字符串哈希函数
			size_t hash = 0;
			for (auto e : key)
			{
				hash *= 131;
				hash += e;
			}
			return hash;
		}
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_tables.resize(10);
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;

			// 负载因子0.7就扩容
			if (_n * 10 / _tables.size() == 7)
			{
				size_t newSize = _tables.size() * 2;
				HashTable<K, V, Hash> newHT;
				newHT._tables.resize(newSize);
				// 遍历旧表
				for (size_t i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]._s == EXIST)
					{
						newHT.Insert(_tables[i]._kv);
					}
				}

				_tables.swap(newHT._tables);
			}

			Hash hf;
			// 线性探测
			size_t hashi = hf(kv.first) % _tables.size();
			while (_tables[hashi]._s == EXIST)
			{
				hashi++;

				hashi %= _tables.size();
			}

			_tables[hashi]._kv = kv;
			_tables[hashi]._s = EXIST;
			++_n;

			return true;
		}

		HashData<K, V>* Find(const K& key)
		{
			Hash hf;

			size_t hashi = hf(key) % _tables.size();
			while (_tables[hashi]._s != EMPTY)
			{
				if (_tables[hashi]._s == EXIST
					&& _tables[hashi]._kv.first == key)
				{
					return &_tables[hashi];
				}

				hashi++;
				hashi %= _tables.size();
			}

			return NULL;
		}

		// 伪删除法
		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_s = DELETE;
				--_n;
				return true;
			}
			else
			{
				return false;
			}
		}

	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0; // 存储的关键字的个数
	};
}

2.开散列(哈希桶,拉链法)

上面的闭散列并不好用,因此我们重点介绍和实现开散列方法

1.概念

C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表

2.动图演示

插入之前:
C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表
插入过程:
C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表
插入之后:
C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表

3.增容问题

1.拉链法的负载因子

C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表
注意:扩容时因为要将下标重新映射所以扩容会使一个桶当中的数据被打散到不同的桶当中,使得这种极端情况很难发生

2.说明

1.对于这里的哈希桶我们采用单链表
2.为了后续使用开散列的哈希桶封装unordered_set和unordered_map,我们不使用STL库中的forward_list(C++11新增容器:单链表),而是自己手撕单链表
3.因为这里的单链表是我们自己实现的,所以要写析构函数,不能使用编译器默认生成的析构函数
4.为了提高效率,哈希表增容时我们直接转移节点,并不会去进行节点的深拷贝,那样太浪费空间了
5.开散列的哈希表无非就是一个指针数组而已,所以大家不要有任何的害怕
AVL树和红黑树我们都能实现,哈希表怕什么…

3.开散列和闭散列的比较

C++哈希表的实现,爱上C++,数据结构与算法,c++,散列表,哈希表

四.开散列哈希表的实现

1.跟闭散列哈希表相同的部分

namespace wzs
{
	//HashFunc<int>
	template<class K>
	//整型的哈希函数
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	//HashFunc<string>
	//string的哈希函数
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			// BKDR
			size_t hash = 0;
			for (auto e : key)
			{
				hash *= 131;
				hash += e;
			}
			return hash;
		}
	};

	template<class K, class V>
	struct HashNode
	{
		HashNode* _next;
		pair<K, V> _kv;

		HashNode(const pair<K, V>& kv)
			:_kv(kv)
			, _next(nullptr)
		{}
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable()
		{
			_tables.resize(10);
		}

		~HashTable();

		bool Insert(const pair<K, V>& kv);

		Node* Find(const K& key);

		bool Erase(const K& key);
		
	private:
		//哈希表是一个指针数组
		vector<Node*> _tables;
		size_t _n = 0;
		Hash hash;
	};
}

2.析构,查找,删除

1.析构

析构就是遍历哈希表,把每个单链表都销毁即可

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

2.查找

1.根据哈希函数计算出下标,找到对应的哈希桶
2.遍历哈希桶,找数据即可
3.找到则返回该节点,找不到返回空指针

Node* Find(const K& key)
{
	int hashi = hash(key) % _tables.size();
	Node* cur = _tables[hashi];
	while (cur)
	{
		if (cur->_kv.first == key)
		{
			return cur;
		}
		cur = cur->_next;
	}
	return nullptr;
}

3.删除

删除就是找到该节点,让该节点的前驱指向该节点的后继,然后delete该节点
注意:
如果该节点是该哈希桶的头节点,直接让该哈希桶的头节点成为该节点的后继,然后delete该节点即可

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

3.插入

因为我们的哈希表不支持存放重复值,所以插入时
1.先查找在不在,如果在,返回false表示插入失败
2.不在,判断是否需要扩容,如果需要,则进行扩容
3.插入时,先根据哈希函数计算出对应的下标,然后找到该哈希桶头插即可

1.不扩容的代码

bool Insert(const pair<K, V>& kv)
{
	//先查找在不在
	//如果在,返回false,插入失败
	if (Find(kv.first))
	{
		return false;
	}
	//扩容....
	//1.利用哈希函数计算需要插入到那个桶里面
	int hashi = hash(kv.first) % _tables.size();
	//头插
	Node* newnode = new Node(kv);
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	++_n;
	return true;
}

2.扩容代码

1.开辟新的哈希表
2.将新哈希表的容量扩为2倍(一定要做,因为转移数据时需要根据新表的大小映射下标)
3.转移数据时
(1).遍历旧表取节点
(2).利用哈希函数计算该节点在新表中的下标
(3).头插该节点
4.转移完数据后不要忘记把旧表中的哈希桶的节点置空,否则会出现野指针问题

bool Insert(const pair<K, V>& kv)
{
	//扩容
	if (_n == _tables.size())
	{
		//开辟新的哈希表
		HashTable newtable;
		int newcapacity = _tables.size() * 2;
		//扩2倍
		newtable._tables.resize(newcapacity);
		//转移数据
		for (int i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				int hashi = hash(cur->_kv.first) % newtable._tables.size();
				cur->_next = newtable._tables[hashi];
				newtable._tables[hashi] = cur;
				cur = next;
			}
			//防止出现野指针导致重复析构...
			_tables[i] = nullptr;
		}
	}
}

3.插入的完整代码

bool Insert(const pair<K, V>& kv)
{
	//先查找在不在
	//如果在,返回false,插入失败
	if (Find(kv.first))
	{
		return false;
	}
	//扩容
	if (_n == _tables.size())
	{
		//开辟新的哈希表
		HashTable newtable;
		int newcapacity = _tables.size() * 2;
		//扩2倍
		newtable._tables.resize(newcapacity);
		//转移数据
		for (int i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				int hashi = hash(cur->_kv.first) % newtable._tables.size();
				cur->_next = newtable._tables[hashi];
				newtable._tables[hashi] = cur;
				cur = next;
			}
			//防止出现野指针导致重复析构...
			_tables[i] = nullptr;
		}
		//交换两个vector,从而做到交换两个哈希表
		//通过学习vector的模拟实现,我们知道vector进行交换时只交换first,finish,end_of_storage
		_tables.swap(newtable._tables);
	}
	//1.利用哈希函数计算需要插入到那个桶里面
	int hashi = hash(kv.first) % _tables.size();
	//头插
	Node* newnode = new Node(kv);
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	++_n;
	return true;
}

4.开散列哈希表的完整代码

namespace wzs
{
	//HashFunc<int>
	template<class K>
	//整型的哈希函数
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	//HashFunc<string>
	//string的哈希函数
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			// BKDR
			size_t hash = 0;
			for (auto e : key)
			{
				hash *= 131;
				hash += e;
			}
			return hash;
		}
	};

	template<class K, class V>
	struct HashNode
	{
		HashNode* _next;
		pair<K, V> _kv;

		HashNode(const pair<K, V>& kv)
			:_kv(kv)
			, _next(nullptr)
		{}
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable()
		{
			_tables.resize(10);
		}

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

		bool Insert(const pair<K, V>& kv)
		{
			//先查找在不在
			//如果在,返回false,插入失败
			if (Find(kv.first))
			{
				return false;
			}
			//扩容
			if (_n == _tables.size())
			{
				//开辟新的哈希表
				HashTable newtable;
				int newcapacity = _tables.size() * 2;
				//扩2倍
				newtable._tables.resize(newcapacity);
				//转移数据
				for (int i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						int hashi = hash(cur->_kv.first) % newtable._tables.size();
						cur->_next = newtable._tables[hashi];
						newtable._tables[hashi] = cur;
						cur = next;
					}
					//防止出现野指针导致重复析构...
					_tables[i] = nullptr;
				}
				//交换两个vector,从而做到交换两个哈希表
				//通过学习vector的模拟实现,我们知道vector进行交换时只交换first,finish,end_of_storage
				_tables.swap(newtable._tables);
			}
			//1.利用哈希函数计算需要插入到那个桶里面
			int hashi = hash(kv.first) % _tables.size();
			//头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			return true;
		}

		Node* Find(const K& key)
		{
			int hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

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

	private:
		//哈希表是一个指针数组
		vector<Node*> _tables;
		size_t _n = 0;
		Hash hash;
	};
}

以上就是C++哈希表的实现的全部内容,希望能对大家有所帮助!文章来源地址https://www.toymoban.com/news/detail-765701.html

到了这里,关于C++哈希表的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构-线性表的顺序表基本操作代码实现(超级详细清晰 C++实现)

    顺序表是用一段 物理地址连续的存储单元 依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。 顺序表: 可动态增长的数组,要求数据是连续存储的 特点: 随机访问 顺序既可以 静态分配 ,也可以 动态分配 。在静态分配时,由于数组

    2024年02月07日
    浏览(56)
  • 数据结构中: 一元多项式的运算(相加,相减,相乘)------用C语言 / C++来实现。 数据结构线性表的操作和应用(顺序存储)

    线性表的操作和应用(顺序存储)。用顺序存储实现一元多项式,并进行加、减、乘运算。 (1)一元多项式结构体创建  (2)初始化 (3)一元多项式赋值             (4)打印一元多项式 (5)加法运算                        (6)减法运算 (7)乘法运算    全部代

    2024年02月01日
    浏览(65)
  • C++:【数据结构】哈希表

    哈希表(hash table)又叫散列表,是一种很实用的数据结构。首先来看一下百度给出的定义:散列表,是 根据关键码值(Key value)而直接进行访问的数据结构 。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放

    2024年02月03日
    浏览(58)
  • 【数据结构与算法】前缀和+哈希表算法

    关于前缀和和哈希这两个概念大家都不陌生,在之前的文章中也有过介绍:前缀和与差分算法详解 而哈希表最经典的一题莫过于 两数之和 题目链接 题目描述: 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它

    2024年02月01日
    浏览(112)
  • 【数据结构】哈希表(算法比赛向)

    目录 一:介绍 一:什么是哈希表 二、哈希表的应用 二:存储结构 a.拉链法: b.开放寻址法: 三:扩展 a.字符串哈希: 例题:      一:什么是哈希表 1、哈希表也叫散列表,哈希表是一种数据结构,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插

    2023年04月25日
    浏览(49)
  • 数据结构,查找算法(二分,分块,哈希)

    一、查找算法         1、二分查找:(前提条件: 必须有序的序列) 2、分块查找:(块间有序,块内无序)     索引表  +  源数据表     思路:     (1)先在索引表中确定在哪一块中     (2)再遍历这一块进行查找 //索引表 typedef  struct  {     int max; //块中最大值

    2024年02月11日
    浏览(60)
  • 【数据结构(C++版)】哈希表(散列表)

    目录   1. 散列表的概念 2. 散列函数的构造方法 2.1 直接定址法 2.2 除留余数法 2.3 数字分析法 2.4 平方取中法 3. 处理冲突的方法 3.1 开放定址法 3.1.1 线性探测法 3.1.2 平方探测法 3.1.3 双散列法 3.1.4 伪随机序列法 3.2 拉链法(链接法) 4. 散列查找及性能分析 5. 哈希的应用 5.1 位

    2024年02月15日
    浏览(46)
  • 【数据结构与算法】哈希—— 位图 | 布隆过滤器 | 哈希切割

    🐱作者:一只大喵咪1201 🐱专栏:《数据结构与算法》 🔥格言: 你只管努力,剩下的交给时间! 哈希是一种映射思想,这里再讲解两种应用哈希思想的数据结构。 问题: 给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数

    2024年02月02日
    浏览(57)
  • 【数据结构与算法——TypeScript】哈希表

    哈希表介绍和特性 哈希表是一种非常重要的数据结构,但是很多学习编程的人一直搞不懂哈希表到底是如何实现的。 在这一章节中,我门就一点点来实现一个自己的哈希表。 通过实现来理解哈希表背后的原理和它的优势。 几乎所有的编程语言都有直接或者间接的应用这种数

    2024年02月13日
    浏览(42)
  • 数据结构算法设计——哈希表(散列表)

            哈希表 又叫 散列表 ,他们两个是同一个东西,本文全文采用“散列表”的叫法。散列表的本质其实就是一个 数组 ,他的作用就像使用数组时一样,输入下标可以得到对应元素,散列表可以实现 输入一个的时候得到这个的地址信息 。 下面是百科给出

    2024年02月03日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包