unordered_map模拟实现|STL源码剖析系列|开散列

news2024/12/27 2:00:35

        博主很久没有更新过STL源码剖析这个系列的文章了,主要是因为大部分STL常用的容器,博主都已经发过文章了,今天博主带着大家把哈希表也模拟实现一下。


前言

那么这里博主先安利一下一些干货满满的专栏啦!

手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏https://blog.csdn.net/yu_cblog/category_11464817.html这里是STL源码剖析专栏,这个专栏将会持续更新STL各种容器的模拟实现。

STL源码剖析https://blog.csdn.net/yu_cblog/category_11983210.html?spm=1001.2014.3001.5482


什么是开散列哈希桶

开散列哈希桶是一种哈希表的实现方式,它的原理是通过哈希函数将键(key)映射到一个存储桶(bucket)的索引位置,每个桶中可以存储多个键值对。

以下是开散列哈希桶的基本原理:

  • 初始化:创建一个固定大小的桶数组,每个桶都可以存储一个或多个键值对。
  • 哈希函数:选择一个合适的哈希函数,它可以将键映射到桶的索引位置。哈希函数应该是快速计算的,并且应该尽可能地将键均匀地分布在桶数组中。
  • 插入操作:当要插入一个键值对时,首先使用哈希函数计算出键的哈希值。根据哈希值找到对应的桶,然后将键值对存储在该桶中。如果多个键映射到同一个桶,可以使用链表、数组等数据结构来解决冲突,将它们存储在同一个桶中。
  • 查找操作:当要查找一个键时,使用哈希函数计算出键的哈希值。根据哈希值找到对应的桶,并在该桶中搜索键值对。如果有多个键映射到同一个桶,可以遍历桶中的键值对,找到匹配的键。
  •  删除操作:删除操作类似于查找操作。首先使用哈希函数计算出键的哈希值,找到对应的桶,并在桶中搜索键值对。如果找到匹配的键值对,将其从桶中删除。
  • 开散列哈希桶的优点是可以处理哈希冲突,即多个键映射到同一个桶的情况。通过合理选择哈希函数和解决冲突的方法,可以使桶中的键值对均匀分布,提高查找、插入和删除的效率。然而,如果哈希函数选择不当或者数据集分布不均匀,可能导致冲突增加,从而降低了性能。因此,在设计和使用开散列哈希桶时,选择适当的哈希函数和解决冲突的策略非常重要。

代码Github地址

C++icon-default.png?t=N658https://github.com/Yufccode/BitCode/tree/main/Cpp

open_hash.h

#pragma once

//tips:  deepcopy hasn't realize

#include<map>
#include<set>
#include<vector>
#include<string>
#include<iostream>
using namespace std;


//开散列
//拉链法
//哈希桶

template<class K>
struct HashFunc
{
	//目的就是把一个复杂类型转化成一个无符号整型
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
//特化
template<>
struct HashFunc<string>
{
	//BKDR算法
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			val *= 131;
			val += ch;
		}
		return val;
	}
};



template<class T>
struct HashNode
{
	T _data;
	HashNode<T>* _next;
	HashNode(const T& data)
		:_data(data), _next(nullptr) {}
};
//前置声明一下哈希表
template<class K, class T, class Hash, class KeyOfT>
struct HashTable;
template<class K, class T, class Hash, class KeyOfT>
struct __hash_iterator
{
	typedef HashNode<T>Node;
	typedef HashTable<K, T, Hash, KeyOfT> HT;
	typedef __hash_iterator<K, T, Hash, KeyOfT>self;
	Node* _node;
	HT* _pht; //因为这里要用HashTable对象指针,所以前面要前置声明一下

	__hash_iterator(Node* node, HT* pht)
		:_node(node), _pht(pht) {}
	T& operator*()
	{
		return _node->_data;
	}
	T* operator->()
	{
		return &_node->_data;
	}
	self& operator++()
	{
		if (_node->_next)
		{
			_node = _node->_next;
		}
		else
		{
			//找下一个桶
			//首先要找到自己是哪个桶的 
			//算一下当前的哈希值就行了
			Hash hash;
			KeyOfT kot;
			size_t i = hash(kot(_node->_data)) % _pht->_tables.size();//这里是访问不了_tables的,要设置一个友元
			++i;//从下一个桶开始走
			for (; i < _pht->_tables.size(); i++)
			{
				if (_pht->_tables[i])//如果当前桶不为空
				{
				_node = _pht->_tables[i];
				break;
				}
			}
			//如果找到最后都没有找到不为空的桶 -- 也就是一开始下标为i的桶已经是最后一个桶了
			if (i == _pht->_tables.size())
			{
				_node = nullptr;
			}
		}
		return*this;
	}
	bool operator!=(const self& s)const
	{
		return _node != s._node;
	}
	bool operator==(const self& s)const
	{
		return _node == s._node;
	}
};
/// <summary>
/// 看下源码我们可以发现,以前我们都是会复用代码的 比如Ref Ptr这样复用
/// 但是在哈希表这里,是分开实现的,这肯定是有原因的

template<class K, class T, class Hash, class KeyOfT>
struct HashTable
{
private:
	typedef HashNode<T> Node;
private:
	vector<Node*>_tables;
	size_t _size = 0;//存储的有效数据个数
public:
	friend struct __hash_iterator<K, T, Hash, KeyOfT>;//让__hash_iterator可以访问_tables
	typedef __hash_iterator<K, T, Hash, KeyOfT> iterator;//迭代器
	iterator begin()
	{
		for (size_t i = 0; i < _tables.size(); i++)
		{
			if (_tables[i])
			{
				return iterator(_tables[i], this);//第二个参数是this,要的是哈希表的指针,给个this就行
			}
		}
		//如果一个桶都没有
		return end();
	}
	iterator end()
	{
		return iterator(nullptr, this);
	}
public:
	HashTable() = default;
	~HashTable()
	{
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = this->_tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				free(cur);
				cur = next;
			}
			_tables[i] = nullptr;
		}
	}
private:
	inline size_t __stl_next_prime(size_t n)
	{
		static const size_t __stl_num_primes = 28;
		static const size_t __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
		};

		for (size_t i = 0; i < __stl_num_primes; ++i)
		{
			if (__stl_prime_list[i] > n)
			{
				return __stl_prime_list[i];
			}
		}

		return -1;
	}
public:
	pair<iterator, bool> insert(const T& data)
	{
		KeyOfT kot;
		//去重
		iterator ret = find(kot(data));
		if (ret != end())//已经有这个值了
		{
			return make_pair(ret, false);//插入失败
		}
		//扩容
		//负载因子到1就扩容
		if (_size==_tables.size())
		{
			//同样的问题:弄个新vector好还是复用insert好?
			//这里创建vector更好
			//因为节点我们可以直接拿下来
			//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
			vector<Node*>newTables;
			//newTables.resize(newSize, nullptr);
			newTables.resize(__stl_next_prime(_tables.size()), nullptr);
			//旧表中的节点移动映射到新表
			Hash hash;
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;//因为cur会改 -- 所以提前保存下一个
					size_t hashi = hash(kot(cur->_data)) % newTables.size();
					cur->_next = newTables[hashi];
					newTables[hashi] = cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
			_tables.swap(newTables);//新的和旧的交换一下
		}

		Hash hash;
		size_t hashi = hash(kot(data)) % _tables.size();
		//头插?尾插?
		Node* newnode = new Node(data);
		newnode->_next = _tables[hashi];
		_tables[hashi] = newnode;
		this->_size++;
		return make_pair(iterator(newnode, this), true);//返回新节点的迭代器和true
	}
	iterator find(const K& key) 
	{
		//先判断一下表是不是空的
		if (_tables.size() == 0)
		{
			return end();//表为空 -- 返回空
		}
		Hash hash;
		KeyOfT kot;
		size_t hashi = hash(key) % _tables.size();
		Node* cur = _tables[hashi];
		while (cur)
		{
			if (kot(cur->_data) == key)return iterator(cur, this);
			cur = cur->_next;
		}
		return end();
	}
	bool erase(const K& key)
	{
		if (_tables.size() == 0)
		{
			return false;
		}
		Hash hash;
		KeyOfT kot;
		size_t hashi = hash(key) % _tables.size();
		Node* cur = _tables[hashi];
		Node* prev = nullptr;
		while (cur)
		{
			if (kot(cur->_data)==key)
			{
				if (prev == nullptr)
				{
					//说明删的是头
					_tables[hashi] = cur->_next;
					delete(cur);
					--this->_size;
					return true;
				}
				else
				{
					prev->_next = cur->_next;
					delete cur;
					--this->_size;
					return true;
				}
			}
			prev = cur;
			cur = cur->_next;
		}
		return false;
	}
	size_t size()
	{
		return this->_size;
	}
	size_t bucket_size()
	{
		return _tables.size();
	}
	size_t bucket_num()
	{
		size_t ret = 0;
		for (size_t i = 0; i < _tables.size(); i++)
		{
			if (_tables[i])
			{
				++ret;
			}
		}
		return ret;
	}
	size_t longest_bucket_length()
	{
		size_t max_len = 0;
		for (size_t i = 0; i < _tables.size(); i++)
		{
			size_t len = 0;
			Node* cur = _tables[i];
			while (cur)
			{
				++len;
				cur = cur->_next;
			}
			max_len = max(max_len, len);
		}
		return max_len;
	}
};

UnorderedMap.h

#pragma once

#include"open_hash.h"
using namespace std;

//tips:  deepcopy hasn't realize

namespace yfc
{
	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 HashTable<K, pair<K, V>, Hash, MapKeyOfT>::iterator iterator;//µü´úÆ÷
		//¼ÇµÃ¼Ótypename	
		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
	public:
		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _ht.insert(kv);
		}
		bool erase(const K& key)
		{
			return _ht.erase(key);
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		HashTable<K, pair<K, V>, Hash, MapKeyOfT>_ht;
	};
	//=================²âÊÔ==================
	void test_map1()
	{
		unordered_map<string, string>dict;
		dict.insert(make_pair("a", "A"));
		dict.insert(make_pair("b", "B"));
		dict.insert(make_pair("c", "C"));
		dict.insert(make_pair("d", "D"));
		unordered_map<string, string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			cout << it->first << ":" << it->second << endl;
			++it;
		}
	}
	void test_map2()
	{
		string arr[] = { "Æ»¹û","Æ»¹û","Ï㽶","Î÷¹Ï","Æ»¹û","Ï㽶","Î÷¹Ï","Æ»¹û","Î÷¹Ï","Ï㽶","Î÷¹Ï","Î÷¹Ï" };
		//HashTable<string, int, HashFuncString>countHT;
		unordered_map<string, int>map;
		for (auto& str : arr)
		{
			map[str]++;
		}
		for (auto& kv : map) 
		{
			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;
	}
}

UnorderedSet.h

#pragma once

#include"open_hash.h"
using namespace std;

//tips:  deepcopy hasn't realize

namespace yfc
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;//迭代器
		//记得加typename
		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
	public:
		pair<iterator, bool> insert(const K& key)
		{
			return _ht.insert(key);
		}
		bool erase(const K& key)
		{
			return _ht.erase(key);
		}
	private:
		HashTable<K, K, Hash, SetKeyOfT>_ht;
	};
	//=================测试==================
	void test_set1()
	{
		unordered_set<int>s;
		s.insert(2);
		s.insert(3);
		s.insert(1);
		s.insert(2);
		s.insert(5);
		s.insert(-1);
		unordered_set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << endl;
			++it;
		}
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/734039.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

点云最小外包矩形计算

1、原理介绍 一簇点云的最小外包矩形&#xff08;Minimum Bounding Rectangle&#xff0c;MBR&#xff09;&#xff0c;是指用一个矩形将该簇点云框起来&#xff0c;所有点云数据在矩形框内。如下图所示为一个矩形框刚好将点云数据全部包围。 下面给出一种基于最大重叠度的最小…

[工业互联-19]:如何在QT中增加SOEM主站

目录 第1章 基本步骤 第2章 详细步骤 2.1.QT安装 2.2.VS安装 2.3.Win10 Debuggers 2.4.QT配置 2.5. SOEM移植 &#xff08;&#xff11;&#xff09;lib库生成 &#xff08;2&#xff09;文件移植: 文件整理 第1章 基本步骤 要在QT中添加SOEM主站功能&#xff0c;您需…

用OpenCV创建一张灰度黑色图像并设置某一列为白色

这段代码首先创建了一个400行600列的单通道灰度图像。然后,它遍历图像中的每个像素。如果像素位于列索引为30的列中,则将该像素的值设置为255。在灰度图像中,0表示黑色,255表示白色。因此,这段代码将图像的第30列设置为白色。 在 OpenCV 中,cv::Mat 构造函数的调用 cv::…

【算法 -- LeetCode】(13)罗马数字转整数

1、题目 罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M …

哈希表和字符串专题1—205. 同构字符串 1002. 查找共用字符 925. 长按键入 844.比较含退格的字符串 C++实现

文章目录 205. 同构字符串1002. 查找共用字符925. 长按键入844.比较含退格的字符串栈模拟双指针 205. 同构字符串 class Solution { public:bool isIsomorphic(string s, string t) {unordered_map<char, char> map1;unordered_map<char, char> map2;for(int i0, j…

AI绘画:StableDiffusion炼丹Lora攻略-实战萌宠图片生成

Lora攻略-实战萌宠图片生成 写在前面的话一&#xff1a;准备二、Lora作用1.AI模特2.炼衣服Lora3.改变画风/画面背景Lora模型究竟是什么&#xff1f; 三、如何炼制自己的Lora模型&#xff1f;四、炼丹前的准备&#xff08;**下载整合包**&#xff09;五、选择合适的大模型六、高…

管理类联考——逻辑——记忆篇——数字编码——公式

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&…

MySQL练习题(2)

创建如下员工标表 插入数据 1-- 按员工编号升序排列不在10号部门工作的员工信息 2-- 查询姓名第二个字母不是A且薪水大于1000元的员工信息&#xff0c;按薪水降序排列 4-- 求每个部门的平均薪水 5-- 求每个部门的最高薪水 6-- 求每个部门…

Coverity 2021.9 for win Coverity 2022.6 for linux

Coverity是一款快速、准确且高度可扩展的静态分析 (SAST) 解决方案&#xff0c;可帮助开发和安全团队在软件开发生命周期 (SDLC) 的早期解决安全和质量缺陷&#xff0c;跟踪和管理整个应用组合的风险&#xff0c;并确保符合安全和编码标准。Coverity 是一款精确的综合静态分析与…

JVM04-优化JVM内存分配以及内存持续上升问题和CPU过高问题排查

1-JVM内存分配 1.1-JVM内存分配性能问题 JVM内存分配不合理最直接的表现就是频繁的GC&#xff0c;这会导致上下文切换等性能问题&#xff0c;从而降低系统的吞吐量、增加系统的响应时间。因此&#xff0c;如果你在线上环境或性能测试时&#xff0c;发现频繁的GC&#xff0c;且…

密码学入门——分组密码模式

文章目录 参考书一、简介二、ECB模式三、CBC模式四、CFB模式五、OFB模式六、CTR模式 参考书 图解密码技术&#xff0c;第三版 一、简介 分组密码工作模式指的是将明文分成固定长度的块&#xff0c;并对每个块应用相同的加密算法进行加密的过程。这些块的长度通常是64位或128…

leetcode 654. 最大二叉树

2023.7.9 又是一道递归构造二叉树的题&#xff0c;和昨天做的那道题从中序与后序遍历序列构造二叉树类似&#xff0c;5分钟AC了。 大致思路就是通过找到数组中的最大值&#xff0c;并将其作为根节点&#xff0c;然后递归地构建左子树和右子树&#xff0c;最终返回整个最大二叉树…

PMP项目管理-敏捷

敏捷知识体系&#xff1a; 传统项目特点 1> 一开始就对详细的需求进行很高的投入 2> 价值只有到项目结束的时候才能体现, 风险较高 3> 一开始就要编写很多的文档 4> 客户参与度不高, 澄清完需求之后基本不参与 5> 需要花大量的时间来汇报当前的项目状态 6> 无…

Freertos-mini智能音箱项目---IO扩展芯片PCA9557

项目上用到的ESP32S3芯片引脚太少&#xff0c;选择了PCA9557扩展IO&#xff0c;通过一路i2c可以扩展出8个IO。这款芯片没有中断输入&#xff0c;所以更适合做扩展输出引脚用&#xff0c;内部寄存器也比较少&#xff0c;只有4个&#xff0c;使用起来很容易。 输入寄存器 输出寄存…

线程本地变量交换框架-TransmitterableThreadLocal(阿里开源)

上文 &#xff1a;秒级达百万高并发框架-Disruptor TransmitterableThreadLocal介绍 TransmitterableThreadLocal简称TTL 是阿里巴巴开源的一个框架。TransmittableThreadLocal是对Java中的ThreadLocal进行了增强和扩展。它旨在解决在线程池或异步任务调用链中&#xff0c;Thre…

MAC电脑查看SHA256方式

背景 现在很多网站下载大文件时&#xff0c;以往通过查看文件大小来确定是否下载正确&#xff0c;但是很多情况下&#xff0c;文件下载后大小差不多&#xff0c;但是很多时候却时候出现无法安装的问题&#xff0c;有可能还是下载的文件出现错误&#xff0c;导致文件无法正常使…

机器学习 day25(softmax在神经网络模型上的应用)

输出层采用softmax 在识别手写数字的模型中&#xff0c;预测y只有两个结果&#xff0c;所以输出层采用sigmoid激活函数且只有一个神经元。若预测y有10个结果&#xff08;0-9&#xff09;&#xff0c;该模型的前向传播计算方式与识别数字的模型完全相同&#xff0c;即隐藏层的…

dubbo概念及基本架构

一. Dubbo概念 1、Dubbo是一个开源的高性能&#xff0c;轻量级的Java RPC框架 RPC&#xff1a;远程服务调用 2、RPC的远程服务调用和SOA的服务治理两种功能。 二. Dubbo架构 0、服务提供方首先启动 1、服务提供方启动后&#xff0c;将其注射到注册中心里 2、消费者想调用提供…

线程随机性

目录 线程随机性的展现 执行start()的顺序不代表执行run()的顺序 在使用多线程技术时&#xff0c;代码运行的结果与代码执行顺序或者调用顺序无关。线程是一个子任务&#xff0c;而CPU是以不确定的方式&#xff0c;或者说是以随机的时间来调用线程中的run方法的。所以说线程执…

nvm 下载nodejs 失败

解决办法&#xff1a; 1.查看nvm安装路径 nvm root2、在安装路径下找到setting.txt,添加两句话 node_mirror: http://npm.taobao.org/mirrors/node/ npm_mirror: https://npm.taobao.org/mirrors/npm/3.再执行nvm install 就可以了。