从哈希桶角度看 unordered_map 与 unordered_set 的实现

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

一、引言

哈希函数与哈希桶是计算机科学中用于实现高效数据检索的重要工具。在之前的博客中,我们已经详细探讨了哈希的基本概念、哈希函数的构造方法以及哈希桶的工作原理等内容。在本篇博客中,我们将进一步深入探索C++中的unordered系列数据结构,并利用之前介绍的哈希桶原理进行模拟实现。

C++11提供的unordered系列数据结构,如unordered_mapunordered_set等,是STL(Standard Template Library)中提供的一组非常重要的容器。它们以哈希表为基础,通过哈希函数将键映射到存储位置,从而实现了快速的插入、删除和查找操作。这些数据结构在处理大规模数据时,能够展现出比有序容器(如map、set)更高的性能。在C++11中,提供了的4个unordered系列的关联式容器。这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。

本文将使用哈希桶对C++ unordered系列数据结构进行模拟实现。文本前置内容,请点击此处: 哈希技术解析:从哈希函数到哈希桶迭代器的全面指南


二、C++ unordered系列的无序关联式容器概览

1. unordered_map与unordered_multimap简介

数据结构特性:

  • unordered_map:C++ STL中的一个无序关联容器,它存储的元素是键值对,并且每个键在容器中唯一。其内部实现通常基于哈希表,通过哈希函数将键映射到存储位置,从而提供了常数时间复杂度的插入、删除和查找操作。
  • unordered_multimap:与unordered_map类似,但它允许键在容器中出现多次,即可以存储多个具有相同键的键值对。

应用场景:

  • unordered_map:当需要快速根据键查找对应的值时,或者当键的唯一性很重要时,unordered_map是一个很好的选择。例如,在缓存系统、词频统计等场景中,unordered_map可以高效地存储和检索键值对。
  • unordered_multimap:当需要存储多个具有相同键的键值对时,可以使用unordered_multimap。这在某些特定的应用场景中很有用,比如记录一个单词在文本中出现的所有位置。

2. unordered_set与unordered_multiset简介

数据结构特性:

  • unordered_set:是一个无序集合,它存储的元素是唯一的,不包含重复的元素。其内部实现也基于哈希表,通过哈希函数将元素映射到存储位置,从而实现了常数时间复杂度的插入、删除和查找操作。
  • unordered_multiset:与unordered_set类似,但它允许集合中包含重复的元素。

应用场景:

  • unordered_set:当需要快速检查一个元素是否存在于集合中,或者需要维护一个不包含重复元素的集合时,unordered_set是一个合适的选择。例如,在算法中去除重复元素、实现并集和交集运算等场景,unordered_set都能提供高效的解决方案。
  • unordered_multiset:当需要统计元素的出现次数或者需要维护一个包含重复元素的集合时,可以使用unordered_multiset。这在某些特定的数据处理和分析任务中可能会很有用。

总的来说,C++的unordered系列数据结构提供了高效的无序存储和检索机制,适用于各种需要快速处理大量数据的场景。通过合理地选择和使用这些数据结构,可以显著提高程序的性能和效率。

非unordered系列数据结构点击此处:深入解析C++树形关联式容器:map、set及其衍生容器的使用与原理-CSDN博客


三、基于哈希桶的C++ unordered系列数据结构模拟实现

1、unordered_map的模拟实现

  • 使用自定义哈希桶存储键值对

    unordered_map的简化模板类定义(注:hash_bucket是我实现的哈希桶所在的命名空间) :

    template<class K, class V, class Hash = HashFunc<K>>
    class unordered_map
    {
    	struct MapKeyOfT {
            const K& operator()(const pair<K, V>& kv) { return kv.first; }
        };
    public:
    		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
        
        // .... 
    private:
    		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
    };
    

    模板参数K:键(Key)的类型。V:值(Value)的类型。Hash:哈希函数(Hash Function)的类型,默认为HashFunc<K>,即默认以K来计算哈希位置。该参数也在哈希桶部分有介绍。

    内部结构体 MapKeyOfT:这个结构体定义了一个调用运算符,用于从pair<K, V>中提取键。这是哈希表内部可能需要的,以便能够根据键来定位存储的元素。

    迭代器类型定义typedef语句定义了一个迭代器类型,表示指向hash_bucket::HashTable中元素的迭代器。这个迭代器类型用于公开unordered_map的接口,以便用户可以遍历集合中的元素。

    私有成员_htunordered_map的私有成员,其类型为hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> 。这个哈希表用于存储键值对,并根据键的哈希值来定位元素。

  • 实现插入、查找、删除等基本操作

    operator[]用于访问或插入一个键值对。如果键k已经存在于unordered_map中,则该函数返回该键对应的值的引用;如果键k不存在,则该函数插入一个新的键值对(键为k,值为V()的默认构造实例),并返回新插入值的引用:

    V& operator[](const K& k) {
        pair<iterator, bool> it = insert({ k,V() });
        return (*it.first).second;
        //return (*((this->insert(make_pair(k, V()))).first)).second;
    }
    

    在这段代码中,insert成员函数被调用,它尝试插入一个键值对到unordered_map中。insert返回一个pair<iterator, bool>,其中迭代器指向新插入的元素(或已存在的元素),布尔值表示是否实际插入了新元素。由于unordered_map不允许重复的键,所以对于operator[]来说,这个布尔值总是true,除非在插入过程中发生了异常。然后,通过解引用迭代器it.first来获取键值对的引用,并返回其second成员(即值)的引用。

    pair<iterator,bool> insert(const pair<K, V>& kv) { return _ht.Insert(kv); }  
    bool erase(const K& k) { return _ht.Erase(k); }  
    iterator find(const K& k) { return _ht.Find(k); }
    
    • insert函数接收一个pair<K, V>类型的参数,并调用_ht.Insert方法尝试将其插入哈希表中。它返回一个pair<iterator, bool>,其中迭代器指向新插入的元素(或已存在的元素),布尔值表示是否成功插入了新元素。
    • erase函数接收一个键类型的参数,并调用_ht.Erase方法尝试从哈希表中删除具有该键的键值对。它返回一个布尔值,表示删除操作是否成功。
    • find函数接收一个键类型的参数,并调用_ht.Find方法查找具有该键的键值对。如果找到,它返回一个指向该键值对的迭代器;否则,返回end()迭代器。
  • 封装哈希桶迭代器

    我们的哈希桶已实现了绝大部分的功能,因此我们此处仅仅调用其函数即可。

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

2、unordered_set的模拟实现

  • 利用哈希桶存储唯一元素

    unordered_set的简化模板类定义(注:hash_bucket是我实现的哈希桶所在的命名空间) :

    template<class K, class Hash = HashFunc<K>>
    class unordered_set{
        struct SetKeyOfT
        {
            const K& operator()(const K& key) { return key; }
        };
    public:
        typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::iterator iterator;
    	//...
    private:
        hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
    };
    

    模板参数unordered_set模板接受两个类型参数,KHashK是集合中元素的键类型,Hash是哈希函数类型,用于计算键的哈希值。默认情况下,如果没有提供Hash,它将使用HashFunc<K>作为哈希函数。

    内部结构体SetKeyOfT:这是一个简单的函数对象(或称为仿函数),它重载了operator()以返回其输入的引用。在unordered_set的上下文中,它用于从键中提取键本身(在这种情况下,键就是元素本身)。这是为了与hash_bucket::HashTable的接口保持一致,该接口可能期望一个可以从某种类型中提取键的函数对象。

    迭代器类型定义:使用typedef语句定义了一个名为iterator的类型别名,它表示指向hash_bucket::HashTable中元素的迭代器。这个迭代器类型用于公开unordered_set的接口,以便用户可以遍历集合中的元素。

    私有成员变量_htunordered_set的一个私有成员变量,其类型为hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>。这表示它是一个哈希表,用于存储unordered_set中的元素。键类型是const K,因为集合中的元素不应被修改(在unordered_set中,元素是唯一的,并且一旦插入就不能被修改)。SetKeyOfT用于从键中提取键,而Hash是用于计算哈希值的函数。

  • 实现集合的基本操作

    pair<iterator,bool> insert(const K& k) { return _ht.Insert(k); }
    bool erase(const K& k) { return _ht.Erase(k); }
    iterator find(const K& k) { return _ht.Find(k); }
    
    • insert函数尝试在集合中插入一个元素,并返回一个pair<iterator, bool>,其中迭代器指向新插入的元素(或已存在的元素),布尔值表示是否实际插入了新元素。由于unordered_set不允许重复元素,所以如果尝试插入一个已经存在的元素,该函数不会插入新元素,而是返回指向已存在元素的迭代器,并将布尔值设置为false
  • 封装哈希桶迭代器

    此处与unordered_map相同。

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

3、哈希桶及其迭代器实现的代码

该文已详细叙述哈希桶相关内容 ->深入解析C++树形关联式容器:map、set及其衍生容器的使用与原理-CSDN博客

template<class K>
struct HashFunc {
	size_t operator()(const K& key) { return (size_t)key; }
};
template<>
struct HashFunc<string> {
	size_t operator()(const string& s) {
		size_t hashi = 0;
		for (auto& e : s) {
			hashi += e;
			hashi *= 31;
		}
		return hashi;
	}
};
namespace hash_bucket
{
	template<class T>
	struct HashNode {
		HashNode<T>* _next;
		T _data;
		HashNode(const T& data) :_next(nullptr), _data(data) {}
	};

	template<class K, class T, class KeyOfT, class Hash >
	class HashTable;


	template<class K, class T, class KeyOfT, class Hash>
	struct  __HTIterator {
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		typedef __HTIterator<K, T, KeyOfT, Hash> Self;
		Node* _node;
		HT* _ht;
		__HTIterator(Node* node, HT* ht) :_node(node), _ht(ht) {}

		T& operator*() { return _node->_data; }
		T* operator->() { return &_node->_data; }
		bool operator!=(const Self& s)const { return _node != s._node; }
		bool operator==(const Self& s) const { return _node == s._node; }

		Self& operator++() {
			if (_node->_next) {
				_node = _node->_next;
			}
			else {
				KeyOfT kot;
				Hash hs;
				size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
				hashi++;
				while (hashi < _ht->_tables.size()) {
					if (_ht->_tables[hashi]) {
						_node = _ht->_tables[hashi];
						break;
					}

					hashi++;
				}
				if (hashi == _ht->_tables.size()) {
					_node = nullptr;
				}
			}
			return *this;
		}
	};


	template<class K, class T, class KeyOfT, class Hash >
	class HashTable {
		typedef HashNode<T> Node;

		template<class K, class T, class KeyOfT, class Hash>
		friend struct __HTIterator;

	public:
		typedef __HTIterator<K, T, KeyOfT, Hash> iterator;
		iterator begin(){
			for (size_t i = 0; i < _tables.size(); i++)
				if (_tables[i])
					return iterator(_tables[i], this);

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

		HashTable()
		//:kot(KeyOfT()),hs(Hash())
		{
			_tables.resize(10, nullptr);
			_n = 0;
			kot = KeyOfT();
			hs = Hash();
		} 
		~HashTable() {
			for (size_t i = 0; i < _tables.size(); i++) {
				Node* cur = _tables[i];
				while (cur) {
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}
		pair<iterator,bool> Insert(const T& data) {
	
			iterator it = Find(kot(data));
			if (it != end())
				return { it,false};

			
			if (_n == _tables.size()) {
				vector<Node*> newTables(_tables.size() * 2, nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur) {
						Node* next = cur->_next;
						size_t hashi = hs(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}
			size_t hashi = hs(kot(data)) % _tables.size();
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			return { iterator(newnode, this),true };
		}
		bool Erase(const K& key) {
			size_t hashi = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];

			while (cur) {
				if (kot(cur->_data) == key) {
					// 删除
					if (prev)
						prev->_next = cur->_next;
					else
						_tables[hashi] = cur->_next;

					delete cur;

					--_n;
					return true;
				}

				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
		iterator Find(const K& key) {
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur) {
				if (kot(cur->_data) == key)
					return iterator(cur, this);

				cur = cur->_next;
			}
			return iterator(nullptr, this);
		}
	private:
		vector<Node*> _tables;
		size_t _n;
		KeyOfT kot; 
		Hash hs;
	};
}

四、扩展与应用

1. 自定义哈希函数

在C++中,当使用std::unordered_setstd::unordered_map等无序容器时,哈希函数起着至关重要的作用。默认的哈希函数对于许多类型都工作得很好,但有时对于自定义类型或特殊需求,默认的哈希函数可能不是最优的,甚至可能导致性能下降或哈希冲突过多。

为特定类型设计哈希函数

对于自定义类型,需要提供一个哈希函数,该函数接受自定义类型的对象作为参数,并返回一个足够大的整数值。设计哈希函数时,需要确保:

  • 不同的对象尽可能映射到不同的哈希值。
  • 相同的对象总是映射到相同的哈希值。
  • 哈希函数的计算应该尽可能快。

例如,对于一个包含字符串和整数的自定义类型,可以使用字符串的哈希值和整数的哈希值的组合作为整体的哈希值。

2. 其他unordered数据结构

除了unordered_setunordered_map之外,标准库还提供了unordered_multimapunordered_multiset。这两个数据结构分别允许存储具有相同键的多个值对和多个值。

unordered_multimap与unordered_map

unordered_multixxxunordered_xxx的主要区别在于前者允许键重复,而后者不允许。具体来说:

  • 键的重复性unordered_map中的每个键都是唯一的,每个键只能映射到一个值。而unordered_multimap允许键的重复,这意味着同一个键可以映射到多个值。
  • 使用场景:当你需要存储键值对,并且每个键只对应一个值时,unordered_map是合适的选择。而如果你需要存储的键值对中有多个键是相同的,并且每个键对应多个值,那么unordered_multimap更为合适。
  • 内部实现:两者都使用哈希表作为底层数据结构,以实现快速的插入、删除和查找操作。但由于unordered_multimap允许键重复,因此在处理冲突和存储键值对时可能需要更复杂的逻辑。

unordered_multiset与unordered_set

  • 元素的重复性unordered_set中的每个元素都是唯一的,不允许有重复元素。而unordered_multiset则允许元素重复,即集合中可以包含多个相同的元素。
  • 使用场景:当你需要存储一个不包含重复元素的集合时,unordered_set是合适的选择。而如果你需要存储的集合中可能包含重复的元素,那么unordered_multiset更为合适。
  • 内部实现:两者都使用哈希表作为底层数据结构,以实现快速的插入、删除和查找操作。但由于unordered_multiset允许元素重复,因此在处理冲突和存储元素时可能需要更复杂的逻辑。

在实际应用中,根据具体的需求和数据特性选择合适的数据结构是非常重要的。例如,在需要统计词频的场景中,由于一个单词可能在文本中出现多次,因此使用unordered_multiset来存储单词和它们的出现次数会更合适。而在某些需要唯一标识的场景中,如用户ID的存储,使用unordered_set来确保ID的唯一性则更为合适。

3. 实际应用案例分析

unordered系列数据结构在实际项目中有着广泛的应用,特别是在需要快速查找和插入的场景中。

案例一:词频统计

在处理大量文本数据时,词频统计是一个常见的任务。可以使用unordered_map来存储每个单词及其出现的次数。由于哈希表提供了平均常数时间的查找和插入操作,因此这种方法在处理大规模文本时非常高效。

案例二:缓存系统

在缓存系统中,通常需要快速查找和插入键值对。unordered_mapunordered_set可以用作缓存的底层数据结构,提供快速的访问速度。当缓存达到最大容量时,还可以使用这些数据结构来高效地执行替换策略(如LRU缓存替换)。

本文完整代码: unordered_map与unordered_set · 比奇堡的Zyb/每日学习 - 码云 - 开源中国 (gitee.com)文章来源地址https://www.toymoban.com/news/detail-842489.html

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

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

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

相关文章

  • C++进阶--使用哈希表实现unordered_map和unordered_set的原理与实例

    C++进阶--使用哈希表实现unordered_map和unordered_set的原理与实例

    本文将介绍如何使用哈希表来实现C++ STL库中的unordered_map和unordered_set容器。我们将会解释哈希表的基本原理,并给出具体的代码示例,帮助读者更好地理解和应用哈希表。 哈希原理讲解–链接入口 set和map的实现的文章,与unordered_map实现类似 unordered_set是一种集合存储的容器

    2024年04月09日
    浏览(14)
  • 改造哈希表,封装unordered_map和unordered_set

    改造哈希表,封装unordered_map和unordered_set

    正文开始前给大家推荐个网站,前些天发现了一个巨牛的 人工智能 学习网站, 通俗易懂,风趣幽默 ,忍不住分享一下给大家。点击跳转到网站。 unordered_map是存的是pair是K,V型的,而unordered_set是K型的,里面只存一个值,那我们如何利用一个数据结构将他们都封装出来呢?

    2024年02月03日
    浏览(13)
  • 【C++学习】哈希表的底层实现及其在unordered_set与unordered_map中的封装

    【C++学习】哈希表的底层实现及其在unordered_set与unordered_map中的封装

    🎉个人名片: 🐼作者简介:一名乐于分享在学习道路上收获的大二在校生 🙈个人主页🎉:GOTXX 🐼个人WeChat:ILXOXVJE 🐼本文由GOTXX原创,首发CSDN🎉🎉🎉 🐵系列专栏:零基础学习C语言----- 数据结构的学习之路----C++的学习之路 🐓每日一句:如果没有特别幸运,那就请特

    2024年04月16日
    浏览(23)
  • C++ 哈希+unordered_map+unordered_set+位图+布隆过滤器(深度剖析)

    C++ 哈希+unordered_map+unordered_set+位图+布隆过滤器(深度剖析)

    想象一下,你有一个巨大的图书馆,里面摆满了成千上万本书。如果你想要找到其中一本特定的书,你会怎么做?如果你按照书的编号来有序地排列它们,找到特定的书本可能会很容易。但是,如果书籍是随机地摆放,你可能需要逐本地查找,这个过程会非常耗时。 而哈希函

    2024年02月21日
    浏览(14)
  • C++利用开散列哈希表封装unordered_set,unordered_map

    C++利用开散列哈希表封装unordered_set,unordered_map

    1.之前我们已经实现了开散列的哈希表,今天我们来用它封装unordered_set,unordered_map 2.本文的封装比利用红黑树封装set和map更加复杂 建议大家先去看我的红黑树封装set和map再来看本文 因为有很多地方跟红黑树封装set和map时是同样的思路和方法,所以本文不会太去赘述一遍 因为un

    2024年03月24日
    浏览(12)
  • 【C++】如何用一个哈希表同时封装出unordered_set与unordered_map

    【C++】如何用一个哈希表同时封装出unordered_set与unordered_map

    👀 樊梓慕: 个人主页  🎥 个人专栏: 《C语言》 《数据结构》 《蓝桥杯试题》 《LeetCode刷题笔记》 《实训项目》 《C++》 《Linux》 《算法》 🌝 每一个不曾起舞的日子,都是对生命的辜负 目录 前言 1.哈希桶源码  2.哈希表模板参数的控制 3.字符串作为键值如何映射哈希值

    2024年03月26日
    浏览(12)
  • 【C++】STL——用一个哈希表封装出unordered_map和unordered_set

    【C++】STL——用一个哈希表封装出unordered_map和unordered_set

    根据先前对unordered_map和unordered_set的学习,我们得知其底层是借助哈希表来实现的,接下来我们会使用上篇博客实现的开散列哈希表来模拟实现unordered_map和unordered_set,哈希表源代码链接: Hash/Hash/HashBucket.h · wei/cplusplus - 码云 - 开源中国 (gitee.com) 下面我们对哈希表进行改造,

    2023年04月18日
    浏览(16)
  • C++STL详解(十) -- 使用哈希表封装unordered_set和unordered_map

    C++STL详解(十) -- 使用哈希表封装unordered_set和unordered_map

    因为不同容器的类型不同,如果是unordered_map,V代表一个键值对,如果unordered_set,V代表Key值,而底层哈希表并不知道上层容器所要传递的V模板参数类型,为了与哈希表的模板参数区分,我们将哈希表中的V模板参数改为T. 例如: 在哈希表中当我们使用Find函数进行查找时: 如果上层传递的

    2024年02月01日
    浏览(18)
  • 【C++入门到精通】哈希 (STL) _ unordered_map _ unordered_set [ C++入门 ]

    【C++入门到精通】哈希 (STL) _ unordered_map _ unordered_set [ C++入门 ]

    欢迎各位大佬们的关顾,本文将介绍unordered系列容器以及其中的两个重要成员: unordered_map 和 unordered_set 。unordered_map是一种无序的关联容器,它使用哈希表来存储键值对,并提供高效的插入、查找和删除操作。在本文中,我们将首先介绍unordered_map的基本概念和特点,然后详

    2024年02月08日
    浏览(9)
  • 从C语言到C++_31(unordered_set和unordered_map介绍+哈希桶封装)

    从C语言到C++_31(unordered_set和unordered_map介绍+哈希桶封装)

    目录 1. unordered_set和unordered_map 1.1 unordered_map 1.2 unordered_set 1.3 unordered系列写OJ题 961. 在长度 2N 的数组中找出重复 N 次的元素 - 力扣(LeetCode) 349. 两个数组的交集 - 力扣(LeetCode) 217. 存在重复元素 - 力扣(LeetCode) 884. 两句话中的不常见单词 - 力扣(LeetCode) 2. 实现unorder

    2024年02月13日
    浏览(5)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包