链地址法(哈希桶)

链地址法(哈希桶)

解决冲突的思路

开放定址法中所有的元素都放到哈希表⾥,链地址法中所有的数据不再直接存储在哈希表中,哈希表 中存储⼀个指针 ,没有数据映射这个位置时,这个指针为空,有多个数据映射到这个位置时,我们把这些冲突的数据链接成++⼀个链表,挂在哈希表这个位置下⾯++,链地址法也叫做拉链法或者哈希桶。

扩容

开放定址法负载因⼦必须⼩于1,链地址法的负载因⼦就没有限制了,可以⼤于1。负载因⼦越⼤,哈 希冲突的概率越⾼,空间利⽤率越⾼;负载因⼦越⼩,哈希冲突的概率越低,空间利⽤率越低;stl中 unordered_xxx的最⼤负载因⼦基本控制在1,⼤于1就扩容,我们下⾯实现也使⽤这个⽅式。

下⾯演⽰ {19,30,5,36,13,20,21,12,24,96} 等这⼀组值映射到M=11的表中

h(19) = 8,h(30) = 8,h(5) = 5,h(36) = 3,h(13) = 2,h(20) = 9,h(21) = 10,h(12) = 1,h(24) = 2,h(96) = 88

哈希桶实现代码:

cpp 复制代码
namespace hash_bucket
{
    //仿函数
    template<class K>
    struct HashFunc
    {
        size_t operator()(const K& key)
        {
            return (size_t)key;
        }
    };
	//string用得多,特化一下
    template<>
    struct HashFunc<string>
    {
        size_t operator()(const string& s)
        {
            size_t hash = 0;
            for (auto ch : s)
            {
                hash += ch;
            }
            return hash;
        }
    };
    
	template<class K,class V>
	struct HashNode
	{
		pair<K, V> _kv;
		HashNode<K, V>* _next;//别定义成pair了

		HashNode(const pair<K,V>& kv)
			:_kv(kv)
			,_next(nullptr)
		{}
	};

	template<class K,class V,class Hash=HashFunc<K>> 
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable()
			:_tables(__stl_next_prime(0))
			,_n(0)
		{}

		//vector里面存的是内置类型Node*而不是自定义类型,所以要自己处理
		//如果析构需要自己写那么拷贝构造和赋值重载也得自己写
		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];	
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;//
			}
		}

		Hash hash;//

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

		bool Erase(const K& key)
		{
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;//要记录
			while (cur)
			{
				if (key == cur->_kv.first)
				{
                    //分两种情况处理
					if (prev == nullptr)//要删除的是头结点
					{
						_tables[hashi] = cur->_next;

					}
					else//删除的不是头结点
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

		bool Insert(const pair<K,V>& kv)
		{
			if (_n == _tables.size())
			{
				vector<Node*> newtables(__stl_next_prime(_tables.size())+1);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = hash(cur->_kv.first) % newtables.size();
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newtables);//
			}

			//头插
			size_t hashi = hash(kv.first) % _tables.size();
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;

			return true;
		}

	private:
		vector<Node*> _tables;
		size_t _n=0;

		//自己造轮子
		/*struct Data
		{
			ListNode* _head;
			RBTreeNode* _root;
			size_t len;
		};*/

		//套两个结构在里面
		/*struct Date
		{
			list<pair<K, V>> _list;
			map<pair<K, V>> _map;
			size_t len;
		};

		vector<Data> _tables;
		size_t _n = 0;*/
	};

Tips:

  • 在扩容时,我们不再像开放定址法为了复用Insert而创建一个HashTable而是创建一个新vector,因为Insert里要创建新节点,然后swap之后带走旧表时又要释放这些结点,一来一回消耗比较大;直接用新vector,把原vector挂的结点直接"拿"到新表就可以了。
  • 如果觉得挂的链表太长了,可以换成比如_n>8就换挂红黑树,创建一个结构体Data,里面存储的可以是自己"造轮子"的也可以直接用list和map这两个结构(就可以直接用自带的接口)。如果减到比如8个以下了再换回链表。
  • vector里面存的是内置类型Node*而不是自定义类型,所以要自己处理,否则不会被处理;如果析构函数需要自己实现,那么赋值重载和拷贝构造也需要自己实现,深拷贝。
相关推荐
知识分享小能手2 小时前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
luckys.one2 小时前
第9篇:Freqtrade量化交易之config.json 基础入门与初始化
javascript·数据库·python·mysql·算法·json·区块链
~|Bernard|3 小时前
在 PyCharm 里怎么“点鼠标”完成指令同样的运行操作
算法·conda
战术摸鱼大师3 小时前
电机控制(四)-级联PID控制器与参数整定(MATLAB&Simulink)
算法·matlab·运动控制·电机控制
Christo33 小时前
TFS-2018《On the convergence of the sparse possibilistic c-means algorithm》
人工智能·算法·机器学习·数据挖掘
汇能感知4 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun4 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
好家伙VCC4 小时前
数学建模模型 全网最全 数学建模常见算法汇总 含代码分析讲解
大数据·嵌入式硬件·算法·数学建模
茯苓gao5 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾5 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang