c++进阶之----orderedmap和unorderedmap

前言:由于STL容器库提供的这两个容器的用法和其他容器差不多,用法方面我只会提一嘴,中心             放在模拟实现上

1.  unordered_map

unordered_map 是一个关联容器,用于存储键值对(key-value)的集合。它的键(key)是唯一的,每个键对应一个值(value)。unordered_map 的查找、插入和删除操作的平均时间复杂度为 O(1)

特点

  • 无序性:元素存储的顺序与插入顺序无关,由哈希函数决定。

  • 唯一键:每个键在容器中只能出现一次。

  • 高效查找:通过哈希表实现,查找效率高。

适用场景

  • 需要快速查找、插入和删除键值对的场景。

  • 键需要唯一且无序的场景。

参考代码

#include<iostream>
#include<unordered_map>
using namespace std;
int main()
{
	unordered_map<int, string> umap;
	umap.insert({ 1, "Apple" });
	umap[2] = "Banana"; // 另一种插入方式
	for (auto& pair : umap)
	{
		cout << pair.first << ": " << pair.second << endl;
	}
	if (umap.find(1) != umap.end()) 
	{
		cout << umap[1]; // 输出 "Apple"
	}
	umap.erase(1); // 删除键为 1 的元素
	for (auto& pair : umap) 
	{
		cout << pair.first << ": " << pair.second << endl;
	}
	return 0;
}

 2.unordered_set

unordered_set 是一个关联容器,用于存储唯一的键值集合。它与 unordered_map 类似,但只存储键(没有值)。unordered_set 的查找、插入和删除操作的平均时间复杂度也为 O(1)

特点

  • 无序性:元素存储的顺序与插入顺序无关。

  • 唯一性:每个键只能出现一次。

  • 高效查找:通过哈希表实现,查找效率高。

适用场景

  • 需要快速判断某个元素是否存在的场景。

  • 需要存储唯一元素的场景。

 参考代码: 

#include <iostream>
#include <unordered_set>

int main() {
	std::unordered_set<int> uset;
	uset.insert(1);
	uset.insert(3);
	uset.insert(2);
	// 遍历
	for (const auto& elem : uset) 
	{
		std::cout << elem << " " ;
	}
	std::cout << std::endl;
	// 查找
	if (uset.count(2)) {
		std::cout << "2 exists in the set" << std::endl;
	}

	// 遍历
	for (const auto& elem : uset) {
		std::cout << elem << " ";
	}

	// 删除
	uset.erase(2);
	// 遍历
	for (const auto& elem : uset)
	{
		std::cout << elem << " ";
	}
	std::cout << std::endl;
	std::cout << std::endl;
	return 0;
}

 

3. unordered_map 和 unordered_set 的区别

特性unordered_mapunordered_set
存储内容键值对(key-value唯一的键值
插入操作插入键值对插入单个值
查找操作通过键查找对应的值判断某个值是否存在
适用场景需要存储键值对的场景需要存储唯一值的场景

4. 性能注意事项

  • 哈希冲突:如果哈希函数设计不好,可能会导致哈希冲突,降低性能。

  • 负载因子unordered_mapunordered_set 有一个负载因子(load factor),表示哈希表中元素数量与桶数量的比例。当负载因子过高时,容器会自动重新哈希(rehash),这会增加时间开销。

  • 自定义哈希函数:如果默认的哈希函数不满足需求,可以自定义哈希函数。

5.总结

  • unordered_map:适用于需要存储键值对的场景,查找、插入和删除效率高。

  • unordered_set:适用于需要存储唯一值的场景,判断元素是否存在效率高。

两者都基于哈希表实现,因此在需要高效查找的场景下非常有用。

 6.二者的模拟实现

6.1  改造hstable.h文件

由于二者都是基于哈希结构来实现的,所以我们可以将上节课的哈希桶的内容拿出来稍加改造,加入迭代器,并重载一下++,*,->等运算符,实现一下begin(),end()函数等等,要包括iterator和const_iterator,这里直接给出改造后的代码,读者可以根据需要选择性阅读!

这是改造后的hstable文件(原哈希桶文件) 

#pragma once
#include<iostream>
#include<vector>
using namespace std;
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
  53,         97,         193,       389,       769,
  1543,       3079,       6151,      12289,     24593,
  49157,      98317,      196613,    393241,    786433,
  1572869,    3145739,    6291469,   12582917,  25165843,
  50331653,   100663319,  201326611, 402653189, 805306457,
  1610612741, 3221225473, 4294967291
};

inline unsigned long __stl_next_prime(unsigned long n)
{
	const unsigned long* first = __stl_prime_list;
	const unsigned long* last = __stl_prime_list + __stl_num_primes;
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;
}

template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		size_t hashi = 0;
		for (auto ch : key)
		{
			hashi *= 131;
			hashi += ch;
		}

		return hashi;
	}
};
namespace hash_bucket
{
	template<class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;

		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{}
	};
	// 前置声明
	template<class K, class T, class KeyOfT, class Hash>
	class HashTable;

	template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
	struct HTIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyOfT,Hash> HT;
		typedef HTIterator<K, T, Ref, Ptr, KeyOfT,Hash> Self; 

		Node* _node;
		const HT* _ht;

		HTIterator(Node* node, const HT* ht)
			:_node(node)
			, _ht(ht)
		{}

		Ref operator*()             //由于我们的返回类型是不确定的,所以我们就用模板代替就好
		{
			return _node->_data;
		}

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

		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;
					}
					else
					{
						++hashi;
					}
				}
				// 所有桶都走完了,nullptr去做end()
				if (hashi == _ht->_tables.size())
					_node = nullptr;
			}
			return *this;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
		bool operator==(const Self& s)
		{
			return _node == s._node;
		}
	};
	//pair<const K, V>
	template<class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		// 友元声明   hash_bucket::HashTable<K, pair<const K, V>, MapKetofT, Hash>::Iterator iterator;
		template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
		friend struct HTIterator;

		typedef HashNode<T> Node;
	public:
		typedef HTIterator<K,T,T&,T*, KeyOfT, Hash> Iterator;
		typedef HTIterator<K, T, const T&, const T*, KeyOfT, Hash> ConstIterator;
		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);
		}
		ConstIterator begin() const
		{
			// 找第一个不为空的桶里面的第一个节点
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					return ConstIterator(_tables[i], this);
				}
			}

			return End();
		}

		ConstIterator End() const
		{
			return ConstIterator(nullptr, this);
		}

		HashTable(size_t size=__stl_next_prime(0))
			:_tables(size,nullptr)
		{}

		~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) //这里说的有问题 就是const的
		{
			KeyOfT kot;
			Iterator it = Find(kot(data)); //大写
			if (it != End())
			{
				//说明原来的哈希表已经有这个数据了,插入失败
				return { it,false };
			}
			Hash hs;
			// 负载因子到1,再扩容
			if (_n == _tables.size())
			{
				// 也可以,但是扩容新开辟节点,释放旧节点,有点浪费
				/*HashTable<K, V> newHT(__stl_next_prime(_tables.size() + 1));
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						newHT.Insert(cur->_kv);
						cur = cur->_next;
					}
				}
				_tables.swap(newHT._tables);*/

				vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), 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];
						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;

			//
			// 		HTIterator(Node* node, const HT* ht)
			//      :_node(node)
			///	      , _ht(ht)
			//
			//return { {newnode,this},true };
			return make_pair(Iterator(newnode, this), true);
		}

		Iterator Find(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];         //_tables[hashi]可以理解为链表的第一个数
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return Iterator(cur, nullptr);
				}
				cur = cur->_next;
			}
			return End();
		}

		bool Erase(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					//删除,但是要保证不影响这个桶的其他结点
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					--_n;
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};
}

6.2 编写unorderedmap.h文件

由于我们在模拟实现一个容器,那()运算符是必不可少的,这里我们对数据的操作都是要对键进行操作的,所以我们运算符重载一下Operator(),取键值

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

 然后我们把begin(),end(),insert()等函数封装一下,

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

		iterator end()
		{
			return _ht.End();
		}
		const_iterator begin() const
		{
			return _ht.begin();
		}
		const_iterator end() const
		{
			return _ht.End();
		}
		pair<iterator, bool> insert(const pair<K, V>& kv) //pait<string, string>&
		{
			return _ht.Insert(kv);
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.Insert({ key,V() });
			return ret.first->second;
		}

最后是汇总代码以及测试文件:

 

#pragma once
#include"hstable.h"
#include<string>

namespace rens
{
	template<class K, class V, class Hash = HashFunc<K>>
	class um
	{
		struct MapKetofT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKetofT, Hash>::Iterator iterator;
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKetofT, Hash>::ConstIterator const_iterator;

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

		iterator end()
		{
			return _ht.End();
		}
		const_iterator begin() const
		{
			return _ht.begin();
		}
		const_iterator end() const
		{
			return _ht.End();
		}
		pair<iterator, bool> insert(const pair<K, V>& kv) //pait<string, string>&
		{
			return _ht.Insert(kv);
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.Insert({ key,V() });
			return ret.first->second;
		}

	private:
		hash_bucket::HashTable<K, pair<const K, V>, MapKetofT, Hash> _ht; //写错了
	};
}

void test_unordered_map()
{
	rens::um<string, string> dict;

	dict.insert({ "string"," " });
	dict.insert({ "left", "" });

	dict["sort"];

	dict["left"] = "左边,剩余";
	dict["right"] = "右边";

	rens::um<string, string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;
}

6.3 编写Unorderedset.h文件

基本逻辑思路是一样的,这里不在赘述,这里有一点注意的是,我们这里SetKeyOfT模板,可以不写,但是由于我们是将他与hstable文件以及unorderedmap.h文件一起编译的,为了照顾unorderedmap,我们这里加一下

参考代码:

#pragma once
#include"hstable.h"

namespace rens
{
	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;
		//typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator const_iterator;

		using iterator = typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator;
		using const_iterator = typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator;

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

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

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

		const_iterator end() const
		{
			return _ht.End();
		}

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

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

	void print(const unordered_set<int>& s)
	{
		unordered_set<int>::const_iterator it = s.begin();
		while (it != s.end())
		{
			//*it = 1;
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void test_unordered_set()
	{
		unordered_set<int> us;
		us.insert(5);
		us.insert(1);
		us.insert(3);
		us.insert(5);
		us.insert(315);
		us.insert(5312);
		us.insert(333);

		for (auto e : us)
		{
			cout << e << " ";
		}
		cout << endl;

		unordered_set<int>::iterator it = us.begin();
		while (it != us.end())
		{
			// *it = 1;
			cout << *it << " ";
			++it;
		}
		cout << endl;

		print(us);
	}
}

6.4 测试一下 

#include"unorderedset.h"
#include"unorderedmap.h"
int main()
{
	//test_unordered_map();
	rens::test_unordered_set();
	return 0;
}

到这里,我们对这两个容器的模拟实现就结束了,下一篇文章中我们将会讲解c++11的一些新的特性与相关知识!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值