【C++进阶】用哈希实现unordered_set和unordered_map的模拟

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

主厨的主页:Chef‘s blog  

所属专栏:c++大冒险
 

总有光环在陨落,总有新星在闪烁


前言:

        之前我们学完红黑树后对他进行了改造,使之成为map和set的底层容器,今天我们则要把哈希表进行修改并以此为基础实现unordered_set和unordered_map

一.哈希桶(修改版)

1.1.节点

template<class T>
struct HashNode
{
	HashNode<T> _next;
	T _data;
	HashNode(const T& data = T())
		:_next(nullptr)
		, _data(data)
	{}
};

注意事项:

  • 数据类型是T,因为要同时适用unordered_set(存储键值)和unordered_map(存储键值对

类比咱们用红黑树写map和set时的做法。

1.2.迭代器

哈希表的迭代器类型是单向迭代器,没有反向迭代器

1.2.1成员变量

template<class K, class T, class KeyOfT, class HF>
class HashTable;
template<class K,class T, class Ref,class Ptr,class KeyOfT, class HF>
struct HashIterator
{
	typedef HashNode<T> Node;
	typedef HashIterator<K, T, Ref, Ptr, KeyOfT, HF> self;
	typedef HashIterator<K, T, T&, T*, KeyOfT, HF> iterator;
	typedef HashTable<K, T, KeyOfT, HF> HT;
	Node* _node;
	HT* _ht;
};

注意事项:

  •  增加了_ht成员变量,因为在重载++时,当一条单链表走到空,则需要走到下一个哈希桶的位置,需要通过哈希表的vector成员找下一个位置 
  • 因为HashTable是在后面实现的,所以我们要先写一个声明

1.2.2简单函数实现

HashIterator(Node*&node=nullptr,Node*&ht=nullptr)
	:_node(node)
	,_ht(ht)
{}
HashIterator(const iterator&it)
	:_node(it._node)
	, _ht(it._ht)
{}
Ref& operator*()const
{
	return _node->_data;
}
Ptr& operator->()const
{
	return &(_node->_data);
}
bool operator==(const self&se)
{
	return _node == se._node;
}
bool operator!=(const self& se)
{
	return _node != se._node;
}

注意事项:

  • 拷贝构造函数可以以普通迭代器拷贝出普通迭代器(普通迭代器调用时)以及const迭代器(const迭代器调用时)

1.2.3operator++

self& operator++()
{
	Node* node = _node;
	if (node->_next)
	{
		_node = node->_next;
		return *this;
	}
	else
	{
		KeyOfT kot;
		size_t hash = HF(kot(_node->_data)) % _ht->_tables.size()+1;
		for (hash; hash < _ht->_tables.size(); hash++)
		{
			if (_ht->tables[hash])
			{
				_node = _ht->tables[hash];
				return *this;
			}

		}
		_node = nullptr;
		return *this;
	}
}
self operator++(int)
{
	self new_self=*this;
	(*this)++;
	return new_self;
}

思路:

  1. 前置++的思路: 
    1. 下一个结点不为空,则跳到下一位
    2. 下一个结点为空,则先取模算出哈希地址,再往后探测不为空的哈希桶 
  2. 后置++:复用前置++,返回临时对象

1.3哈希桶本身

1.3.1成员变量

template<class K, class T, class KeyOfT, class HF>
class HashTable
{
	template<class K, class T, class Ref, class Ptr, class KeyOfT, class HF>
	friend struct HashIterator;
	typedef HashNode<T> Node;
public:
protected:
	vector<Node*> _tables;
	size_t _size = 0;//有效数据个数
};

注意事项:

  1. 迭代器要访问哈希桶的私有成员,所以要声明友元
  2. 模板参数KeyOfT是为了从类型T中取出键值
  3. 模板参数HF即是HashFunc,哈希函数,用于辅助键值转化为整型进行取模操作

1.3.2构造函数和析构函数

HashTable(size_t size = 10)
{
	_tables.resize(size);
}
~HashTable()
{
	for (auto hash_node : _tables)
	{
		while (hash_node)
		{
			Node* new_node = hash_node->_next;
			delete hash_node;
			hash_node = new_node;
		}
	}
}

1.3.3 begin和end 

typedef HashIterator< K,T, T&, T*,  KeyOfT,  HF> iterator;
typedef HashIterator< K, T, const T&, const T*,  KeyOfT,  HF> const_iterator;
iterator begin()
{
	for (size_t i = 0; i < _tables.size(); i++)
		if (_tables[i])
			return iterator(_tables[i], this);
	return iterator(nullptr, this);
}
const_iterator begin()const
{
	for (size_t i = 0; i < _tables.size(); i++)
		if (_tables[i])
			return const_iterator(_tables[i], this);
	return const_iterator(nullptr, this);
}
iterator end()
{
	return iterator(nullptr, this);
}
const_iterator end()
{
	return const_iterator(nullptr, this);
}

注意事项:

  1. begin返回最开始不为空的哈希桶的迭代器,而不是最开始的哈希桶的迭代器
  2. end返回空迭代器
  3. 构造迭代器需要传入哈希表本身的地址,这里直接传this指针即可

1.3.4Find函数 

iterator Find(const K&key)
{
	HF hf;
	KeyOfT kot;
	size_t hash = hf(key) % _tables.size();
	Node* cur = _tables[hash];
	while (cur)
	{
		if (kot(cur->_data) == key)
			return iterator(cur,_tables);
		cur = cur->_next;
	}
	return iterator(nullptr,_tables);
}

注意事项:

  1. 返回值类型是迭代器
  2. 运用两个仿函数,,kot负责获取存储的键值,hf作为哈希函数把键值key转整型

1.3.5   Insert函数 

	pair<iterator,bool> Insert(T& data)
{
	KeyOfT kot;
	HF hf;
	iterator it = Find(kot(data));
	if (it._node)
	{
		return make_pair(it, false);
	}
	if (_size == _tables.size())
	{
		vector<Node*> new_tables(_size * 2);
		for (auto node : _tables)
		{
			while (node)
			{
				Node* next = node->_next;
				size_t hash = hf(kot(node->_data)) % new_tables.size();
				node->_next = new_tables[hash];
				new_tables[hash] = node;
				node = next;
			}
		}
		_tables.swap(new_tables);
	}
	
	size_t hash = hf(kot(data)) % _tables.size();
	Node* cur = _tables[hash];
	Node* p(data);
	p->_next = cur;
	_tables[hash] = p;
	_size++;
	return make_pair(iterator(p,this),true);
}

注意事项:

  1. 返回值类型是pair,第一个参数为迭代器,第二个参数为布尔值(记录是否插入成功)
  2. 运用两个仿函数,,kot负责获取存储的键值,hf作为哈希函数把键值key转整型

1.3.6Erase函数 

	bool Erase(const K& key)
{
	HF hf;
	KeyOfT kot;
	size_t hash = hf(key) % _tables.size();
	Node* cur = _tables[hash];
	Node* pre = nullptr;
	while (cur)
	{
		if (kot(cur->data) == key)
			break;
		pre = cur;
		cur = cur->_next;
	}
	if (cur == nullptr)
		return false;
	_size--;
	if (pre == nullptr)
		_tables[hash] = cur->_next;
	else
		pre->_next = cur->_next;
	delete cur;
	return true;
}

二、unordered_set

2.1成员变量及仿函数 

template<class K,class HF=HashFunc<K>>
class unordered_set
{
public:
	struct SetKeyOfT
	{
		K& operator()(const K& key)
		{
			return key;
		}
	};
protected:
	HashTable<K, K, SetKeyOfT, HF> _hf;
};

注意事项:

  •  1.这里的数据存储类型就是键值Key本身,所以SetKeyOfT直接返回key就行
  • 2.哈希函数不是固定的,可以根据需求自己进行实现并传到给定模板参数中

2.2 begin和end 

typedef typename HashTable<K, K, SetKeyOfT, HF>::iterator iterator;
typedef typename HashTable<K, K, SetKeyOfT, HF>::const_iterator const_iterator;
iterator begin()
{
	return _ht.begin();
}
const_iterator begin()const
{
	return _ht.begin();
}
iterator end()
{
	return _ht.end();
}
const_iterator end()const
{
	return _ht.end();
}

注意事项:

  • 在C++中,编译器默认iterator为变量名,如果作为类型名,需要在前面加上typename加上typename关键字。
  • unordered_set中存储的键值key不允许修改,所以其普通迭代器和const迭代器均为哈希表的const迭代器
  • 我们称set的begin为A,哈希的begin为B,A的实现是对B的调用,在A的参数是普通迭代器时,B也是普通迭代器,B的返回值也是普通迭代器,但A的返回值是const迭代器,所以这里需要用普通迭代器创建一个const迭代器的临时变量,这就用到之前写的拷贝构造函数了。

2.3Find 

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==
iterator Find(const K&key)
{
	return _ht.Find(key);
}

注意事项: 

  •  此时也有从普通迭代器到const迭代器的转换 

2.4Insert

pair<iterator, bool> Insert(const K& key)
{
	return _ht.Insert(key);
}

注意事项: 

  1. 函数形参类型是K
  2. 此时也有从普通迭代器到const迭代器的转换

 2.5Erase

bool Erase(const K& key)
{
	return _ht.Erase(key);
}

三、unordered_map

unordered_map和unordered_set的实现有众多相似之处,

3.1 成员变量与仿函数

template<class K,class V,class HF=HashFunc<K>>
class unordered_map
{
	struct MapKeyOfT
{
	const K& operator()(const pair<K, V>&kv)
	{
		return kv.first;
	}
	};
protected:
	HashTable<K, pair<const K, V>, MapKeyOfT, HF> _ht;
};

注意事项:

  • 1.这里节点存的数据类型是pair<K,V>,我们的MapKeyOfT作用是返回其中的键值key 
  • 2.哈希函数不是固定的,可以根据需求自己进行实现并传到给定模板参数中

3.2 begin和end

typedef typename HashTable<K, pair<K,V>, MapKeyOfT, HF>::iterator iterator;
typedef typename HashTable<K, pair<K,V>, MapKeyOfT, HF>::const_iterator const_iterator;
iterator begin()
{
	return _ht.begin();
}
const_iterator begin()const
{
	return _ht.begin();
}
iterator end()
{
	return _ht.end();
}
const_iterator end()const
{
	return _ht.end();
}

注意事项:

  1. 加上typename关键字,编译器才能识别类型
  2. unordered_map不允许修改key,故普通迭代器和const的pair中的K都要加上const修饰,但是允许修改存储的data,所以普通和const迭代器一一对应(好好想想这点map和set的处理差异)

3.3 find

iterator Find(const K& key)
{
	return _ht.Find(key);
}

3.4Insert

	pair<iterator, bool> Insert(const pair<const K, V>& kv)
	{
		return _ht.Insert(kv);
	}

注意事项:

  • 形参类型是键值对而不是键值

3.5 operator[ ]

V& operator[](const& K key)
{
	return (*(_ht.Insert(make_pair(key, V()))).first).second;
}
//或者你也可以这么写
V& operator[](const K& key)
{
	pair<iterator, bool> cur = insert(make_pair(key, V()));
	return cur.first->second;
}

注意事项:

  1.  利用operator[]进行插入和修改操作是很方便的,所以要学会灵活运用哦 
  2. 返回值是value的引用,我们可以直接进行修改 

3.6 Erase 

bool Erase(const K& key)
{
	return _ht.Erase(key);
}

 


 如果你对该系列文章有兴趣的话不妨点个关注哦,我会持续更新相关博客的

🥰创作不易,你的支持对我最大的鼓励🥰

🪐~ 点赞收藏+关注 ~🪐


 

  • 35
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值