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

C++ 哈希桶模拟实现(补充)

目录

定义基本的存储结构

Insert()和Find()

Erase()

如何控制哈希冲突?

Insert()中添加扩容操作

其他问题的解决

 

UnorderedMap.h和UnorderedSet.h

迭代器实现与UnorderedMap.h和UnorderedSet.h的封装


定义基本的存储结构

#pragma once
#include<iostream>
#include<vector>
using namespace std;
namespace OpenHash
{
	template<class K,class V>
	struct HashNode
	{
		HashNode<K, V>* _next;
		pair<K, V> _kv;


        //构造函数
        HashNode(const pair<K, V>& kv)
			:_next(nullptr)
			,_kv(kv)
		{}
	};

	template<class K,class V>
	class HashTable
	{
		typedef HashNode<K, V>Node;
	public:
		//待写
	private:
		vector<Node*> _table;
		size_t _n = 0;    //有效数据的个数
	};
}

Insert()和Find()

bool Insert(const pair<K, V>& kv)
{
	//Find()函数防止冗余
	if (Find(kv.first))
		return false;

	size_t index = kv.first % _table.size();
	//采用头插法,尾插法还需要找尾,麻烦
	Node* newnode = new Node(kv);

	newnode->_next = _table[index];
	_table[index] = newnode;
	++_n;

	return true;
}

Node* Find(const K& key)
{
	size_t index = kv.first % _table.size();
	Node* cur = _table[index];
	while (cur)
	{
		if (cur->_kv.first == key)
		{
			return cur;
		}
		else
		{
			cur = cur->_next;
		}
	}
	return nullptr;
}

Erase()

思考:如果单链表不给头结点,如何删除指定节点?采用替换法删除

将下一个节点的值赋给指定节点,再删除下一个节点

 思考:采用替换法如何删除尾节点?以下图为例,删除104节点

 

bool Erase(const K& key)
{
	size_t index = kv.first % _table.size();
	Node* prev = nullptr;
	Node* cur = _table[index];
	while (cur)
	{
		if (cur->_kv.first == key)
		{
			if (_table[index] == cur)
			{
				_table[index] = cur->_next;
			}
			else
			{
				prev->_next = cur->_next;
			}
			--_n;
			delete cur;
			return true;
		}
		prev = cur;
		cur = cur->_next;
	}
	return false;
}

如何控制哈希冲突?

 

Insert()中添加扩容操作

bool Insert(const pair<K, V>& kv)
{
	//Find()函数防止冗余
	if (Find(kv.first))
		return false;

	//负载因子到1的时候增容
	//负载因子为0的时候要防止除零错误
	if (_n == _table.size())
	{
		vector<Node*> newtable;
		size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
		newtable.resize(newSize, nullptr);

		//遍历取旧表中节点,重新算映射到新表中的位置,挂到新表中
		for (size_t i = 0; i < _table.size(); ++i)
		{
			if (_table[i])//operator[]会检查给的下标是否在0到size这个范围之内
			{
				Node* cur = _table[i];
				while (cur)//若不为空,则把该桶取完,计算其在新表中的位置
				{
					Node* next = cur->_next;//保存cur的下一个节点
					size_t index = cur->_kv.first % newtable.size();
					//头插
					cur->_next = newtable[index];
					_table[index] = cur;

					cur = next;
				}
				_table[i] = nullptr;//都处理完后旧表的当前位置可置空
			}
		}
		_table.swap(newtable); //交换新旧表
	}

	size_t index = kv.first % _table.size();
	//采用头插法,尾插法还需要找尾,麻烦
	Node* newnode = new Node(kv);

	newnode->_next = _table[index];
	_table[index] = newnode;
	++_n;

	return true;
}

 

其他问题的解决

1.K不支持取模的情况?提供一个默认返回的仿函数,再提供一个特化的仿函数。后续需要取模的地方使用仿函数即可(详细见全部代码)

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

// 特化
template<>
struct Hash<string>
{
	// "int"  "insert" 
	// 字符串转成对应一个整形值,因为整形才能取模算映射位置
	// 期望->字符串不同,转出的整形值尽量不同
	// "abcd" "bcad"
	// "abbb" "abca"
	size_t operator()(const string& s)
	{
		// BKDR Hash
		size_t value = 0;
		for (auto ch : s)
		{
			value += ch;
			value *= 131;
		}

		return value;
	}
};

2.除了控制负载因子,尽量让表的大小为素数也可以控制哈希冲突。但是此时对于增容的挑战很大:要保证增容二倍后还是表的大小还是素数。->通过素数表解决

 
//提供一个素数表(来自STL源码)
size_t GetNextPrime(size_t prime)
{
	const int PRIMECOUNT = 28;
	static const size_t primeList[PRIMECOUNT] =  //定义成静态的,不用每次重新创建数组
	{
		53ul, 97ul, 193ul, 389ul, 769ul,
		1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
		49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
		1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
		50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
		1610612741ul, 3221225473ul, 4294967291ul
	};

	size_t i = 0;
	for (; i < PRIMECOUNT; ++i)
	{
		//获取一个比当前素数要大的数
		if (primeList[i] > prime)
			return primeList[i];
	}

	return primeList[i];
}

此时增容函数就可进行修改

bool Insert(const pair<K, V>& kv)
{
	//Find()函数防止冗余
	if (Find(kv.first))
		return false;

	HashFunc hf;

	//负载因子到1的时候增容
	//负载因子为0的时候要防止除零错误
	if (_n == _table.size())
	{
		vector<Node*> newtable;
		//size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
		//newtable.resize(newSize, nullptr);
		newtable.resize(GetNextPrime(_table.size()));//通过素数表可重新写增容函数

		//遍历取旧表中节点,重新算映射到新表中的位置,挂到新表中
		for (size_t i = 0; i < _table.size(); ++i)
		{
			if (_table[i])//operator[]会检查给的下标是否在0到size这个范围之内
			{
				Node* cur = _table[i];
				while (cur)//若不为空,则把该桶取完,计算其在新表中的位置
				{
					Node* next = cur->_next;//保存cur的下一个节点
					size_t index = hf(cur->_kv.first) % newtable.size();
					//头插
					cur->_next = newtable[index];
					_table[index] = cur;

					cur = next;
				}
				_table[i] = nullptr;//都处理完后旧表的当前位置可置空
			}
		}
		_table.swap(newtable); //交换新旧表
	}

	size_t index = hf(kv.first) % _table.size();
	//采用头插法,尾插法还需要找尾,麻烦
	Node* newnode = new Node(kv);

	newnode->_next = _table[index];
	_table[index] = newnode;
	++_n;

	return true;
}

 

UnorderedMap.h和UnorderedSet.h

#pragma once
#include"HashTable.h"

namespace bit
{
	template<class K, class V>
	class unordered_map
	{
	public:
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

		bool Insert(const pair<K, V>& kv)
		{
			_ht.Insert(kv);
			return true;
		}

	private:
		OpenHash::HashTable<K, pair<K, V>, MapKeyOfT>_ht;
	};
}
 
#pragma once
#include"HashTable.h"

namespace bit
{
	template<class K, class V>
	class unordered_set
	{
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& k)
			{
				return k;
			}
		};

		bool Insert(const K k)
		{
			_ht.Insert(k);
			return true;
		}

	private:
		OpenHash::HashTable<K,V,SetKeyOfT>_ht;
	};
}

迭代器实现与UnorderedMap.h和UnorderedSet.h的封装

各个部分的改动和添加看注释即可,最终整体代码如下

#pragma once
#include<iostream>
#include<vector>
using namespace std;
namespace OpenHash
{
	template<class K>
	struct Hash
	{
		size_t operator()(const K& key)
		{
			return key;
		}
	};

	// 特化
	template<>
	struct Hash<string>
	{
		// "int"  "insert" 
		// 字符串转成对应一个整形值,因为整形才能取模算映射位置
		// 期望->字符串不同,转出的整形值尽量不同
		// "abcd" "bcad"
		// "abbb" "abca"
		size_t operator()(const string& s)
		{
			// BKDR Hash
			size_t value = 0;
			for (auto ch : s)
			{
				value += ch;
				value *= 131;
			}

			return value;
		}
	};
	
	template<class T>
	struct HashNode
	{
		HashNode<T>* _next;
		T _data;

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

	//前置声明
	//前置声明不需要给默认参数
	template<class K, class T, class KeyOfT, class HashFunc>
	class HashTable;


	//哈希桶迭代器
	template<class K, class T, class KeyOfT, class HashFunc = Hash<K>>
	struct _HTIterator
	{
		typedef HashNode<T> Node;
		typedef _HTIterator<K, T, KeyOfT, HashFunc>Self;
		typedef HashTable<K, T, KeyOfT, HashFunc>HT;
		Node* _node;
		HT* _pht;//传哈希表的指针

		_HTIterator(Node* node,HT* pht)
			:_node(node)
			,_pht(pht)
		{}

		Self& operator++()
		{
			//1.当前桶中还有数据,那么就在当前桶往后走
			//2.当前桶走完了,需要往下一个桶去走
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				//迭代器走到当前桶的最后一个位置的时候如何进入下一个桶?
				_pht->_table.size();
				KeyOfT kot;
				HashFunc hf;
				size_t index = hf(kot(_node->_data)) % _pht->_table.size();//算出当前桶的位置
				//通过keyoft的仿函数取出data里面的k取出,k再通过HashFunc的仿函数转换成可以取模的整型
				++index;
				while (index < _pht->_table.size())
				{
					if (_pht->_table[index])//找到下一个桶了
					{
						_node = _pht->_table[index];//指向桶里面的第一个节点
						return *this;
					}
					else
					{
						++index;
					}
				}
				
				//return iterator(nullptr, _pht);//后面没有桶了,返回空的迭代器
				_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;
		}
	};

	template<class K,class T, class KeyOfT,class HashFunc = Hash<K>>
	class HashTable
	{
		typedef HashNode<T>Node;

		//设计类模板的友元
		template<class K, class T, class KeyOfT, class HashFunc>//需要添加模板参数
		friend struct _HTIterator;
	
	public:
		typedef _HTIterator<K, T, KeyOfT, HashFunc> iterator;

		//默认的构造
		HashTable() = default; //C++11关键词:显示指定生成默认构造


		 //拷贝构造赋值
		HashTable(const HashTable& ht)
		{
			//深拷贝
			_n = ht._n;
			_table.resize(ht._table.size());
			for (size_t i = 0; i < ht._table.size(); i++)
			{
				Node* cur = ht._table[i];
				while (cur)
				{
					Node* copy = new Node(cur->_data);
					//头插到新表
					copy->_next = table[i];
					_table[i] = copy;

					cur = cur->_next;
				}
			}
		}

		//赋值
		HashTable& operator=(const HashTable ht)
		{
			_table.swap(ht._table);
			swap(_n,ht._n);
			
			return *this;
		}

		//析构
		~HashTable()
		{
			for (size_t i = 0; i < _table.size();i++)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;//保存当前节点
					delete cur; //释放当前节点
					cur = next;
				}
				_table[i] = nullptr;
			}
		}

		iterator begin()
		{
			//返回第一个桶里面的第一个节点
			size_t i = 0;
			while (i<_table.size())
			{
				if (_table[i])//不为空就找到第一个桶
				{
					return iterator(_table[i],this);//迭代器需要一个节点的指针,_table[i]就是节点的指针
				    //成员函数this就是自己对象的地址,即this就是哈希表的地址
				}
				++i;
			}
			return end();
		}

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

		//提供一个素数表(来自STL源码)
		size_t GetNextPrime(size_t prime)
		{
			const int PRIMECOUNT = 28;
			static const size_t primeList[PRIMECOUNT] =  //定义成静态的,不用每次重新创建数组
			{
				53ul, 97ul, 193ul, 389ul, 769ul,
				1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
				49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
				1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
				50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
				1610612741ul, 3221225473ul, 4294967291ul
			};

			size_t i = 0;
			for (; i < PRIMECOUNT; ++i)
			{
				//获取一个比当前素数要大的数
				if (primeList[i] > prime)
					return primeList[i];
			}

			return primeList[i];
		}


		pair<iterator,bool> Insert(const T& data)
		{ 
			KeyOfT kot;
			//找到了
			auto ret = Find(kot(data));
			if(ret != end())
				return make_pair(ret,false);

			HashFunc hf;

			//负载因子到1的时候增容
			//负载因子为0的时候要防止除零错误
			if (_n == _table.size())
			{
				vector<Node*> newtable;
				//size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
				//newtable.resize(newSize, nullptr);
				newtable.resize(GetNextPrime(_table.size()));//通过素数表可重新写增容函数

				//遍历取旧表中节点,重新算映射到新表中的位置,挂到新表中
				for (size_t i = 0; i < _table.size(); ++i)
				{
					if (_table[i])//operator[]会检查给的下标是否在0到size这个范围之内
					{
						Node* cur = _table[i];
						while (cur)//若不为空,则把该桶取完,计算其在新表中的位置
						{
							Node* next = cur->_next;//保存cur的下一个节点
							size_t index = hf(kot(cur->_data)) % newtable.size();
							//头插
							cur->_next = newtable[index];
							_table[index] = cur;

							cur = next;
						}
						_table[i] = nullptr;//都处理完后旧表的当前位置可置空
					}
				}
				_table.swap(newtable); //交换新旧表
			}

			size_t index = hf(kot(data)) % _table.size();
			//采用头插法,尾插法还需要找尾,麻烦
			Node* newnode = new Node(data);
			
			newnode->_next = _table[index];
			_table[index] = newnode;
			++_n;

			return make_pair(iterator(newnode, this), true);

		}

		iterator Find(const K& key)
		{
			if (_table.size() == 0)
			{
				return end();
			}
			KeyOfT kot;
			HashFunc hf;
			size_t index = hf(key) % _table.size();
			Node* cur = _table[index];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur,this);//Find的返回值为迭代器
				}
				else
				{
					cur = cur->_next;
				}
			}
			return end();
		}

		bool Erase(const K& key)
		{
			size_t index = hf(kot(key)) % _table.size();
			Node* prev = nullptr;
			Node* cur = _table[index];
			while (cur)
			{
				if (kot(cur->data) == key)
				{
					if (_table[index] == cur)
					{
						_table[index] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					--_n;
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

		void TestHashTable1()
		{
			int a[] = { 1,5,30,100000,100,18,15,7,40,44 };
			HashTable<int, int>ht;
			for (auto e : a)
			{
				ht.Insert(make_pair(e.e));
			}
			ht.Insert(make_pair(25, 25));

		}
	private:
		vector<Node*> _table;
		size_t _n = 0;    //有效数据的个数
	};
}
#pragma once
#include "HashTable.h"

namespace bit
{
        template<class K>
        class unordered_set
        {
                struct SetKeyOfT
                {
                        const K& operator()(const K& k)
                        {
                                return k;
                        }
                };
        public:
                typedef typename OpenHash::HashTable<K, K, SetKeyOfT>::iterator iterator;
                iterator begin()
                {
                        return _ht.begin();
                }

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

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

        private:
                OpenHash::HashTable<K, K, SetKeyOfT> _ht;
        };

        void test_unordered_set1()
        {
                unordered_set<int> us;
                us.insert(200);
                us.insert(1);
                us.insert(2);
                us.insert(33);
                us.insert(50);
                us.insert(60);
                us.insert(243);
                us.insert(6);

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

#pragma once
#include "HashTable.h"

namespace bit
{
    template<class K, class V>
    class unordered_map
    {
        struct MapKeyOfT
        {
            const K& operator()(const pair<K, V>& kv)
            {
                return kv.first;
            }
        };
    public:
        typedef typename OpenHash::HashTable<K, pair<K, V>, MapKeyOfT>::iterator iterator;
        iterator begin()
        {
            return _ht.begin();
        }

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

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

        V& operator[](const K& key)
        {
            pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
            return ret.first->second;
        }

    private:
        OpenHash::HashTable<K, pair<K, V>, MapKeyOfT> _ht;
    };

    void test_unordered_map1()
    {
        unordered_map<string, string> dict;
        dict.insert(make_pair("sort", "排序"));
        dict["left"] = "左边";
        dict["left"] = "剩余";
        dict["map"] = "映射";
        dict["string"] = "字符串ַ";
        dict["set"] = "集合";

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

相关文章:

  • Rethinking the Inception Architecture for Computer Vision--Christian Szegedy
  • 安卓毕业设计成品基于Uniapp+SSM实现的智能课堂管理APP在线学习网
  • 基于metaRTC嵌入式webrtc的H265网页播放器实现(我与metaRTC的缘分)
  • 【设计模式】Java设计模式 - 组合模式
  • Android之Handler(上)
  • 网络协议:网络安全
  • php防止SQL注入的网上二手交易平台的设计与实现毕业设计-附源码241552
  • 美团笔试题目(Java后端5题2小时)
  • HTML期末大学生网页设计作业——奇恩动漫HTML (1页面) HTML CSS JS网页设计期末课程大作业
  • 浅谈如何学习网络编程
  • 【MYSQL】表的增删改查
  • 中国地板工具租赁服务行业竞争态势与经营效益预测报告2022-2028年
  • 查看docker 容器的端口
  • xubuntu16.04系统中隐藏网络连接的弹窗提示
  • 基于HTML的环境网站设计 HTML+CSS环保网站项目实现 带设计说明psd
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • [LeetCode] Wiggle Sort
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • ES6 学习笔记(一)let,const和解构赋值
  • express如何解决request entity too large问题
  • Git同步原始仓库到Fork仓库中
  • Laravel5.4 Queues队列学习
  • MySQL数据库运维之数据恢复
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • quasar-framework cnodejs社区
  • Unix命令
  • 从输入URL到页面加载发生了什么
  • 分布式任务队列Celery
  • 简单基于spring的redis配置(单机和集群模式)
  • 力扣(LeetCode)357
  • 数据结构java版之冒泡排序及优化
  • 微服务框架lagom
  • 用jquery写贪吃蛇
  • 正则学习笔记
  • 最近的计划
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • #define、const、typedef的差别
  • (11)MSP430F5529 定时器B
  • (八)c52学习之旅-中断实验
  • (补)B+树一些思想
  • (二)linux使用docker容器运行mysql
  • (附源码)springboot 房产中介系统 毕业设计 312341
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (十六)一篇文章学会Java的常用API
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (原創) 如何將struct塞進vector? (C/C++) (STL)
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • 、写入Shellcode到注册表上线
  • .Net - 类的介绍
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .Net 路由处理厉害了
  • .NET 指南:抽象化实现的基类