由[哈希/散列]模拟实现[unordered_map/unordered_set] (手撕迭代器)

news2024/12/23 10:45:23

文章目录

  • 1.迭代器分析
  • 2.细节处理
  • 3.完整代码
    • 3.1HashTable.h
    • 3.2unordered_set.h
    • 3.3unordered_map.h
    • 3.4Test.cpp

在这里插入图片描述

1.迭代器分析

在这里插入图片描述

2.细节处理

以下两篇文章均为笔者的呕心沥血
想要搞懂本篇文章的uu请自行查阅

哈希/散列的细节实现

哈希/散列–哈希表[思想到结构][==修订版==]

手撕迭代器的细节处理

模拟实现map/set[改编红黑树实现map/set容器底层]

3.完整代码

3.1HashTable.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <array>
#include <time.h>
#include <queue>
#include <stack>
#include <string>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <functional>
#include <assert.h>
using namespace std;

//转换函数
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return key;
	}
};
template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t value = 0;
		for (auto ch : s)
		{
			value = value * 131 + ch;
		}
		return value;
	}
};

//开散列/链地址法
namespace ChainAddressing
{
	//STL库中unordered_map/unordered_set的定义
	/* template
	   <
		class Key,
		class T,
		class Hash = hash<Key>,
		class Pred = equal_to<Key>,
		class Alloc = allocator< pair<const Key,T> >
	   >
		class unordered_map;
		class unordered_set;
	*/

	//结点类
	template<class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;

		HashNode(const T& data)
			:_next(nullptr)
			, _data(data)
		{

		}
	};

///  迭代器  

	//哈希表前置声明
	template<class K, class T, class GetKey, class Hash>
	class HashTable;
	//迭代器类
	template<class K, class T, class Ref, class Ptr, class GetKey, class Hash>
	struct HashIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, GetKey, Hash> HT;

		typedef HashIterator<K, T, Ref, Ptr, GetKey, Hash> Self;
		typedef HashIterator<K, T, T&, T*, GetKey, Hash> Iterator;

		//成员属性
		Node* _node;
		const HT* _ht;

		//构造函数
		HashIterator(Node* node, const HT* ht)
			:_node(node)
			, _ht(ht)
		{
		
		}

		//特殊构造函数
		HashIterator(const Iterator& it)
			:_node(it._node)
			, _ht(it._ht)
		{
		
		}

		//解引用运算符
		Ref operator*()
		{
			return _node->_data;
		}

		//成员访问运算符
		Ptr operator->()
		{
			return &_node->_data;
		}

		//关系运算符
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}

		//前置++
		Self& operator++()
		{
			//下个结点不空 向下++即可
			if (_node->_next != nullptr)
			{
				_node = _node->_next;
			}
			//下个结点为空
			else
			{
				GetKey get;
				Hash hash;
				//当前数据的哈希下标
				size_t hashi = hash(get(_node->_data)) % _ht->_tables.size();
				//下个结点为空 说明当前桶已空 找下一个非空桶
				++hashi;

				//找非空桶时 肯定不能越过哈希表
				while (hashi < _ht->_tables.size())
				{
					//_ht->_tables[hashi] : 
					// 取出的是表中的ptr 指向桶中首结点指针[如果存在的话]
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}
					else
						++hashi;
				}

				//while循环结束情况:
				//1.不满足循环条件 hashi == _ht->_tables.size() 
				// 即找完整个表 都没有找到非空桶 即当前桶之后的桶全为空
				//2._ht->_tables[hashi]不为空 找到了 break出来了 
				//面对上述情况 只需对第一种情况操作 即桶均空 ++失败 表中已无数据 返回空指针
				if (hashi == _ht->_tables.size())
					_node = nullptr;
			}
			return *this;
		}
	};

///

	//哈希表类
	template< class K, class T, class GetKey, class Hash >
	class HashTable
	{
		template<class K, class T, class Ref, class Ptr, class GetKey, class Hash>
		friend struct HashIterator;

		typedef HashNode<T> Node;
	public:
		typedef HashIterator<K, T, T&, T*, GetKey, Hash> iterator;
		typedef HashIterator<K, T, const T&, const T*, GetKey, Hash> const_iterator;

		//迭代器
		iterator begin()
		{
			Node* ptr = nullptr;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				ptr = _tables[i];
				if (ptr)
					break;
			}

			return iterator(ptr, this);
		}

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

		const_iterator begin() const
		{
			Node* ptr = nullptr;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				ptr = _tables[i];
				if (ptr)
					break;
			}

			return const_iterator(ptr, this);
		}

		const_iterator end() const
		{
			return const_iterator(nullptr, this);
		}

		//析构函数
		~HashTable()
		{
			for (auto& ptr : _tables)
			{
				while (ptr)
				{
					//记录下一个结点
					Node* next = ptr->_next;
					//释放当前结点
					delete ptr;
					//更新ptr
					ptr = next;
				}

				ptr = nullptr;
			}
		}

		//查找函数
		iterator Find(const K& key)
		{
			if (_tables.size() == 0)
				return end();

			GetKey get;
			Hash hash;

			size_t hashi = hash(key) % _tables.size();
			Node* ptr = _tables[hashi];
			while (ptr)
			{
				if (get(ptr->_data) == key)
					return iterator(ptr, this);
				ptr = ptr->_next;
			}
			return end();
		}

		//删除函数
		bool Erase(const K& key)
		{
			Hash hash;
			GetKey get;

			size_t hashi = hash(key) % _tables.size();
			Node* prv = nullptr;
			Node* ptr = _tables[hashi];
			while (ptr)
			{
				if (get(ptr->_data) == key)
				{
					if (prv == nullptr)
						_tables[hashi] = ptr->_next;
					else
						prv->_next = ptr->_next;

					delete ptr;
					return true;
				}
				else
				{
					prv = ptr;
					ptr = ptr->_next;
				}
			}
			return false;
		}

		//C++SGI版本STL库:获得下一个素数
		//在SGI下 设定哈希表的容量为素数
		//[可能效率更高 有兴趣的可以自行查阅]
		/*
		size_t GetNextPrime(size_t index)
		{
			static const int num = 28;
			static const unsigned long prime[num] =
			{
				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
			};

			size_t i = 0;
			for (i = 0; i < num; ++i)
			{
				if (prime[i] > index)
					return prime[i];
			}

			return prime[i];
		}
		*/

		//插入函数
		pair<iterator, bool> Insert(const T& data)
		{
			GetKey get;
			iterator it = Find(get(data));
			if (it != end())
				return make_pair(it, false);

			Hash hash;
			//负载因子/荷载系数 -- Load_Factor = _n / _tables.size();
			//负载因子 == 1时扩容
			if (_n == _tables.size())
			{
				///  高级代码1.0  /
				/*
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				HashTable<K, V> newht;
				newht.resize(newsize);
				for (auto& ptr : _tables)
				{
					while (ptr)
					{
						newht.Insert(ptr->_pair);
						ptr = ptr->_next;
					}
				}

				_tables.swap(newht._tables);
				*/


				//初代扩容
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;

				//引进stl库素数思想
				//size_t newsize = GetNextPrime(_tables.size());

				vector<Node*> newtables(newsize, nullptr);
				//遍历旧表 取出旧表里每一个指针
				for (auto& ptr : _tables)
				{
					//ptr是旧表里存储的每一个指针
					//它指向当前哈希桶的首结点 即他存储的是首结点的地址
					while (ptr)
					{
						//算出 当前结点根据新哈希函数计算的新下标
						size_t hashi = hash(get(ptr->_data)) % newtables.size();

						//ptr是首结点的地址 ptr->_next为第二个结点的地址
						Node* next = ptr->_next;

						// 头插到新表
						ptr->_next = newtables[hashi];
						newtables[hashi] = ptr;

						//更新ptr 即向下遍历
						ptr = next;
					}
				}

				_tables.swap(newtables);
			}

			//哈希函数计算出的下标
			size_t hashi = hash(get(data)) % _tables.size();
			//链表头插
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;

			return make_pair(iterator(newnode, this), false);
		}

		//最大桶数据个数
		size_t MaxBucketSize()
		{
			size_t max = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				auto ptr = _tables[i];
				size_t size = 0;
				while (ptr)
				{
					++size;
					ptr = ptr->_next;
				}

				//每个桶数据个数
				//printf("[%d] -> %d\n", i, size);

				if (size > max)
					max = size;
			}
			return max;
		}
	private:
		vector<Node*> _tables; // 指针数组
		size_t _n = 0;         // 存储有效数据个数
	};
}

3.2unordered_set.h

#pragma once
#include "HashTable.h"

namespace ape
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
	public:
		//获得key
		struct SetGetKey
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename ChainAddressing::HashTable<K, K, SetGetKey, Hash>::const_iterator iterator;
		typedef typename ChainAddressing::HashTable<K, K, SetGetKey, Hash>::const_iterator 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 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:
		ChainAddressing::HashTable<K, K, SetGetKey, Hash> _ht;
	};

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

	//测试函数
	void test_unordered_set()
	{
		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
		unordered_set<int> s;
		for (auto e : a)
			s.insert(e);

		s.insert(54);
		s.insert(107);

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

		for (auto e : s)
			cout << e << " ";

		cout << endl;
		print(s);
	}
}

3.3unordered_map.h

#pragma once
#include "HashTable.h"

namespace ape
{
	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
	public:
		//获得key
		struct MapGetKey
		{
			const K& operator()(const pair<K, V>& pair)
			{
				return pair.first;
			}
		};
	public:
		typedef typename ChainAddressing::HashTable<K, pair<const K, V>, MapGetKey, Hash>::iterator iterator;
		typedef typename ChainAddressing::HashTable<K, pair<const K, V>, MapGetKey, Hash>::const_iterator 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>& pair)
		{
			return _ht.Insert(pair);
		}

		//下标运算符
		V& operator[](const K& key)
		{
			pair<iterator, bool> pair = insert(make_pair(key, V()));
			return pair.first->second;
		}

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

		//删除
		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}

	private:
		ChainAddressing::HashTable<K, pair<const K, V>, MapGetKey, Hash> _ht;
	};

	void test_unordered_map1()
	{
		unordered_map<int, int> m;
		m.insert(make_pair(1, 1));
		m.insert(make_pair(3, 3));
		m.insert(make_pair(2, 2));

		unordered_map<int, int>::iterator it = m.begin();
		while (it != m.end())
		{
			cout << it->first << ":" << it->second << endl;
			++it;
		}
		cout << endl;
	}

	void test_unordered_map2()
	{
		string s[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };
		unordered_map<string, int> um;
		for (auto& e : s)
		{
			um[e]++;
		}

		for (auto& pair : um)
		{
			cout << pair.first << ":" << pair.second << endl;
		}
	}

	class Date
	{
		friend struct HashDate;
		friend ostream& operator<<(ostream& _cout, const Date& d);

	public:
		Date(int year = 1900, int month = 1, int day = 1)
			: _year(year)
			, _month(month)
			, _day(day)
		{
		
		}

		bool operator<(const Date& d) const
		{
			return (_year < d._year) 
				|| (_year == d._year && _month < d._month)
				|| (_year == d._year && _month == d._month && _day < d._day);
		}

		bool operator>(const Date& d) const
		{
			return (_year > d._year)
				|| (_year == d._year && _month > d._month) 
				|| (_year == d._year && _month == d._month && _day > d._day);
		}

		bool operator==(const Date& d) const
		{
			return _year == d._year
				&& _month == d._month
				&& _day == d._day;
		}

	private:
		int _year;
		int _month;
		int _day;
	};
	ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}

	struct HashDate
	{
		size_t operator()(const Date& d)
		{
			size_t value = 0;

			value += d._year;
			value *= 131;

			value += d._month;
			value *= 131;

			value += d._day;
			value *= 131;
			 
			return value;
		}
	};

	void test_unordered_map4()
	{
		Date d1(2023, 10, 1);
		Date d2(2023, 10, 5);
		Date d3(2023, 10, 2);
		Date d4(2023, 10, 2);
		Date d5(2023, 10, 2);
		Date d6(2023, 10, 1);

		Date a[] = { d1, d2, d3, d4, d5, d6 };

		unordered_map<Date, int, HashDate> um;
		for (auto e : a)
		{
			um[e]++;
		}

		for (auto& pair : um)
		{
			cout << pair.first << ":" << pair.second << endl;
		}
	}
}

3.4Test.cpp

#define _CRT_SECURE_NO_WARNINGS 
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <array>
#include <time.h>
#include <queue>
#include <stack>
#include <string>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <functional>
#include <assert.h>
using namespace std;


#include "HashTable.h"
#include "Unordered_Map.h"
#include "Unordered_Set.h"


int main()
{
	//ape::test_unordered_set();
	ape::test_unordered_map4();
	
	return 0;
}

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

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

相关文章

硬件知识:U盘相关知识介绍,值得收藏

目录 什么是U盘&#xff1f; U盘根据结构有哪几种&#xff1f; 根据U盘的存储介质、外形、功能分类有哪几种&#xff1f; 什么是U盘&#xff1f; U盘&#xff0c;全称为USB闪存盘&#xff0c;是一种以闪存芯片作为数据存储介质的移动存储设备。U盘的历史可以追溯到1998年&am…

vue3 中使用 echarts 图表——准备篇

我们常常在项目中使用图表来表示数据&#xff0c;而目前最常用的图标就是echarts&#xff0c;接下来我们就开始学习在vue中使用echarts图标。 一、准备一个vue项目&#xff08;一般通过vite来构建&#xff0c;而不是vue-cli&#xff09; 1.找到打开vite官网 2. 运行创建命令 …

MySQL进阶-存储引擎

目录 1.MySQL体系结构 体系结构图 各层的作用 2.存储引擎简介 2.1查看当前表的存储引擎 2.2 查询mysql支持的存储引擎 2.3 InnoDB简介 2.4 MyISAM简介 2.5 Memory简介 3.存储引擎的选择 1.MySQL体系结构 mysql体系结构主要有四层结构&#xff0c;从上到下依次是&#…

Spring Cloud zuul扩展能力设计和心得

前言 实际上Spring Cloud已经废弃zuul了&#xff0c;改用gateway&#xff0c;但是webflux的技术并没在实际项目大规模普及&#xff0c;还有很多servlet NIO的应用&#xff0c;所以zuul还是很有必要改造的&#xff0c;实测zuul调优&#xff08;调节转发的连接池&#xff09;跟g…

【算法挨揍日记】day11——852. 山脉数组的峰顶索引、162. 寻找峰值

852. 山脉数组的峰顶索引 852. 山脉数组的峰顶索引 题目描述&#xff1a; 符合下列属性的数组 arr 称为 山脉数组 &#xff1a; arr.length > 3存在 i&#xff08;0 < i < arr.length - 1&#xff09;使得&#xff1a; arr[0] < arr[1] < ... arr[i-1] < …

数据结构:二叉树(超详解析)

目录​​​​​​​ 1.树概念及结构 1.1树的概念 1.2树的相关概念 1.3树的表示 1.3.1孩子兄弟表示法&#xff1a; 1.3.2双亲表示法&#xff1a;只存储双亲的下标或指针 两节点不在同一树上&#xff1a; 2.二叉树概念及结构 2.1.概念 2.2.特殊的二叉树&#xff1a; 2…

掌握交易时机!

“您是否知道您选择购买和出售加密货币的时间会产生很大的影响&#xff1f;当然&#xff0c;大多数交易者都知道高价卖出和低价买入的基本知识。然而&#xff0c;在选择交易加密货币的最佳时机时&#xff0c;还需要考虑许多其他小细节。加密货币市场分析表明&#xff0c;一天中…

【MyBatis-Plus】快速精通Mybatis-plus框架—核心功能

刚才的案例中都是以id为条件的简单CRUD&#xff0c;一些复杂条件的SQL语句就要用到一些更高级的功能了。 1.条件构造器 除了新增以外&#xff0c;修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外&#xff0c;还支持…

ES 关于 remote_cluster 的一记小坑

最近有小伙伴找到我们说 Kibana 上添加不了 Remote Cluster&#xff0c;填完信息点 Save 直接跳回原界面了。具体页面&#xff0c;就和没添加前一样。 我们和小伙伴虽然隔着网线但还是进行了深入、详细的交流&#xff0c;梳理出来了如下信息&#xff1a; 两个集群&#xff1a;…

Java常见API---split()

package daysreplace;public class SplitTest {public static void main(String[] args) {String str"武汉市|孝感市|长沙市|北京市|上海市";String[] array str.split("\\|");System.out.println(array[0]);System.out.println(array[1]);System.out.pri…

[黑马程序员TypeScript笔记]------一篇就够了

目录&#xff1a; TypeScript 介绍 TypeScript 是什么&#xff1f;TypeScript 为什么要为 JS 添加类型支持&#xff1f;TypeScript 相比 JS 的优势TypeScript 初体验 安装编译 TS 的工具包 编译并运行 TS 代码 简化运行 TS 的步骤 TypeScript 常用类型 概述类型注解常用基础…

​“债务飙升!美国一天内增加2750亿美元,金融震荡的前奏已拉开帷幕!”

2023年10月4日&#xff0c;美国政府向美国债务追加2750亿美元&#xff0c;相当于现在比特币&#xff08;BTC&#xff09;总市值的一半还多。 有人会说:多一点、少一点&#xff0c;没什么区别.....确实&#xff0c;当你看美国债务时&#xff0c;2750亿美元并没有什么意义&#x…

oringin的x轴(按x轴规定值)绘制不规律的横坐标

1.双击x轴 2.选择刻度线标签 3.选择刻度

【数据结构】排序算法(二)—>冒泡排序、快速排序、归并排序、计数排序

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.冒泡排序 2.快速排序 2.1Hoare版 2.2占…

二叉树的初步认识

二叉树是这么一种树状结构&#xff1a;每个节点最多有两个孩子&#xff0c;左孩子和右孩子 重要的二叉树结构 完全二叉树&#xff08;complete binary tree&#xff09;是一种二叉树结构&#xff0c;除最后一层以外&#xff0c;每一层都必须填满&#xff0c;填充时要遵从先左后…

YOLOv5报错:TypeError: load() missing 1 required positional argument: ‘Loader‘

报错信息 报错位置 解决办法 将 self.update(yaml.load(fo.read())) 改为&#xff1a; self.update(yaml.safe_load(fo.read()))

信息学 学习/复习 抽签器(附源码)

问你一个问题&#xff0c;你考试前怎么复习呀&#xff1f; 效果图 以下是源代码&#xff0c;可自行修改 [C] #include<bits/stdc.h> #include<windows.h> using namespace std; vector<string>item; int main(void) {item.push_back("Manacher"…

谁“动”了我的信息?

通信公司“内鬼” 批量提供手机卡 超6万张手机卡用来发涉赌短信 2023年10月2日&#xff0c;据报道2022年12月&#xff0c;湖北省公安厅“雷火”打击整治治安突出问题专项行动指挥部研判发现&#xff0c;有人在湖北随州利用虚拟拨号设备GOIP发出大量赌博短信。随州市公安局研判…

第一课数组、链表、栈、队列

第一课数组、链表、栈、队列 acwing136 邻值查找---中等题目描述代码展示 lc20.有效的括号--简单题目描述代码展示 lc25.K 个一组翻转链表--困难题目描述代码展示 lc26.删除有序数组中的重复项--简单题目描述代码展示 lc88.合并两个有序数组--简单题目描述代码展示 lc141.环形链…

Flink--9、双流联结(窗口联结、间隔联结)

星光下的赶路人star的个人主页 我还有改变的可能性&#xff0c;一想起这点&#xff0c;我就心潮澎湃 文章目录 1、基于时间的合流——双流联结&#xff08;Join&#xff09;1.1 窗口联结&#xff08;Window Join&#xff09;1.2 间隔联结&#xff08;Interval Join&#xff09;…