【C++】unordered_map与unorder_set的封装(哈希桶)

文章目录

前言


我们开辟一个指针数组,指针数组中存放我们结点的类型,我们算出元素的下标hashi后,头插在数组的对应位置,数组的位置可以链接一串链表,所以也避免了哈希冲突

一、模板参数的改造

我们这里把pair<K,V>看成一个整体,我们设计模板的时候就不需要考虑是不是键值对类型,需不需要多传一个模板参数的问题,达到了普适性。

cpp 复制代码
template<class T>
struct HashNode {
	T _data;
	HashNode<T>* _next;
	HashNode(const T& data)
		:_data(data),
		_next(nullptr)
	{}
};

在map中,T传pair<K,V>类型

在set中,T传K类型

二、模板的特例化操作

我们要想插入一个结点,肯定要知道这个结点插入的位置,所以我们要自己写一个哈希函数的模板来进行下标的计算,但我们int类型之间计算下标很容易,那我们字符串该怎么办?
这个时候就需要模板的特例化了,根据传入的参数不同,具体类型具体分析

cpp 复制代码
template<class K>
struct DefaultHashFunc {
	size_t operator()(const K& key) {
		return (size_t)key;
	}
};

template<>
struct DefaultHashFunc<string> {//对字符串特殊处理
	size_t operator()(const string& str) {
		size_t hash = 0;
		for (auto e : str) {
			hash *= 131;
			hash += e;
		}
		//字符串的总和再与131相乘,
		//减少字符串和雷同的情况
		return hash;
	}
};

之后再对算出的hash进行取模的操作

三、仿函数的妙用

我们value_type类型用模板参数T代替之后,这个时候就会衍生一个问题,我T可能为键值对类型,我键值对之间怎么比较呢?
例如:T t1与T t2两个变量,我们肯定不能直接比较,肯定要依据他们的键值大小进行比较,所以我们需要自己写一个用于比较的函数,这个时候仿函数刚好能发挥这个用处,可以作为模板参数传入自己写的比较函数

取出他们的键,让他们进行比较,这里set也这样写是为了配合map,因为两者都用的一个哈希桶模板

cpp 复制代码
struct SetKeyOfT {
			const K& operator()(const K&key) {
				return key;
			}
		};


struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
 

四、unordered迭代器基本操作

1.const迭代器注意:


这里如果使用const迭代器会报错,因为发生了权限的放大

cpp 复制代码
修改方法:在参数的位置以及定义的时候给HashTable加上const
这样当我们传const类型的时候发生权限的平移,传普通类型的时候发生权限缩小
	const HashTable<K, T, KeyOfT, HashFunc>* _pht;
	Node* _node;

	HTIterator(Node*node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
		:_node(node),
		_pht(pht)
	{}

2.HashTable与HTIterator的冲突

HashTable中用到了HTIterator,因为要创建出迭代器,而我们HTIterator内部使用到了HashTable中的私有。这就造成了先有鸡还是先有蛋的问题。

解决方法:

在HTIterator的类前面进行前置声明。告诉编译器这个HashTable是存在的

在HashTable中声明友元

五、迭代器的构造问题


在unordered_set中我们返回的pair里面的iterator是const类型,但我们哈希桶里面写的Insert中的pair返回的是普通迭代器,因为类模板实例化不同的模板参数就是不同的类型,所以这里const_iterator与iterator我们可以看成是两个不相同的类型,如果我们直接传哈希桶里面的Insert返回值会发生报错,因为类型不匹配。
这个时候我们需要一个函数来将iterator类型转变为const_iterator类型,我们可以从迭代器的拷贝构造下手。

cpp 复制代码
typedef HTIterator<  K,  T,   Ptr,   Ref,   KeyOfT,  HashFunc>  Self;
typedef HTIterator<  K, T, T*, T&, KeyOfT, HashFunc> Iterator;
typedef HashNode<T> Node;
const HashTable<K, T, KeyOfT, HashFunc>* _pht;
Node* _node;


HTIterator(const Iterator& it)
		:_node(it._node)
		, _pht(it._pht)
	{}

如果调用这个函数的是普通迭代器iterator,这里就是纯拷贝构造

如果调用这个函数的是const_iterator,那么这个函数就是构造,我们可以传入普通迭代器iterator来构造出const_iterator

六、完整代码

1.hash_bucket.h

cpp 复制代码
#pragma once
#include<vector>
#include<string>


namespace hash_bucket {

template<class T>
struct HashNode {//结点的定义
	T _data;
	HashNode<T>* _next;
	HashNode(const T& data)
		:_data(data),
		_next(nullptr)
	{}
};


template<class K>
struct DefaultHashFunc {//哈希函数进行下标的求取
	size_t operator()(const K& key) {
		return (size_t)key;
	}
};

template<>//模板特例化
struct DefaultHashFunc<string> {//对字符串特殊处理
	size_t operator()(const string& str) {
		size_t hashi = 0;
		for (auto e : str) {
			hashi *= 131;
			hashi += e;
		}
		return hashi;
	}
};


template<class K, class T, class KeyOfT, class HashFunc >
class HashTable;
//前置声明
template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
struct HTIterator {

	typedef HTIterator<  K, T, Ptr, Ref, KeyOfT, HashFunc>  Self;
	typedef HTIterator<  K, T, T*, T&, KeyOfT, HashFunc> Iterator;
	typedef HashNode<T> Node;


	const HashTable<K, T, KeyOfT, HashFunc>* _pht;
	//引入哈希桶,因为我们进行++操作的时候需要哈希桶数组来确定位置
	//这里哈希桶要为const类型
	Node* _node;

	HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
		:_node(node),
		_pht(pht)
	{}

	HTIterator(const Iterator& it)
		:_node(it._node)
		, _pht(it._pht)
	{}


	Self& operator++() {
		if (_node->_next) {
			//当前链表后面还有
			_node = _node->_next;
		}
		else {
			//当前链表以及走完
			KeyOfT kot;
			HashFunc hf;
			size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
			//kot先取出_node->_data中的K值然后hf算出哈希下标
			++hashi;
			//从下一个位置开始
			while (hashi < _pht->_table.size()) {
				if (_pht->_table[hashi]) {
					//找不为空的位置
					_node = _pht->_table[hashi];
					return*this;
				}
				else {
					hashi++;
				}
			}
			_node = nullptr;
		}
		return *this;
	}

	Ref operator*() {
		return _node->_data;
	}
	Ptr operator->() {
		return &_node->_data;
	}

	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}

	bool operator==(Self& s) {
		return _node == s._node;
	}

};


template<class K,class T,class KeyOfT,class HashFunc=DefaultHashFunc<K>>
//KeyOfT的作用是取出T里面的K值,因为T有可能为pair类型
class HashTable {
	typedef HashNode<T> Node;
public:
	template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc >
	friend struct HTIterator;//友元的引入
	typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;
	typedef HTIterator<K, T, const T*, const T&, KeyOfT, HashFunc> const_iterator;

	iterator begin() {
		//找到数组的第一个不为空的位置
		for (size_t i = 0; i < _table.size(); i++) {
			if (_table[i]) {
				return iterator(_table[i], this);
			}
		}
		return iterator(nullptr, this);
	}

	iterator end() {
		return iterator(nullptr, this);
	}


	const_iterator begin()const {
		for (size_t i = 0; i < _table.size(); i++) {
			if (_table[i]) {
				return const_iterator(_table[i], this);
			}
		}
		return const_iterator(nullptr, this);
	}

	const_iterator end()const {
		return const_iterator(nullptr, this);
	}

	HashTable(){
		//构造函数
		_table.resize(10,nullptr);
	}
	~HashTable() {
		//析构函数
		for (size_t i = 0; i < _table.size(); i++) {
			Node* cur = _table[i];
			while (cur) {
				Node* first = cur->_next;
				delete cur;
				cur = first;
			}
			_table[i] = nullptr;
		}
	}

	iterator Find(const K& key)
	{
		HashFunc hf;
		KeyOfT kot;
		size_t hashi = hf(key) % _table.size();
		Node* cur = _table[hashi];
		while (cur)
		{
			if (kot(cur->_data) == key)
			{
				return iterator(cur, this);
			}

			cur = cur->_next;
		}

		return end();
	}

	pair<iterator, bool> Insert(const T& data)
	{
		KeyOfT kot;

		iterator it = Find(kot(data));
		if (it != end())
		{
			return make_pair(it, false);
		}

		HashFunc hf;

		// 负载因子到1就扩容
		if (_n == _table.size())
		{
			 
			//size_t newSize = _table.size() * 2;
			size_t newSize = _table.size()*2;
			vector<Node*> newTable;
			newTable.resize(newSize, nullptr);

			// 遍历旧表,顺手牵羊,把节点牵下来挂到新表
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;

					// 头插到新表
					size_t hashi = hf(kot(cur->_data)) % newSize;
					cur->_next = newTable[hashi];
					newTable[hashi] = cur;

					cur = next;
				}

				_table[i] = nullptr;
			}

			_table.swap(newTable);
		}

		size_t hashi = hf(kot(data)) % _table.size();
		// 头插
		Node* newnode = new Node(data);
		newnode->_next = _table[hashi];
		_table[hashi] = newnode;
		++_n;
		return make_pair(iterator(newnode, this), true);
	}


	bool Erase(const K& key) {
		HashFunc hf;
		size_t hashi = hf(key) % _table.size();
		Node* prev = nullptr;
		Node* cur = _table[hashi];
		while (cur) {
			if (kot(cur->_data) == key) {
				if (prev == nullptr) {
					
					_table[hashi] = nullptr;
				}
				else {
					Node* next = cur->_next;
					prev->_next = next;
				}
				delete cur;
				return true;
			}
			prev = cur;
			cur = cur->_next;
		}
		--_n;
		return false;
	}


private:
	vector<Node*> _table;//定义指针数组
	size_t _n;
};
 

}

2.unordered_set.h

cpp 复制代码
#pragma once
#include"hash_bucket.h"

namespace bit {
	template<class K>
	class unordered_set {
		struct SetKeyOfT {
			const K& operator()(const K& key) {
				return key;
			}
		};

	public:
		typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;
		//重点
		typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;

		iterator begin()const {
			return _ht.begin();
		}

		iterator end()const {
			return _ht.end();
		}

		pair<iterator,bool>insert(const K& key) {
			pair< hash_bucket::HashTable<K, K, SetKeyOfT>::iterator,bool>ret= _ht.Insert(key);
			//先拿到为普通迭代器的iterator
			return pair<iterator, bool>(ret.first, ret.second);
			//用普通迭代器构造const迭代器
		}

	private:
		hash_bucket::HashTable<K, K, SetKeyOfT> _ht;
	};
	 

 }

3.unordered_map.h

cpp 复制代码
#pragma once
#include"hash_bucket.h"

namespace bit {
	template<class K,class V>
	class unordered_map {
		struct MapKeyOfT {
			const K& operator()(pair<K,V>kv) {
				return kv.first;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}

		iterator begin() {
			return _ht.begin();
		}

		iterator end() {
			return _ht.end();
		}
		const_iterator begin()const {
			return _ht. begin();
		}
		const_iterator end()const {
			return _ht.end();
		}

		V& operator[](const K& key) {
			pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
			return ret.first->second;
		}

	private:
		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
	};
 }
相关推荐
蹉跎x2 分钟前
力扣104. 二叉树的最大深度
算法·leetcode·职场和发展
gaogao_jack4 分钟前
[Leetcode小记] 3233. 统计不是特殊数字的数字数量
java·算法·leetcode
澄澈i10 分钟前
设计模式学习[9]---模板方法模式
c++·学习·设计模式·模板方法模式
沃和莱特12 分钟前
C++中类的继承
数据库·c++·编程·c·指针·友元函数
zzzhpzhpzzz18 分钟前
设计模式——解释器模式
算法·设计模式·解释器模式
一只鸡某28 分钟前
实习冲刺第二十九天
数据结构·c++·算法·leetcode
鲨鱼吃橘子41 分钟前
【C++融会贯通】哈希表的使用
c语言·开发语言·数据结构·c++·链表·哈希算法·散列表
誓约酱44 分钟前
Linux系统常用指令
linux·运维·服务器·c++
ZZZ_O^O1 小时前
【贪心算法入门第一题——860.柠檬水找零】
学习·算法·leetcode·贪心算法
Easy数模1 小时前
竞赛思享会 | 2024年第十届数维杯国际数学建模挑战赛D题【代码+演示】
python·算法·数学建模