【初阶与进阶C++详解】第二十篇:unordered_map和unordered_set(接口使用+模拟实现)

个人主页:企鹅不叫的博客

专栏

  • C语言初阶和进阶
  • C项目
  • Leetcode刷题
  • 初阶数据结构与算法
  • C++初阶和进阶
  • 《深入理解计算机操作系统》
  • 《高质量C/C++编程》
  • Linux

⭐️ 博主码云gitee链接:代码仓库地址

⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!

系列文章

【初阶与进阶C++详解】第一篇:C++入门知识必备

【初阶与进阶C++详解】第二篇:C&&C++互相调用(创建静态库)并保护加密源文件

【初阶与进阶C++详解】第三篇:类和对象上(类和this指针)

【初阶与进阶C++详解】第四篇:类和对象中(类的六个默认成员函数)

【初阶与进阶C++详解】第五篇:类和对象下(构造+static+友元+内部类

【初阶与进阶C++详解】第六篇:C&C++内存管理(动态内存分布+内存管理+new&delete)

【初阶与进阶C++详解】第七篇:模板初阶(泛型编程+函数模板+类模板+模板特化+模板分离编译)

【初阶与进阶C++详解】第八篇:string类(标准库string类+string类模拟实现)

【初阶与进阶C++详解】第九篇:vector(vector接口介绍+vector模拟实现+vector迭代器区间构造/拷贝构造/赋值)

【初阶与进阶C++详解】第十篇:list(list接口介绍和使用+list模拟实现+反向迭代器和迭代器适配)

【初阶与进阶C++详解】第十一篇:stack+queue+priority_queue+deque(仿函数)

【初阶与进阶C++详解】第十二篇:模板进阶(函数模板特化+类模板特化+模板分离编译)

【初阶与进阶C++详解】第十三篇:继承(菱形继承+菱形虚拟继承+组合)

【初阶与进阶C++详解】第十四篇:多态(虚函数+重写(覆盖)+抽象类+单继承和多继承)

【初阶与进阶C++详解】第十五篇:二叉树搜索树(操作+实现+应用KVL+性能+习题)

【初阶与进阶C++详解】第十六篇:AVL树-平衡搜索二叉树(定义+插入+旋转+验证)

【初阶与进阶C++详解】第十七篇:红黑树(插入+验证+查找)

【初阶与进阶C++详解】第十八篇:map_set(map_set使用+multiset_multimap使用+模拟map_set)

【初阶与进阶C++详解】第十九篇:哈希(哈希函数+哈希冲突+哈希表+哈希桶)


文章目录

  • 系列文章
  • 一、unordered_map介绍和使用
    • 1.unordered_map介绍
    • 2.unordered_map构造
    • 3.unordered_map容量
    • 4.unordered_map迭代器
    • 5.unordered_map元素访问和查询
    • 6.unordered_map修改
  • 二、unordered_set介绍和使用
    • 1.unordered_set介绍
    • 2.unordered_set构造
    • 3.unordered_set容量
    • 4.unordered_set迭代器
    • 5.unordered_set元素访问和查询
    • 6.unordered_set修改
  • 三、模拟实现unordered_map和unordered_set
    • 1.框架结构
    • 2.unordered_map和unordered_set仿函数实现
    • 4.迭代器实现
    • 5.封装unordered_map和unordered_set


一、unordered_map介绍和使用

1.unordered_map介绍

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

2.unordered_map构造

unordered_map ( const unordered_map& ump ) 拷贝构造

3.unordered_map容量

bool empty() const 检测unordered_map是否为空
size_t size() const 获取unordered_map的有效元素个数

4.unordered_map迭代器

begin 返回unordered_map第一个元素的迭代器
end 返回unordered_map最后一个元素下一个位置的迭代器
cbegin 返回unordered_map第一个元素的const迭代器
cend 返回unordered_map最后一个元素下一个位置的const迭代器

5.unordered_map元素访问和查询

operator[] 返回与key对应的value,没有一个默认值
iterator find(const K& key) 返回key在哈希桶中的位置,查找的时间复杂度可以达到O(1)
size_t count(const K& key) 返回哈希桶中关键码为key的键值对的个数,unordered_map中key是不能重复的,因此count函数的返回值最大为1

6.unordered_map修改

pair insert ( const value_type& val ) 向容器中插入键值对
iterator erase ( const_iterator position ) 删除容器中的键值对
void clear() 清空容器中有效元素个数
void swap(unordered_map&) 交换两个容器中的元素

测试代码:

void test_unordered_map()
{
	unordered_map<int, int> um;
	map<int, int> m;

	int arr[] = { 4,2,3,1,6,8,9,3 };

	for (auto e : arr)
	{
		um.insert(make_pair(e, e));
		m.insert(make_pair(e, e));
	}

	unordered_map<int, int>::iterator umit = um.begin();
	map<int, int>::iterator mit = m.begin();

	cout << "unordered_map:" << endl;
	while (umit != um.end())
	{
		cout << umit->first << ":" << umit->second << endl;
		++umit;
	}
	cout << "map:" << endl;
	while (mit != m.end())
	{
		cout << mit->first << ":" << mit->second << endl;
		++mit;
	}
}

二、unordered_set介绍和使用

1.unordered_set介绍

  1. unordered_set是以无特定顺序存储唯一元素的容器,并且允许根据它们的值快速检索单个元素,是一种K模型,不存储键值对,只存储Key
  2. 在unordered_set中,元素的值同时是它的key,它唯一地标识它。键值是不可变的,因unordered_set中的元素不能在容器中修改一次 ,但是可以插入和删除它们。
  3. 在内部,unordered_set中的元素不是按任何特定顺序排序的,而是根据它们的哈希值组织成桶,以允许直接通过它们的值快速访问单个元素,时间复杂度可以达到O(1)。
  4. unordered_set容器比set容器更快地通过它们的key访问单个元素,尽管它们通常对于通过其元素的子集进行范围迭代的效率较低。
  5. 容器中的迭代器至少是单向迭代器。

2.unordered_set构造

unordered_set ( const unordered_set& ust ) 拷贝构造

3.unordered_set容量

bool empty() const 检测unordered_map是否为空
size_t size() const 获取unordered_map的有效元素个数

4.unordered_set迭代器

begin 返回unordered_set第一个元素的迭代器
end 返回unordered_set最后一个元素下一个位置的迭代器
cbegin 返回unordered_set第一个元素的const迭代器
cend 返回unordered_set最后一个元素下一个位置的const迭代器

5.unordered_set元素访问和查询

operator[] 返回与key对应的value,没有一个默认值
iterator find(const K& key) 返回key在哈希桶中的位置,查找的时间复杂度可以达到O(1)
size_t count(const K& key) 返回哈希桶中关键码为key的键值对的个数,unordered_map中key是不能重复的,因此count函数的返回值最大为1

6.unordered_set修改

pair insert ( const value_type& val ) 向容器中插入键值对
iterator erase ( const_iterator position ) 删除容器中的键值对
void clear() 清空容器中有效元素个数
void swap(unordered_set&) 交换两个容器中的元素

测试代码:

void test_unordered_set()
{
	unordered_set<int> us;
	set<int> s;

	int arr[] = { 4,2,3,1,6,8,9,3 };

	for (auto e : arr)
	{
		us.insert(e);
		s.insert(e);
	}

	unordered_set<int>::iterator usit = us.begin();
	set<int>::iterator sit = s.begin();

	cout << "unordered_set:" << endl;
	while (usit != us.end())
	{
		cout << *usit << " ";
		++usit;
	}
	cout << endl;

	cout << "set:" << endl;
	while (sit != s.end())
	{
		cout << *sit << " ";
		++sit;
	}
	cout << endl;
}

三、模拟实现unordered_map和unordered_set

map和set模拟实现

1.框架结构

//模板参数第一个是key关键字,第二个参数不同容器V的类型不同,可以是K,也可以是pair类型
//第三个参数因为T的类型不同,通过value取key的方式就不同,第四个参数就是一个仿函数,为了获取V中K的值然后取模
// unordered_map ->HashTable, MapKeyOfT> _ht;
// unordered_set ->HashTable _ht;
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable
{
	typedef HashNode<T> Node;
public:
private:
	// 指针数组
	vector<Node*> _tables;
	size_t _n = 0;// 记录表中的数据个数
};

2.unordered_map和unordered_set仿函数实现

map和set仿函数实现,我们用新增模板参数代替键值对来进行比较,两者调用同一棵树,用仿函数区分两者

template<class K, class HashFunc = DefaultHash<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)//返回键值key
			{
				return key;//set只有一个键值
			}
		};
	};
-----------------------------------------------

template<class K, class V, class HashFunc = DefaultHash<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)//返回键值key
			{
				return kv.first;//map是键值对,需要提供kv的first进行比较
			}
		};
	};

4.迭代器实现

下面是迭代器完整模板,哈希表的迭代器类型是单向迭代器,没有反向迭代器,即没有实现–运算符的重载

//声明哈希表
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable;
//迭代器
//模板参数第一个是key关键字,第二个参数不同容器V的类型不同,可以是K,也可以是pair类型
//第三个参数因为T的类型不同,通过value取key的方式就不同,第四个参数就是一个仿函数,为了获取V中K的值然后取模
template<class K, class T, class KeyOfT, class HashFunc>
class __HTIterator
{
	typedef HashNode<T> Node;
	typedef __HTIterator<K, T, KeyOfT, HashFunc> Self;
public:
	Node* _node;
	HashTable<K, T, KeyOfT, HashFunc>* _pht;
	//初始化,节点指针和哈希表
	__HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
		:_node(node)
		, _pht(pht)
	{}
	//前置++
	Self& operator++()
	{
		if (_node->_next)
		{
			_node = _node->_next;
		}
		else
		{
			KeyOfT kot;
			HashFunc hf;
			size_t hashi = hf(kot(_node->_data));
			//当前桶走完了
			++hashi;
			//遍历找下一个不为空的桶
			for (; hashi < _pht->_tables.size(); ++hashi)
			{
				if (_pht->_tables[hashi])
				{
					_node = _pht->_tables[hashi];
					break;
				}
			}

			// 没有找到不为空的桶,用nullptr去做end标识
			if (hashi == _pht->_tables.size())
			{
				_node = nullptr;
			}
		}

		return *this;
	}

	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;
	}
};

将迭代器模板添加到哈希中,并且准备好普迭代器和const迭代器,和begin()和end()函数

// unordered_map ->HashTable, MapKeyOfT> _ht;
// unordered_set ->HashTable _ht;
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable
{
	 //迭代器是哈希表的友元
    //由于 ++ 重载函数在寻找下一个结点时,会访问哈希表成员变量 _table,而 _table 是哈希表的私有成员
	template<class K, class T, class KeyOfT, class HashFunc>
	friend class __HTIterator;
	typedef HashNode<T> Node;
public:
	typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator;
	//第一个桶里的第一个节点的迭代器
	iterator begin()
	{
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			Node* cur = _tables[i];
			if (cur)
			{
				//传送第一个节点数据,哈希表对象地址
				return iterator(cur, this);
			}
		}

		return end();
	}
	//最后一个数据的下一个位置
	iterator end()
	{
		return iterator(nullptr, this);
	}
private:
	// 指针数组
	vector<Node*> _tables;
	size_t _n = 0;
};

5.封装unordered_map和unordered_set

template<class K, class V, class HashFunc = DefaultHash<K>>
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
  return kv.first;
}
};
public:
iterator begin()
{
  return _ht.begin();
}

iterator end()
{
  return _ht.end();
}
//配合后面·[]实现所以插入返回值使用pair
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _ht.Insert(kv);	
}

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

bool erase(const K& key)
{
 return _ht.Erase(key);
}	
	//得到Key返回对应的Value的引用
V& operator[](const K& key)
{
 pair<iterator, bool> ret = insert(make_pair(key, V()));
 return ret.first->second;
}
private:
Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc> _ht;
};
------------------------------------------------------------
template<class K, class HashFunc = DefaultHash<K>>
class unordered_set
{
struct SetKeyOfT
{
  const K& operator()(const K& key)
  {
    return key;
  }
};
public:
//通过类域去取iterator,可能是内置类型或者静态成员变量,typename声明后面为一个类型名称,不是成员函数或者成员变量
typedef typename Bucket::HashTable<K, K, SetKeyOfT, HashFunc>::iterator iterator;

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

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

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

bool erase(const K& key)
{
  return _ht.Erase(key);
}
private:
Bucket::HashTable<K, K, SetKeyOfT, HashFunc> _ht;
};

测试代码:

void test_unordered_map()
{
	unordered_map<string, int> countMap;

	string strArr[] = { "香蕉","香蕉" ,"水蜜桃","西瓜","苹果","西瓜","香蕉" ,"苹果","西瓜","苹果","苹果","香蕉" ,"水蜜桃" };

	for (auto& e : strArr)
	{
		countMap[e]++;
	}

	countMap["芒果"] = 10;

	for (auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
}
-----------------------------------------------------
void test_unordered_set()
{
	unordered_set<string> s;

	s.insert("sort");
	s.insert("pass");
	s.insert("cet6");
	s.insert("pass");
	s.insert("cet6");

	s.erase("sort");

	for (auto& e : s)
	{
		cout << e << endl;
	}
}

你可能感兴趣的:(#,C++初阶和进阶,#,高阶数据结构,c++,算法,开发语言)