当前位置: 首页 > news >正文

C++----unordered_map unordered_set使用及模拟

unordered_map unordered_set使用及模拟

  • 使用
  • 模拟
    • 结构
    • 修改底层bucket_hash
    • my_unordered_map
    • my_unordered_set
    • 注意

使用

unordered_set: 添加链接描述
unordered_map: 添加链接描述
unorderd_multiset:添加链接描述
unorderd_multimap: 添加链接描述

模拟

结构

底层采用bucket_hash,参考:数据结构----哈希(有修改:比如迭代器,仿函数等)
整体结构在这里插入图片描述

修改底层bucket_hash

对于HashNode:修改模板参数为template<class T>Tpair<K, V>

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

增加一个HTIterator类:注意hashtable迭代器为单向迭代器 (双向迭代器会导致HashTable使用双链表,浪费空间)

  • 注意:Iterator在STL sgi版中实现构造函数为__hashtable_iterator(node* n, hashtable* tab), 这里借鉴,意味着迭代器类里面要使用HashTable类的对象
  • 将operator++,!=等重载设为public成员,方便调用
  • 对于class HashTable需要加上友元:friend class HTIterator<K, T, Hash, KOT>;
  • 注意要在HTIterator类前面前置声明class HashTable,否则:
    在这里插入图片描述
template<class K, class T, class Hash, class KOT>
class HTIterator
{
	typedef HashNode<T> Node;
	typedef HashTable<K, T, Hash, KOT> HT;
	typedef HTIterator<K, T, Hash, KOT> Self;
public:
	Node* _node;
	HT* _ht;

	HTIterator(Node* node, HT* ht)
		:_node(node)
		,_ht(ht)
	{}
	bool operator!=(const Self& s) const
	{
		return (_node != s._node);
	}
	T& operator*()
	{
		return _node->_data;
	}
	T* operator->()
	{
     	return &_node->_data;
	}
	Self operator++()//注意:HashTable的迭代器是单向迭代器,只有++
	{
		if (_node->_next)//链表迭代
		{
			_node = _node->_next;
		}
		else//迭代桶
		{
			Hash hash;
			KOT kot;
			size_t index = hash(kot(_node->_data)) % _ht->_tables.size();
			index++;
			_node = nullptr;//防止走到_tables末尾仍无有效桶
			while (index < _ht->_tables.size())
			{
				if (_ht->_tables[index])
				{
					_node = _ht->_tables[index];
					break;
				}
				else
				{
					index++;
				}
			}
		}
		return *this;
	}
};

对于class HashTable:修改模板参数V为T,T标识pair<K, V>

  • 注意:在成员函数中:所有计算index的地方都要使用hash(kot(cur->_data))或hash(kot(key))统一进行转换为数字:hash是HashFunc kot是KeyOfT (对于MapKeyOfT是提取K,对于SetKeyOfT直接返回K)
  • 库里的insert返回值为pair<iterator, bool>,修改即可
template<class K, class T, class Hash, class KOT>
class HashTable
{
	typedef HashNode<T> Node;
	friend class HTIterator<K, T, Hash, KOT>;
public:
	typedef HTIterator<K, T, Hash, KOT> iterator;
	iterator begin()
	{
		for (auto& e : _tables)
		{
			if (e)
				return iterator(e, this);
		}
		return iterator(nullptr, this);
	}
	iterator end()
	{
		return iterator(nullptr, this);
	}
	~HashTable()//析构函数中处理每个桶的链表
	{
		for (auto& e : _tables)
		{
			Node* cur = e;
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}
			e = nullptr;
		}
		_n = 0;
	}
	bool Erase(const K& key)
	{
		if (_tables.size() == 0)
			return false;
		//注意这里不是双向链表不能借用Find()
		Hash hash;
		KOT kot;
		size_t index = hash(key) % _tables.size();
		Node* prev = nullptr;//需要一个prev记录cur上一个位置,防止删除要删的时cur
		Node* cur = _tables[index];
		while (cur)
		{
			if (kot(cur->_data) == key)
			{
				if (prev == nullptr)
					_tables[index] = cur->_next;
				else
					prev->_next = cur->_next;
				delete cur;//释放cur指针
				_n--;
				return true;
			}
			else
			{
				prev = cur;
				cur = cur->_next;
			}
		}
		return false;
	}
	Node* Find(const K& key)
	{
		//防止除零错误
		if (_tables.size() == 0)
			return nullptr;
		Hash hash;
		KOT kot;
		size_t index = hash(key) % _tables.size();
		Node* cur = _tables[index];
		while (cur)
		{
			if (kot(cur->_data) == key)
				return cur;
			else
				cur = cur->_next;
		}
		return nullptr;
	}
	pair<iterator, bool> Insert(const T& data)
	{
		//哈希桶控制负载因子负载因子为1时 进行扩容
		//库里面的unordered_map::load_factor可以查看负载因子,unordered_map::max_load_factor查看最大负载因子
		if (_tables.size()==0 || _n == _tables.size())
		{
			//size_t newsize = (_tables.size() == 0) ? 10 : _tables.size() * 2;
			//unordered_map::bucket_count查看桶的数量
			size_t newsize = Getnextprime(_tables.size());//使用除留余数法,最好模一个素数(没有明确规定,vs版本下就没有)

			vector<Node*> newtables(newsize, nullptr);
			for (auto& e : _tables)
			{
				Node* cur = e;
				while (cur)
				{
					Node* next = cur->_next;//下面要更改cur->_next这里记录一下
					Hash hash;
					KOT kot;
					size_t index = hash(kot(cur->_data)) % newtables.size();
					//头插
					cur->_next = newtables[index];
					newtables[index] = cur;
					cur = next;
				}
				e = nullptr;//注意,原来的_tables处e仍指向原来指针,应置空
			}
			newtables.swap(_tables);//现代写法
		}
		//插入逻辑
		Hash hash;
		KOT kot;
		size_t index = hash(kot(data)) % _tables.size();
		Node* cur = _tables[index];
		while (cur)
		{
			if (kot((cur->_data)) == kot(data))
				return make_pair(iterator(cur,this),false);
			else
			{
				cur = cur->_next;
			}
		}
		//采用头插更方便(尾插也可以)
		Node* newnode = new Node(data);
		newnode->_next = _tables[index];
		_tables[index] = newnode;
		_n++;
		return make_pair(iterator(_tables[index],this),true);

	}
private:
	vector<Node*> _tables;
	size_t _n;//存储的有效数据
};

my_unordered_map

对于unordered_map类:KOT的原型:MapKeyOfT仿函数

template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
	//参考Map和Set的MapKeyOfT
	struct MapKeyOfT
	{
		//pair里的K都加了const
		const K& operator()(const pair<K, V>& kv) const
		{
			return kv.first;
		}
	};
private:
	bucket_hash::HashTable<K, pair<K, V>, Hash, MapKeyOfT> _hashtable;
};

成员函数end(),begin(),insert等直接复用HashTable中的即可

  • 加上typedef typename bucket_hash::HTIterator<K, K, Hash, SetKeyOfT> iterator;
    注意:C++语言默认情况下,假定通过作用域运算符访问的名字不是类型,所以当我们要访问的是类型时候,必须显示的告诉编译器这是一个类型,通过关键字typename来实现这一点
  • 这里省略了一些:
typedef typename bucket_hash::HTIterator<K, K, Hash, SetKeyOfT> iterator;
iterator begin()
{
	return _hashtable.begin();
}
iterator end()
{
	return _hashtable.end();
}
V& operator[](const K& key)
{
	pair<iterator, bool> ret = insert(make_pair(key, V()));
	return ret.first->second;
}
pair<iterator, bool> insert(const pair<K, V>& kv)
{
	return _hashtable.Insert(kv);
}

测试:
在这里插入图片描述

my_unordered_set

unordered_set同理:
在这里插入图片描述

注意

bucket_hasn中的链表超过8个改用红黑树存储(红黑树用于处理极端情况)

相关文章:

  • R统计绘图-变量分组相关性网络图(igraph)
  • JUC并发编程系列详解篇五(线程基础理论进阶)
  • 设计模式 人类父母和猫孩子的关系理解观察者模式(发布订阅模式)
  • 【java核心技术】Java知识总结 -- 语法篇
  • Neo4j图数据库和GDS图算法应用
  • Hello, World
  • 蒋鑫鸿:9.10国际黄金原油最新外盘行情趋势点评附解一套技术指导
  • gem5 GPGPU-Sim 安装踩坑笔记
  • 【Linux私房菜】—— 远程登录与数据传输、Vim与Vi的基础用法、关机与重启、登录与注销、运行级别、root密码找回
  • JSR-133: JavaTM Memory Model and Thread Specification原文解析
  • html网页如何获取后台数据库的数据(html + ajax + php + mysql)
  • Spring之事务实现原理及其注解@Transactional底层和传播机制原理
  • 第14章: 集合
  • Java后端开发工程师学习笔记【狂神说Java笔记】
  • Linux上的中文输入法安装(Ubuntu + Kali五笔拼音)
  • @jsonView过滤属性
  • css的样式优先级
  • FastReport在线报表设计器工作原理
  • FineReport中如何实现自动滚屏效果
  • Javascript编码规范
  • JavaScript函数式编程(一)
  • Node项目之评分系统(二)- 数据库设计
  • Sass Day-01
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • 电商搜索引擎的架构设计和性能优化
  • 机器学习学习笔记一
  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • 前端js -- this指向总结。
  • 使用Swoole加速Laravel(正式环境中)
  • 推荐一款sublime text 3 支持JSX和es201x 代码格式化的插件
  • 一个完整Java Web项目背后的密码
  • 一天一个设计模式之JS实现——适配器模式
  • 因为阿里,他们成了“杭漂”
  • 阿里云移动端播放器高级功能介绍
  • ​Python 3 新特性:类型注解
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • $.type 怎么精确判断对象类型的 --(源码学习2)
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (4)事件处理——(2)在页面加载的时候执行任务(Performing tasks on page load)...
  • (a /b)*c的值
  • (AngularJS)Angular 控制器之间通信初探
  • (MATLAB)第五章-矩阵运算
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • (ZT)薛涌:谈贫说富
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (转)关于多人操作数据的处理策略
  • (转)项目管理杂谈-我所期望的新人
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • (最全解法)输入一个整数,输出该数二进制表示中1的个数。
  • *setTimeout实现text输入在用户停顿时才调用事件!*
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • @FeignClient 调用另一个服务的test环境,实际上却调用了另一个环境testone的接口,这其中牵扯到k8s容器外容器内的问题,注册到eureka上的是容器外的旧版本...
  • @html.ActionLink的几种参数格式
  • @javax.ws.rs Webservice注解