unordered_set与unordered_map的模拟实现-哈希桶


unordered_set与unordered_map的模拟实现-哈希桶



前言

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。这里我们对unordered_map和unordered_set进行介绍和简单的模拟实现。

编译环境:vs2013


一、哈希桶改造

1 迭代器

因为unordered_set和unordered_map的底层结构是哈希桶,在实现unordered_set和unordered_map之前,我们得先对原来实现开散列基础上的哈希桶进行改造,例如典型的增加迭代器操作。
关于哈希结构的介绍和开散列的实现,可查看文章:哈希介绍及开散列哈希桶实现

//HashBucket.hpp
template<class T, class KOFT, class Ref, class Ptr, class TtoInt = TtoIntDef<T>>
class HashBucketIterator
{
public:
	typedef HashNode<T> Node;
	typedef HashBucketIterator<T, KOFT, Ref, Ptr, TtoInt> Self;
public:
	HashBucketIterator(HashBucket<T, KOFT, TtoInt>* ht = nullptr, Node* pNode = nullptr)
		:_ht(ht)
		, _pNode(pNode)
	{}
	//迭代器具有指针的行为
	//1 解引用* + 指向->(解引用的结果是指针指向的值;->的结果是下一个数据的地址) Ref(int&)对象的别名
	Ref operator*()
	{
		return _pNode->_data;
	}
	Ptr operator->()
	{
		return &(operator*());
	}
	//迭代器可进行移动
	//++a;
	Self& operator++()
	{
		Next();
		return *this;
	}
	//a++
	Self operator++(int)
	{
		Self temp(*this);
		Next();
		return temp;
	}
	//迭代器可进行比较
	bool operator==(const Self& s)const
	{
		return _pNode == s._pNode;
	}
	bool operator!=(const Self& s)const
	{
		return _pNode != s._pNode;
	}
private:
	void Next()
	{
		//_pNode不是当前链表的最后一个节点
		if (_pNode->_next)
		{
			_pNode = _pNode->_next;
		}
		else
		{
			//_pNode为当前桶的最后一个节点,找下一个非空桶的第一个节点
			//1 首先计算_pNode在哪个桶中
			size_t bucketNo = _ht->HashFunc(_pNode->_data);
			//2 从当前开始往后找非空桶
			for (size_t i = bucketNo + 1; i < _ht->BucketCount(); ++i)
			{
				if (_ht->_table[i])
				{
					_pNode = _ht->_table[i];
					return;
				}
			}
			_pNode = nullptr;
		}
	}
private:
	Node* _pNode;      //节点指针
	HashBucket<T, KOFT, TtoInt>* _ht;  //哈希桶对象指针
};

2 哈希桶模板参数

同时,同红黑树模拟map和set时,因为unordered_map中存储的是键值对<key,value>,unordered_set中存储为键值key,且两者在进行元素的比较时,都是用key值来进行比较。
unordered_set可以直接将存储元素来进行比较,而unordered_map却不可以。
所以我们在哈希桶的模板参数中增加KOFT,表示从存储元素T中将K值提取出来。

//哈希桶模板参数列表:
//1. T->哈希桶中存储元素类型
//2. KOFT->如map中将k提取出来进行元素比较
//3. TtoInt->将存储的元素转化为整型,以便哈希函数可以进行取模运算
template<class T, class KOFT,class TtoInt>
//哈希桶类中增加的迭代器
iterator Begin()
	{
		for (size_t i = 0; i < _table.capacity(); ++i)
		{
			if (_table[i])
				return iterator(this, _table[i]);
		}
		return End();
	}
	iterator End()
	{
		return iterator(this, nullptr);
	}

3 测试

//从整型int数据中将数据提取出来
struct KOFI
{
	int operator()(int data)
	{
		return data;
	}
};

void TestHashBucket1()
{
	cout << "测试1:" << endl;
	HashBucket<int, KOFI> ht; //第三个参数使用默认的哈希转换算法,即整型家族数据转换为int类型的数据
	ht.InsertUnique(1);
	ht.InsertUnique(2);
	ht.InsertUnique(3);
	ht.InsertUnique(4);
	ht.InsertUnique(11);
	ht.InsertUnique(12);
	ht.InsertUnique(13);
	ht.InsertUnique(14);
	ht.InsertUnique(44);
	cout << "元素个数为:" << ht.Size() << endl;
	ht.PrintHash();

	//HashBucketIterator<int, KOFI, int&, int*, TtoIntDef<int>> it = ht.Begin();
	auto it = ht.Begin();
	cout << "迭代器遍历哈希桶为:" << endl;
	while(it != ht.End())
	{
		cout << *it << "  ";
		++it;
	}
	cout << endl;
}

//数据转换,处理T为string类型的数据,字符串哈希算法来进行处理
class StrtoInt
{
public:
	size_t operator()(const string& s)
	{
		const char* str = s.c_str();
		unsigned int seed = 131;
		unsigned int hash = 0;
		while (*str)
		{
			hash = hash*seed + (*str++);
		}
		return (hash & 0x7FFFFFFF);
	}
};
//从string中将数据提取出来
class KOFTStr
{
public:
	const string& operator()(const string& s)const
	{
		return s;
	}
};
void TestHashBucket2()
{
	cout << "测试2:" << endl;
	HashBucket<string, KOFTStr,StrtoInt> ht;
	ht.InsertUnique("chinese");
	ht.InsertUnique("math");
	ht.InsertUnique("science");
	ht.InsertUnique("art");

	cout << "元素个数:" << ht.Size() << endl;
	ht.PrintHash();

	auto it = ht.Begin();
	cout << "迭代器遍历哈希桶为:" << endl;
	while (it != ht.End())
	{
		cout << *it << "  ";
		++it;
	}
	cout << endl;
}

在这里插入图片描述

二、unordered_map

unordered_map文档

1 unordered_map说明

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过key快速的索引到与其对应的value。
  2. 在unordered_map中,键值key通常用于惟一地标识元素value,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<key, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_map实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
  6. 它的迭代器至少是向前迭代器。

2 unordered_map模拟实现

当实现好哈希桶后,我们对unordered_map和unordered_set的实现就只是对哈希桶中的操作再进行封装调用而已。

//unordered_Map.hpp
#pragma once
#include<iostream>
#include"HashBucket.hpp"
using namespace std;
namespace map
{
	//字符串转换为整型int的结构体(作为模板参数)
	template<class K>
	class StrtoInt
	{
	public:
		size_t operator()(const K& s)
		{
			const char* str = s.c_str();
			unsigned int seed = 131;
			unsigned int hash = 0;
			while (*str)
			{
				hash = hash*seed + (*str++);
			}
			return (hash & 0x7FFFFFFF);
		}
	};
	//模板参数:
	//K->key
	//V->value
	//KtoI->将K值转换为int类型数据
	template<class K, class V, class KtoI = StrtoInt<K>>
	class unordered_map
	{
		typedef pair<K, V> DataType;
		//从<k,v>中将k提取出来
		struct KOFT
		{
			const K& operator()(const DataType& data)const
			{
				return data.first;
			}
		};
		typedef HashBucket<DataType, KOFT, KtoI> HB;   //构造一个哈希桶对象
	public:
		typename typedef HB::iterator iterator;    //哈希桶的迭代器
	public:
		unordered_map()
			:_hb()
		{}
		//unordered_map中操作///
		//begin() & end()
		iterator begin()
		{
			return _hb.Begin();
		}
		iterator end()
		{
			return _hb.End();
		}
		size_t size()const
		{
			return _hb.Size();
		}
		bool empty()const
		{
			return _hb.Empty();
		}
		V& operator[](const K& data)
		{
			return (*((_hb.InsertUnique(make_pair(data, V()))).first)).second;
			//其实就是先构造一个键值对进行键值对的插入
			//插入成功后返回一个插入成功的键值对<iterator,true>
			//然后键值对进行.first操作取出插入元素所在的迭代器iterator
			//而迭代器中存储着关键值key为data的键值对
			//再进行.second操作获取data对应的value值,即为下标运算符的结果
		}
		//find 返回查找成功元素位置的迭代器
		iterator find(const K& data)
		{
			return _hb.Find(make_pair(data, V()));
		}
		pair<iterator, bool> insert(const DataType& kv)
		{
			return _hb.InsertUnique(kv);
		}
		size_t erase(const K& data)
		{
			return _hb.EraseUnique(make_pair(data, V()));
		}
		void clear()
		{
			return _hb.Clear();
		}
		void swap(unordered_map<K, V, KtoI>& m)
		{
			_hb.Swap(m._hb);
		}
		//哈希桶个数
		size_t bucket_count()const
		{
			return _hb.BucketCount();
		}
		//某个桶中的元素个数
		size_t bucket_size(size_t bucketNo)const
		{
			return _hb.BucketSize(bucketNo);
		}
		//某个元素data所在的桶号
		size_t bucket(const K& data)
		{
			//构造键值对作为参数,使用默认的V()值
			return _hb.BucketNo(make_pair(data, V()));
		}
	private:
		HB _hb;
	};
}
//unordered_map测试函数
void Test_unordered_map()
{
	map::unordered_map<string, string> m;
	m.insert(make_pair("Chinese", "汉语"));
	m.insert(make_pair("English", "英语"));
	m.insert(make_pair("Japanese", "日语"));
	m.insert(make_pair("Korean", "韩语"));
	m.insert(make_pair("Russian", "俄语"));

	cout << "1 元素个数为:" << m.size() << endl;
	cout <<"2 Chinese对应语言为:"<< m["Chinese"] << endl;
	cout << "3 桶的个数为:" << m.bucket_count() << endl;
	cout << "4 0号桶中元素个数为:" << m.bucket_size(0) << endl;
	cout << "5 Japanese所在的桶号为:" << m.bucket("Japanese") << endl;

	auto it = m.begin();
	cout << "6 迭代器遍历哈希桶:" << endl;
	while (it != m.end())
	{
		cout << it->first << "--->" << it->second << endl;
		++it;
	}
	cout << endl;
	if (m.find("Japanese")!=nullptr)
	{
		cout << "Japanese is in the unordered_map." << endl;
	}
	else
	{
		cout << "Japanese is not in the unordered_map.";
	}
	m.erase("Japanese");
	cout << "从表中将Japanese删除后再进行查找:" << endl;
	if (m.find("Japanese")!=nullptr)
	{
		cout << "Japanese is in the unordered_map." << endl;
	}
	else
	{
		cout << "Japanese is not in the unordered_map.";
	}
	m.clear();
}

在这里插入图片描述

三、unordered_set

unordered_set文档

1 unordered_set说明

  1. unordered_set是以不按特定顺序存储唯一元素的关联式容器,并允许根据其值快速检索单个元素。
  2. 在unordered_set中,元素的值同时也是其键,唯一标识它。键是不可变的,因此,unordered_set中的元素不能在容器中修改一次 ,但是可以插入和删除它们。
  3. 在内部,unordered_set中的元素不按任何特定顺序排序,而是根据其哈希值组织到存储桶中,以允许直接按其值快速访问单个元素(平均时间复杂度恒定)。
  4. unordered_set容器比set容器更快地通过其键访问单个元素,尽管它们通常不太有效地通过其元素的子集进行范围迭代。
  5. 它的迭代器至少是向前迭代器。

2 unordered_set模拟实现

unordered_map完成的情况下,两容器只是在存储元素上面不同而已,其余相关操作皆如出一辙。
注意:unordered_set中并没有下标运算符操作。

//unordered_Set.hpp
#pragma once
#include<iostream>
#include"HashBucket.hpp"
using namespace std;
namespace set
{
	//字符串转换为整型int的结构体(作为模板参数)
	template<class K>
	class StrtoInt
	{
	public:
		size_t operator()(const K& s)
		{
			const char* str = s.c_str();
			unsigned int seed = 131;
			unsigned int hash = 0;
			while (*str)
			{
				hash = hash*seed + (*str++);
			}
			return (hash & 0x7FFFFFFF);
		}
	};
	//模板参数:
	//K->key
	//KtoI->将K值转换为int类型数据
	template<class K, class KtoI = StrtoInt<K>>
	class unordered_set
	{
		typedef K DataType;
		//从<k,v>中将k提取出来
		struct KOFT
		{
			const K& operator()(const DataType& data)const
			{
				return data;
			}
		};
		typedef HashBucket<DataType, KOFT, KtoI> HB;   //构造一个哈希桶对象
	public:
		typename typedef HB::iterator iterator;    //哈希桶的迭代器
	public:
		unordered_set()
			:_hb()
		{}
		iterator begin()
		{
			return _hb.Begin();
		}
		iterator end()
		{
			return _hb.End();
		}
		size_t size()const
		{
			return _hb.Size();
		}
		bool empty()const
		{
			return _hb.Empty();
		}
		//find 返回查找成功元素位置的迭代器
		iterator find(const K& data)
		{
			return _hb.Find(data);
		}
		pair<iterator, bool> insert(const DataType& k)
		{
			return _hb.InsertUnique(k);
		}
		size_t erase(const K& data)
		{
			return _hb.EraseUnique(data);
		}
		void clear()
		{
			return _hb.Clear();
		}
		void swap(unordered_set<K, KtoI>& s)
		{
			_hb.Swap(s._hb);
		}
		//哈希桶个数
		size_t bucket_count()const
		{
			return _hb.BucketCount();
		}
		//某个桶中的元素个数
		size_t bucket_size(size_t bucketNo)const
		{
			return _hb.BucketSize(bucketNo);
		}
		//某个元素data所在的桶号
		size_t bucket(const K& data)
		{
			//构造键值对作为参数,使用默认的V()值
			return _hb.BucketNo(data);
		}
	private:
		HB _hb;
	};
}
//unordered_set测试函数
void Test_unordered_set()
{
	set::unordered_set<string> s;
	s.insert("Chinese");
	s.insert("English");
	s.insert("Japanese");
	s.insert("Korean");
	s.insert("Russian");

	cout << "1 元素个数为:" << s.size() << endl;
	cout << "2 桶的个数为:" << s.bucket_count() << endl;
	cout << "3 0号桶中元素个数为:" << s.bucket_size(0) << endl;
	cout << "4 Japanese所在的桶号为:" << s.bucket("Japanese") << endl;

	auto it = s.begin();
	cout << "5 迭代器遍历哈希桶:" << endl;
	while (it != s.end())
	{
		cout << *it<< endl;
		++it;
	}
	cout << endl;
	if (s.find("Japanese") != nullptr)
	{
		cout << "Japanese is in the unordered_map." << endl;
	}
	else
	{
		cout << "Japanese is not in the unordered_map.";
	}
	s.erase("Japanese");
	cout << "从表中将Japanese删除后再进行查找:" << endl;
	if (s.find("Japanese") != nullptr)
	{
		cout << "Japanese is in the unordered_map." << endl;
	}
	else
	{
		cout << "Japanese is not in the unordered_map.";
	}
	s.clear();
}

在这里插入图片描述


四、总结

unordered_map与unordered_set的模拟实现实际就是对底层结构哈希桶的相应接口再封装过程,其相应的操作都是在底层结构哈希桶中去进行。
相较于以红黑树为底层结构的map、multimap、set、multiset来说,以哈希桶为底层结构的unordered_map、unordered_multimap、unordered_set、unordered_multiset关联式容器,其查找元素的时间效率是非常高的,但是其空间的消耗也是巨大的,其是以空间来换取时间。
哈希扩展应用在那种处理海量数据的情况中是十分有用的,比如位图,布隆过滤器等。
相关详细实现细节内容可进入仓库查看:
unordered_map与unordered_set的模拟实现–基于哈希桶

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值