【C++】哈希

news2024/12/31 6:27:47

哈希

  • 一、unordered系列关联式容器
  • 二、哈希原理
    • 2.1 哈希映射
    • 2.2 哈希冲突
      • 2.2.1 闭散列—开放地址法
      • 2.2.2 代码实现
      • 2.2.3 开散列—拉链法
      • 2.2.4 代码实现
  • 三、哈希封装unordered_map/unordered_set
    • 3.1 基本框架
    • 3.2 迭代器实现
      • 3.2.3 operator*和operator->和operator!=
      • 3.2.4 operator++与构造函数
      • 3.2.5 begin()和end()
      • 3.2.6 operator[]
      • 3.2.7 const迭代器问题
  • 四、哈希源码

一、unordered系列关联式容器

map和set的底层是用红黑树实现的,在最差的情况下也能在高度次查询到节点。但是当节点数量非常多的时候,效率并不理想,所以C++11引入了unorderedmap与unorderedset,能极快的查找到元素节点,但是它们的底层不是用搜索树实现的,所以不能保证有序

二、哈希原理

2.1 哈希映射

红黑树我们需要进行比较查找才能找到对应节点。而进过哈希映射函数,让key值跟存储位置建立映射关系,那么在查找时通过该函数可以很快找到该元素。
像计数排序就可以看作一个简单的哈希映射,叫做直接定址法,但是得范围集中才行。
如果范围不集中,就可以用除留余数法,我们可以用元素的值去模上容器的大小。这样所有的元素就一定能存入表中。

2.2 哈希冲突

上面的除留余数法可能会导致两个元素要存储在同一个位置。我们把这种情况称为哈希冲突。而解决哈希冲突的方法有两种

2.2.1 闭散列—开放地址法

闭散列的大致方法就是:当映射的地方已经有值了,那么就按规律找其他位置。而查找空位的方法又分为线性探测和二次探测

【线性探测】
插入:
用除留余数法求出key值的关键码,并将它放到对应的位置上。如果该位置已经存在数据被占用了,那么继续寻找下一个位置,也就是+1的位置,如果+1的位置已经有数据,那么继续+1,直到寻找到下一个空位置为止。

查找:
查找就是取余后往后探索,知道找到空位置就停止,这里要注意如果删除了一个数据,而要查找的元素在删除位置的后边,就会在删除的地方停下来,导致本来存在的元素查找不到。
解决这种情况的方式:
可以再设置一种状态(枚举),将数组中每个数据的状态记录一下,所以就有了存在,空和删除这三种状态。删除的位置状态时删除,查找的时候不会停下。
这里要注意有一种情况是整个闭散列全部都存在或者为删除状态(边插入边删除不会扩容),所以最多循环一圈

负载因子:
表中的有效数据个数/表的大小,载荷因子不能超过1。为了减小冲突,一般到0.7就会扩容。

字符串哈希:
这里要注意如果key是字符串就不能使用除法取余,所以我们需要一个仿函数把字符串转换成数字。

【二次探测】
我们知道线性探测如果发生了冲突并且冲突连在一起就会引起数据堆积,导致搜索效率降低,为了解决这种情况,就有了二次探测。
二次探测的方法就是以i的2次方去进行探测,如果要找的位置Idx被占,下次找Idx + 1^2,如果再次被占,则找Idx + 2^2,以此类推。

2.2.2 代码实现

// 状态
enum Sta
{
	EXIST,
	DELETE,
	EMPTY,
};

// 数据类型
template <class K, class V>
struct HashData
{
	pair<K, V> _kv;
	Sta _state = EMPTY;
};

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

// 字符串哈希
template <>
struct HashKey<string>
{
	size_t operator()(const string& key)
	{
		size_t ans = 0;
		for (int i = 0; i < key.size(); i++)
		{
			ans *= 131;
			ans += i;
		}
		return ans;
	}
};

// 哈希表结构
template <class K, class V, class GetKey = HashKey<K>>
class HashTable
{
	typedef HashData<K, V> data;
public:
	HashTable()
		: _n(0)
	{
		_tables.resize(7);
	}

	bool insert(const pair<K, V>& kv)
	{
		// 重复
		if (find(kv.first)) return false;
		// 负载因子
		size_t load = _n * 10 / _tables.size();
		if (load >= 10)
		{
			// 出作用域后销毁
			HashTable<K, V> newhash;
			newhash._tables.resize(2 * _tables.size());
			for (auto& e : _tables)
			{
				if (e._state == EXIST)
				{
					newhash.insert(e._kv);
				}
			}
			_tables.swap(newhash._tables);
		}
		GetKey Get;
		size_t hashI = Get(kv.first) % _tables.size();
		while (_tables[hashI]._state == EXIST)
		{
			++hashI;
			hashI %= _tables.size();
		}
		_tables[hashI]._kv = kv;
		_tables[hashI]._state = EXIST;
		++_n;
		return true;
	}

	data* find(const K& key)
	{
		GetKey Get;
		size_t hashI = Get(key) % _tables.size();
		size_t startI = hashI;// 最多循环一圈
		while (_tables[hashI]._state != EMPTY)
		{
			if (_tables[hashI]._state == EXIST
				&& _tables[hashI]._kv.first == key)
			{
				return &_tables[hashI];
			}
			++hashI;
			hashI %= _tables.size();
			if (hashI == startI) break;
		}
		return nullptr;
	}

	bool erase(const K& key)
	{
		data* node = find(key);
		if (node)
		{
			node->_state = DELETE;
			return true;
		}
		return false;
	}
private:
	vector<data> _tables;
	size_t _n;// 有效数据个数
};

2.2.3 开散列—拉链法

闭散列解决哈希冲突的办法就是抢占别人的位置,而开散列不一样,冲突的元素可以一起在同一个位置。
首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
在这里插入图片描述
【增容】
随着插入的数量增加,可能导致一个桶的节点数目非常多,为了应对这种情况,在一定情况下需要增容。一般当负载因子为1的时候扩容。

2.2.4 代码实现

template <class K, class V>
struct HashNode
{
	HashNode(const pair<K, V> kv)
		: _kv(kv)
		, _next(nullptr)
	{}

	pair<K, V> _kv;
	HashNode<K, V>* _next;
};

template <class K, class V, class GetKey = HashKey<K>>
class HashTable
{
	typedef HashNode<K, V> Node;
public:
	HashTable()
		: _n(0)
	{
		_tables.resize(10);
	}

	~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;
		}
	}

	bool insert(const pair<K, V>& kv)
	{
		// 重复
		if (find(kv.first))
			return false;
		// 负载因子为1扩容
		if (_tables.size() == _n)
		{
			vector<Node*> newtable;
			newtable.resize(2 * _n);
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					size_t idx = GetKey()(cur->_kv.first) % newtable.size();
					cur->_next = newtable[idx];
					newtable[idx] = cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
			_tables.swap(newtable);
		}

		GetKey Get;
		size_t hashI = GetKey()(kv.first) % _tables.size();
		Node* newnode = new Node(kv);
		newnode->_next = _tables[hashI];
		_tables[hashI] = newnode;
		++_n;
		return true;
	}

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

	bool erase(const K& key)
	{
		size_t idx = GetKey()(key) % _tables.size();
		Node* cur = _tables[idx];
		Node* pre = nullptr;
		while (cur)
		{
			if (cur->_kv.first == key)
			{
				if (pre = nullptr)
				{
					_tables[i] = nullptr;
				}
				else
				{
					pre->_next = cur->_next;
				}
				delete cur;
				--_n;
				return true;
			}
			else
			{
				pre = cur;
				cur = cur->_next;
			}
		}
		return false;
	}
private:
	vector<Node*> _tables;
	size_t _n = 0;
};

三、哈希封装unordered_map/unordered_set

3.1 基本框架

这里的封装跟红黑树的类似,我们需要改变一下节点的结构,不管传入的是key还是pair,都用模板参数T接收。

template <class T>
struct HashNode
{
	HashNode(const T& data)
		: _data(data)
		, _next(nullptr)
	{}

	T _data;
	HashNode<T>* _next;
};

根据前面红黑树的封装可以知道传入的时候第一个参数主要用来查找和删除,第二个参数决定节点是什么类型。
代码如下:

// unordered_Set.h
template <class K, class Hash = HashKey<K>>
class unordered_set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
bool insert(const K& key)
{
	return _ht.insert(key);
}
private:
	hashbucket::HashTable<K, K, Hash, SetKeyOfT> _ht;
};

// unordered_Map.h
template <class K, class V, class Hash = HashKey<K>>
class unordered_map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	bool insert(const T& data)
	{
		return _ht.insert(data);
	}
private:
	hashbucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT> _ht;
};

3.2 迭代器实现

3.2.3 operator*和operator->和operator!=

解引用operator*是将一个指针指向的内容取出来,它返回的是哈希节点的数据。operator->是将指针指向内容的地址取出来,也就是节点指向数据的地址。

T& operator*()
{
	return _node->_data;
}

T* operator->()
{
	return &_node->_data;
}

bool operator!=(const self& it) const
{
	return _node != it._node;
}

3.2.4 operator++与构造函数

如果这个桶没有走完,那么直接遍历当前迭代器指向节点的下一个节点。如果这个桶走完了,要遍历下一个桶。但是既然要遍历,那么一定需要_tables的大小,而_tables又是一个私有成员,随意我们可以用友元类来访问。

template<class K, class T, class GetKey, class KeyOfT>
friend struct HashIterator;// 迭代器需要访问私有

而且我们还需要_tables,所以在构造迭代器的时候要传进来个_tables的指针。

HashIterator(HT* ht, Node* node)// 传递指针
	: _node(node)
	, _ht(ht)
{}

operator++代码如下:

self& operator++()
{
	if (_node->_next)
	{
		_node = _node->_next;
	}
	else
	{
		// 找下一个桶
		KeyOfT kot;
		Hash hash;
		size_t idx = hash(kot(_node->_data)) % _ht->_tables.size();
		idx++;
		while (idx < _ht->_tables.size())
		{
			if (_ht->_tables[idx] != nullptr)
			{
				_node = _ht->_tables[idx];
				break;
			}
			else
			{
				idx++;
			}
		}

		if (idx == _ht->_tables.size())
		{
			_node = nullptr;
		}
	}
	return *this;
}

3.2.5 begin()和end()

begin就是找到_tables表的第一个不为空的桶的头节点,如果找到了,返回第一个位置的迭代器,因为迭代器的构造需要节点指针和哈希表的指针,那么哈希表的指针是什么呢?哈希表的指针就是this,this代表了整个哈希表的指针。将this指针传给迭代器的构造,那么我们就能取到_table。
而end就是最后一个节点的下一个地址,也就是nullptr。

template <class K, class T, class GetKey, class KeyOfT>
class HashTable
{
	typedef HashNode<T> Node;

	template<class K, class T, class GetKey, class KeyOfT>
	friend struct HashIterator;// 迭代器需要访问私有
public:
	typedef HashIterator<K, T, GetKey, KeyOfT> iterator;
	iterator begin()
	{
		for (size_t i = 0; i < _tables.size(); i++)
		{
			if (_tables[i])
			{
				return iterator(this, _tables[i]);
			}
		}
		return iterator(this, nullptr);
	}

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

3.2.6 operator[]

[]在前面的红黑树的封装也出现过,operator[]只有map中有,因为operator[]可以对插入的值进行增加,查找。如果该值第一次出现,那么operator[]充当的是插入,如果该值第二次出现,那么operator[]就充当的是修改。
既然是对是否成功插入做判断,那么insertfind都要做出修改。

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

// HashTable.h
pair<iterator, bool> insert(const T& data)
{
	// 重复
	iterator it = find(KeyOfT()(data));
	if (it != end())
	{
		return make_pair(it, false);
	}
	// 负载因子为1扩容
	if (_tables.size() == _n)
	{
		vector<Node*> newtable;
		newtable.resize(2 * _n);
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				size_t idx = GetKey()(KeyOfT()(data)) % newtable.size();
				cur->_next = newtable[idx];
				newtable[idx] = cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
		_tables.swap(newtable);
	}

	size_t hashI = GetKey()(KeyOfT()(data)) % _tables.size();
	Node* newnode = new Node(data);
	newnode->_next = _tables[hashI];
	_tables[hashI] = newnode;
	++_n;
	return make_pair(iterator(this, newnode), true);
}

iterator find(const K& key)
{
	size_t idx = GetKey()(key) % _tables.size();
	Node* cur = _tables[idx];
	while (cur)
	{
		if (KeyOfT()(cur->_data) == key)
			return iterator(this, cur);
		else
			cur = cur->_next;
	}
	return end();
}

bool erase(const K& key)
{
	size_t idx = GetKey()(KeyOfT()(key)) % _tables.size();
	Node* cur = _tables[idx];
	Node* pre = nullptr;
	while (cur)
	{
		if (cur->_data == key)
		{
			if (pre = nullptr)
			{
				_tables[idx] = nullptr;
			}
			else
			{
				pre->_next = cur->_next;
			}
			delete cur;
			--_n;
			return true;
		}
		else
		{
			pre = cur;
			cur = cur->_next;
		}
	}
	return false;
}

3.2.7 const迭代器问题

在这里插入图片描述
在stl源码中可以看到并没有用以前的方法使用:

typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;

原因是如果使用const版本传递,那么_tables使用[]返回的就是const。而用const迭代器去构造 HT* _ht; Node* _node;就会导致权限放大,无法构造。但是如果改成 const HT* _ht; const Node* _node;,又会导致[]不能修改的问题。

在这里插入图片描述
所以我们需要再写一个const版本迭代器:

template <class K, class T, class Hash, class KeyOfT>
struct ConstHashIterator
{
	typedef HashNode<T> Node;
	typedef ConstHashIterator<K, T, Hash, KeyOfT> self;
	typedef HashTable<K, T, Hash, KeyOfT> HT;

	ConstHashIterator(const HT* ht, const Node* node)// 传递指针
		: _node(node)
		, _ht(ht)
	{}

	const T& operator*() const
	{
		return _node->_data;
	}

	const T* operator->() const
	{
		return &_node->_data;
	}

	bool operator!=(const self& it) const
	{
		return _node != it._node;
	}

	self& operator++()
	{
		if (_node->_next)
		{
			_node = _node->_next;
		}
		else
		{
			// 找下一个桶
			KeyOfT kot;
			Hash hash;
			size_t idx = hash(kot(_node->_data)) % _ht->_tables.size();
			idx++;
			while (idx < _ht->_tables.size())
			{
				if (_ht->_tables[idx] != nullptr)
				{
					_node = _ht->_tables[idx];
					break;
				}
				else
				{
					idx++;
				}
			}

			if (idx == _ht->_tables.size())
			{
				_node = nullptr;
			}
		}
		return *this;
	}
	const HT* _ht;
	const Node* _node;
};

HashTable写入友元类:

template <class K, class T, class Hash, class KeyOfT>
friend struct ConstHashIterator;

添加cbegin()cend()

// unordered_Set.h
typedef typename hashbucket::HashTable<K, K, Hash, SetKeyOfT>::const_iterator const_iterator;

const_iterator cbegin()
{
	return _ht.cbegin();
}

const_iterator cend()
{
	return _ht.cend();
}

//unordered_Map.h
typedef typename hashbucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT>::const_iterator const_iterator;

const_iterator cbegin()
{
	return _ht.cbegin();
}

const_iterator cend()
{
	return _ht.cend();
}

四、哈希源码

unordered_set.h:

#pragma once
#include "HashTable.h"

namespace yyh
{
	template <class K, class Hash = HashKey<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename hashbucket::HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;
		typedef typename hashbucket::HashTable<K, K, Hash, SetKeyOfT>::const_iterator const_iterator;
		iterator begin()
		{
			return _ht.begin();
		}

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

		const_iterator cbegin()
		{
			return _ht.cbegin();
		}

		const_iterator cend()
		{
			return _ht.cend();
		}

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

		bool find(const K& key)
		{
			return _ht.find(key);
		}

		bool erase(const K& key)
		{
			return _ht.erase(key);
		}

	private:
		hashbucket::HashTable<K, K, Hash, SetKeyOfT> _ht;
	};
}

unordered_map.h:

#pragma once
#include "HashTable.h"

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

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

		const_iterator cbegin()
		{
			return _ht.cbegin();
		}

		const_iterator cend()
		{
			return _ht.cend();
		}

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

		bool find(const K& key)
		{
			return _ht.find(key);
		}

		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:
		hashbucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT> _ht;
	};
}

HashTable.h

#pragma once

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

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

// 字符串哈希
template <>
struct HashKey<string>
{
	size_t operator()(const string& key)
	{
		size_t ans = 0;
		for (size_t i = 0; i < key.size(); i++)
		{
			ans *= 131;
			ans += i;
		}
		return ans;
	}
};

namespace hashbucket
{
	template <class T>
	struct HashNode
	{
		HashNode(const T& data)
			: _data(data)
			, _next(nullptr)
		{}

		T _data;
		HashNode<T>* _next;
	};

	// 前置声明
	template <class K, class T, class Hash, class KeyOfT>
	class HashTable;

	template <class K, class T, class Hash, class KeyOfT>
	struct HashIterator
	{
		typedef HashNode<T> Node;
		typedef HashIterator<K, T, Hash, KeyOfT> self;
		typedef HashTable<K, T, Hash, KeyOfT> HT;

		HashIterator(HT* ht, Node* node)// 传递指针
			: _node(node)
			, _ht(ht)
		{}

		T& operator*()
		{
			return _node->_data;
		}

		T* operator->()
		{
			return &_node->_data;
		}

		bool operator!=(const self& it) const
		{
			return _node != it._node;
		}

		self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				// 找下一个桶
				KeyOfT kot;
				Hash hash;
				size_t idx = hash(kot(_node->_data)) % _ht->_tables.size();
				idx++;
				while (idx < _ht->_tables.size())
				{
					if (_ht->_tables[idx] != nullptr)
					{
						_node = _ht->_tables[idx];
						break;
					}
					else
					{
						idx++;
					}
				}

				if (idx == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}

		HT* _ht;
		Node* _node;
	};

	template <class K, class T, class Hash, class KeyOfT>
	struct ConstHashIterator
	{
		typedef HashNode<T> Node;
		typedef ConstHashIterator<K, T, Hash, KeyOfT> self;
		typedef HashTable<K, T, Hash, KeyOfT> HT;

		ConstHashIterator(const HT* ht, const Node* node)// 传递指针
			: _node(node)
			, _ht(ht)
		{}

		const T& operator*() const
		{
			return _node->_data;
		}

		const T* operator->() const
		{
			return &_node->_data;
		}

		bool operator!=(const self& it) const
		{
			return _node != it._node;
		}

		self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				// 找下一个桶
				KeyOfT kot;
				Hash hash;
				size_t idx = hash(kot(_node->_data)) % _ht->_tables.size();
				idx++;
				while (idx < _ht->_tables.size())
				{
					if (_ht->_tables[idx] != nullptr)
					{
						_node = _ht->_tables[idx];
						break;
					}
					else
					{
						idx++;
					}
				}

				if (idx == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}
		const HT* _ht;
		const Node* _node;
	};

	template <class K, class T, class GetKey, class KeyOfT>
	class HashTable
	{
		typedef HashNode<T> Node;

		template<class K, class T, class GetKey, class KeyOfT>
		friend struct HashIterator;// 迭代器需要访问私有

		template <class K, class T, class Hash, class KeyOfT>
		friend struct ConstHashIterator;

	public:
		typedef HashIterator<K, T, GetKey, KeyOfT> iterator;
		typedef ConstHashIterator<K, T, GetKey, KeyOfT> const_iterator;
		iterator begin()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				if (_tables[i])
				{
					return iterator(this, _tables[i]);
				}
			}
			return iterator(this, nullptr);
		}

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

		const_iterator cbegin()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				if (_tables[i])
				{
					return const_iterator(this, _tables[i]);
				}
			}
			return const_iterator(this, nullptr);
		}

		const_iterator cend()
		{
			return const_iterator(this, nullptr);
		}

		HashTable()
			: _n(0)
		{
			_tables.resize(10);
		}

		~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;
			}
		}

		pair<iterator, bool> insert(const T& data)
		{
			// 重复
			iterator it = find(KeyOfT()(data));
			if (it != end())
			{
				return make_pair(it, false);
			}
			// 负载因子为1扩容
			if (_tables.size() == _n)
			{
				vector<Node*> newtable;
				newtable.resize(2 * _n);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t idx = GetKey()(KeyOfT()(data)) % newtable.size();
						cur->_next = newtable[idx];
						newtable[idx] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newtable);
			}

			size_t hashI = GetKey()(KeyOfT()(data)) % _tables.size();
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashI];
			_tables[hashI] = newnode;
			++_n;
			return make_pair(iterator(this, newnode), true);
		}

		iterator find(const K& key)
		{
			size_t idx = GetKey()(key) % _tables.size();
			Node* cur = _tables[idx];
			while (cur)
			{
				if (KeyOfT()(cur->_data) == key)
					return iterator(this, cur);
				else
					cur = cur->_next;
			}
			return end();
		}

		bool erase(const K& key)
		{
			size_t idx = GetKey()(KeyOfT()(key)) % _tables.size();
			Node* cur = _tables[idx];
			Node* pre = nullptr;
			while (cur)
			{
				if (cur->_data == key)
				{
					if (pre = nullptr)
					{
						_tables[idx] = nullptr;
					}
					else
					{
						pre->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				else
				{
					pre = cur;
					cur = cur->_next;
				}
			}
			return false;
		}
	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};
}



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

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

相关文章

【微服务】Ribbon实现负载均衡

目录 1.什么是负载均衡 2.自定义负载均衡 3.基于Ribbon实现负载均衡 Ribbon⽀持的负载均衡策略 4.负载均衡原理 源码跟踪 LoadBalancerIntercepor LoadBalancerClient 5.负载均衡策略IRule 总结 1.什么是负载均衡 通俗的讲&#xff0c; 负载均衡就是将负载&#xff…

环境搭建04-Ubuntu16.04更改conda,pip的镜像源

我常用的pipy国内镜像源&#xff1a; https://pypi.tuna.tsinghua.edu.cn/simple # 清华 http://mirrors.aliyun.com/pypi/simple/ # 阿里云 https://pypi.mirrors.ustc.edu.cn/simple/ #中国科技大学1、将conda的镜像源修改为国内的镜像源 先查看conda安装的信息…

【shell 编程大全】sed详解

sed详解1. 概述 今天单独拉出一章来讲述下sed命令。因为sed命令确实内容太多&#xff0c;不过也是比较灵活的&#xff0c;好了不废话了。我们开始吧 1.2 原理解析 shell脚本虽然功能很多&#xff0c;但是它最常用的功能还是处理文本文件&#xff0c;尤其是在正常的业务操作流程…

4.3 where关键字过滤查询数据

文章目录1.使用WHERE子句2.WHERE子句操作符2.1 使用单个值2.2 不匹配检查2.3 范围值查询2.4 空值检查3.组合WHERE子句3.1 AND操作符3.2 OR操作符3.3 计算次序4.IN操作符5.NOt关键字&#xff15;.注意事项&#xff15;.1 NULL与不匹配&#xff15;.2 SQL过滤与应用过滤&#xff…

【RSA】HTTPS中SSL/TLS握手时RSA前后端加密流程

SSL/TLS层的位置 SSL/TLS层在网络模型的位置&#xff0c;它属于应用层协议。接管应用层的数据加解密&#xff0c;并通过网络层发送给对方。 SSL/TLS协议分握手协议和记录协议&#xff0c;握手协议用来协商会话参数&#xff08;比如会话密钥、应用层协议等等&#xff09;&…

QT中级(6)基于QT的文件传输工具(2)

QT中级&#xff08;6&#xff09;基于QT的文件传输工具&#xff08;2&#xff09;本文实现第一步1 新增功能2 运行效果3 实现思路4 源代码实现这个文件传输工具大概需要那几步&#xff1f;实现多线程对文件的读写实现TCP客户端和服务端实现网络传输 书接上回&#xff1a;QT中级…

27k入职阿里测开岗那天,我哭了,这5个月付出的一切总算没有白费~

先说一下自己的个人情况&#xff0c;计算机专业&#xff0c;16年普通二本学校毕业&#xff0c;经历过一些失败的工作经历后&#xff0c;经推荐就进入了华为的测试岗&#xff0c;进去才知道是接了个外包项目&#xff0c;不太稳定的样子&#xff0c;可是刚毕业谁知道什么外包不外…

沸点 | 实时图数据库技术将赋能银行数字化转型——访同心尚科技总裁王昊

实时图数据库技术将赋能银行数字化转型 ——访同心尚科技总裁王昊 本报记者 赵萌 全国两会召开在即&#xff0c;近日&#xff0c;在多家媒体或研究机构的两会热点话题预测中&#xff0c;“科技创新”“数字经济”位列其中。如何更好发挥信息科技对支持实体经济发展的放大、叠加…

【运维有小邓】Oracle数据库审计

一些机构通常将客户记录、信用卡信息、财务明细之类的机密业务数据存储在Oracle数据库服务器中。这些数据存储库经常因为内部安全漏洞和外部安全漏洞而受到攻击。对这类敏感数据的任何损害都可能严重降低客户对机构的信任。因此&#xff0c;数据库安全性对于任何IT管理员来说都…

webpack.config.js与package.json文件的配置

path要使用绝对路径&#xff0c;通过每次复制文件位置非常麻烦且容易导致问题 使用node中的 写个包名跟入口名称&#xff0c;其他全部回车 此步完成后&#xff0c;自动生成一个package.json包 licence指的是开源&#xff0c;一般不写 安装文件夹需要的依赖 dirname是node自带…

图注意网络GAT理解及Pytorch代码实现【PyGAT代码详细注释】

文章目录GAT代码实现【PyGAT】GraphAttentionLayer【一个图注意力层实现】用上面实现的单层网络测试加入Multi-head机制的GAT对数据集Cora的处理csr_matrix()处理稀疏矩阵encode_onehot()对label编号build graph邻接矩阵构造GAT的推广GAT 题&#xff1a;Graph Attention Netwo…

Netty之ChannelFuture详解

目录 目标 Netty版本 Netty官方API 客户端如何与服务器建立连接&连接成功后的操作方式 实现 如何处理客户端与服务器连接关闭后的操作 正确关闭连接的方式 方法一 方法二 目标 了解Netty如何处理客户端与服务器之间的连接与关闭问题。 Netty版本 <dependency&…

Kafka系列之:Kafka生产者和消费者

Kafka系列之:Kafka生产者和消费者 一、Kafka生产者发送流程二、提高生产者吞吐量三、Kafka消费方式四、Kafka消费者总体工作流程五、按照时间消费Kafka Topic一、Kafka生产者发送流程 batch.size:只有数据积累到batch.size之后,sender才会发送数据,默认16K。linger.ms:如果…

预热:Eyeshot 2023 Beta 正式版不远 Eyeshot 2023 Fem

预热&#xff1a;Eyeshot 2023 Beta 离正式版不远 Eyeshot 2023 Fem 破解版 devDept Software 自豪地宣布推出新的Eyeshot 2023 Beta版本。 现在已经完成了几次迁移&#xff0c;我们有了一个最终的工作区架构&#xff0c;它不再需要设计/设计用户界面分离的对象。正如我们在迁移…

SMPL可视化大杀器,你并不需要下载SMPL就能可视化你的3D Pose

SMPL 是一种3D人体建模方法&#xff0c;现在几乎所有的元宇宙人体建模都是基于此类方法&#xff0c;包括但不限于元宇宙&#xff0c;自动驾驶等领域。它能估计出比较准确的人体3D姿态&#xff0c;得益于海量数据训练的人体3D先验。不仅仅是人体&#xff0c;包括手部&#xff0c…

【Windows应急响应】HW蓝队必备——开机启动项、临时文件、进程排查、计划任务排查、注册表排查、恶意进程查杀、隐藏账户、webshell查杀等

Windows应急响应应急响应的重要性开机启动项temp文件分析浏览器信息分析文件时间属性分析最近打开文件分析进程分析计划任务隐藏账户的发现添加与删除恶意进程发现及关闭补丁信息webshell查杀应急响应的重要性 近年来信息安全事件频发&#xff0c;信息安全的技能、人才需求大增…

linux + jenkins + svn + maven + node 搭建及部署springboot多模块前后端服务

linux搭建jenkins 基础准备 linux配置jdk、maven&#xff0c;配置系统配置文件 vi /etc/profile配置jdk、maven export JAVA_HOME/usr/java/jdk1.8.0_261-amd64 export CLASSPATH.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jarexport MAVEN_H…

【深入浅出 Yarn 架构与实现】4-6 RM 行为探究 - 申请与分配 Container

本小节介绍应用程序的 ApplicationMaster 在 NodeManager 成功启动并向 ResourceManager 注册后&#xff0c;向 ResourceManager 请求资源&#xff08;Container&#xff09;到获取到资源的整个过程&#xff0c;以及 ResourceManager 内部涉及的主要工作流程。 一、整体流程 …

吴恩达机器学习笔记——线性回归

1.模型描述有训练集数据房子面积和卖出的价钱&#xff0c;我们用这组数据来模拟特定面积的房子能够卖出的价钱。这是一个很明显的监督学习&#xff08;supervised learning&#xff09;的例子&#xff0c;因为我们的训练集里包含了正确的结果&#xff08;即房子的卖价&#xff…

非递归迭代实现二叉树前序,中序,后序遍历

文章目录1. 前序遍历2. 中序遍历3. 后序遍历1. 前序遍历 题目链接 解题思路&#xff1a; 非递归遍历一棵树有两点&#xff1a; 1.左路结点 2.左路结点的右子树 什么意思呢&#xff1f; 我们知道前序遍历是按照根&#xff0c;左子树&#xff0c;右子树来的。所以它是先根&…