链地址法(哈希桶)

链地址法(哈希桶)

解决冲突的思路

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

扩容

开放定址法负载因⼦必须⼩于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*而不是自定义类型,所以要自己处理,否则不会被处理;如果析构函数需要自己实现,那么赋值重载和拷贝构造也需要自己实现,深拷贝。
相关推荐
小仇学长18 分钟前
嵌入式八股文面试题(二)C语言算法
c语言·算法·八股文
因兹菜1 小时前
[LeetCode]day21 15.三数之和
数据结构·算法·leetcode
编程就是如此2 小时前
LeetCode Hot100(持续更新中)
算法·leetcode
萌の鱼2 小时前
leetcode 2466. 统计构造好字符串的方案数
数据结构·c++·算法·leetcode
Yolowuwu3 小时前
算法跟练第十一弹——二叉树
java·算法·leetcode
TwilightLemon3 小时前
C++ 使用MIDI库演奏《晴天》
c++
marshalVS3 小时前
前端学习-节点操作(三十五)
学习
云边有个稻草人3 小时前
AI语言模型的技术之争:DeepSeek与ChatGPT的架构与训练揭秘
人工智能·算法·语言模型·chatgpt·deepseek
学游戏开发的4 小时前
UE求职Demo开发日志#29 继续流程实现
笔记·游戏引擎·unreal engine
bbqz0074 小时前
浅说 c++20 cppcoro (三)
c++·c++20·协程·coroutine·co_await·co_yield·cppcoro·co_return