C++---哈希

news2025/7/9 0:39:05

目录

1. unordered系列关联式容器

1.1 unordered_map

1.1.1 unordered_map的介绍

1.1.2 unordered_map的接口说明

1.2 unordered_set

2. 底层结构

2.1 哈希概念

2.2 哈希冲突

2.3 哈希函数

2.4 哈希冲突解决

2.4.1 闭散列

2.4.2 开散列

3. 封装unorder_map和unorder_set

3.1 unorder_map 的封装

3.2 unorder_set 的封装

3.4 unordered_map 加迭代器的封装

3.5 unordered_set 加迭代器的封装

1. unordered系列关联式容器

1.1 unordered_map

1.1.1 unordered_map的介绍

        1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
        2. 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此
键关联。键和映射值的类型可能不同。
        3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
        4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围
代方面效率较低
        5. unordered_map实现了直接访问操作符(operator[ ]),它允许使用key作为参数直接访问
value。
        6. 它的迭代器是正向迭代器

1.1.2 unordered_map的接口说明

1. unordered_map的构造

函数声明功能介绍
unordered_map构造不同格式的unordered_map对象

2. unordered_map的容量

函数声明功能介绍
bool empty() const检测unordered_map是否为空
size_t size() const获取unordered_map的有效元素个数

3. unordered_map的迭代器

函数声明功能介绍
begin返回unordered_map第一个元素的迭代器
end返回unordered_map最后一个元素下一个位置的迭代器
cbegin返回unordered_map第一个元素的const迭代器
cend返回unordered_map最后一个元素下一个位置的const迭代器

4. unordered_map的元素访问

函数声明功能介绍
operator[]返回与key对应的value,没有一个默认值

5. unordered_map的查询

函数声明功能介绍
iterator find(const K& key)返回key在哈希桶中的位置
size_t count(const K& key)返回哈希桶中关键码为key的键值对的个数

注意:unordered_map中key是不能重复的,因此count函数的返回值最大为1
 

6. unordered_map的修改操作

函数声明功能介绍
insert向容器中插入键值对
erase删除容器中的键值对
void clear()清空容器中有效元素个数
void swap(unordered_map&)交换两个容器中的元素

7. unordered_map的桶操作

函数声明功能介绍
size_t bucket_count()const返回哈希桶中桶的总个数
size_t bucket_size(size_t n)const返回n号桶中有效元素的总个数
size_t bucket(const K& key)返回元素key所在的桶号

1.2 unordered_set

与unordered_map类似,详情见文档unordered_set

2. 底层结构

unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构

2.1 哈希概念

        顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N)平衡树中为树的高度,即
O(log_2 N)搜索的效率取决于搜索过程中元素的比较次数

        理想的搜索方法可以不经过任何比较,一次直接从表中得到要搜索的元素

        如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

插入元素:
根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。

搜索元素:
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置
取元素比较,若关键码相等,则搜索成功

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称
为哈希表(Hash Table)(或者称散列表)。

例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:hash(key) = key % capacity ; capacity为存储元素底层空间总的大小。

问题:如果继续向上述哈希表中插入44,会出现什么问题?

2.2 哈希冲突

        上述问题中,不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突
哈希碰撞

        把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

        发生哈希冲突该如何处理呢?

2.3 哈希函数

引起哈希冲突的一个原因可能是:哈希函数设计不够合理

哈希函数设计原则:
        哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值
域必须在0到m-1之间
        哈希函数计算出来的地址能均匀分布在整个空间中
        哈希函数应该比较简单

常见哈希函数:

1. 直接定址法--(常用)
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀
缺点:需要事先知道关键字的分布情况
使用场景:适合查找比较小且连续的情况

例题:字符串中的第一个唯一字符

class Solution {
public:
    int firstUniqChar(string s) {
        int hash[256] = {0};
        for(int i = 0; i < s.size(); i++){
            hash[s[i]]++;
        }
        for(int i = 0; i < s.size(); i++){
            if(hash[s[i]] == 1){
                return i;
            }
        }
        return -1;
    }
};

2. 除留余数法--(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,
按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。

 注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

2.4 哈希冲突解决

解决哈希冲突两种常见的方法是闭散列开散列

2.4.1 闭散列

        闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去

那如何寻找下一个空位置呢?

1. 线性探测

        比如2.1中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,
因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。

线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

插入:
        通过哈希函数获取待插入元素在哈希表中的位置
        如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。

删除:
        采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素
会影响其他元素的搜索
。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。

// 哈希表每个空间给个标记
// EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
enum State
{EMPTY, EXIST, DELETE};

载荷因子:

        为了解决 “哈希表什么情况下进行扩容?如何扩容?” 的问题,我们引入载荷因子的概念。

        载荷因子 = 表中元素个数 / 散列表容量

        载荷因子越大,表中元素越多,产生冲突可能性越大;

        载荷因子越小,表中元素越少,产生冲突可能性越小;

        当达到载荷因子我们就扩容,如果载荷因子设置的太小,空间资源浪费就多,载荷因子设置越大,产生冲突的可能性就越大。因此,一般设置载荷因子为 0.7——0.8。

线性探测简要实现哈希表代码如下:

enum State//标志位
{
	EMPTY,
	EXIST,
	DELETE
};

template<class K,class V>
struct HashData//节点元素包含键值对和标志位
{
	pair<K, V> _kv;
	State _state = EMPTY;
};

template<class K>
struct HashFunc//用于方便比较的仿函数
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//特化
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			//防止出现abc = cba 这种情况,有大佬提出加每个字符前*131
			val *= 131;
			val += ch;
		}
		return val;
	}
};

//因为如果K为string类型,无法用 % 来实现映射关系,因此我们写个Hash仿函数
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (Find(kv.first))
			return false;

		//表为空或载荷因子超70%就扩容
		if (_tables.size() == 0 || 10 * _size / _tables.size() >= 7)
		{
			size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
			HashTable<K, V,Hash> newHT;
			newHT._tables.resize(newSize);
			//旧表数据映射到新表
			for (auto e : _tables)
			{
				if (e._state == EXIST)
				{
					newHT.Insert(e._kv);
				}
			}
			_tables.swap(newHT._tables);
		}

		Hash hash;
		size_t hashi = hash(kv.first) % _tables.size();
		//线性探测
		while (_tables[hashi]._state == EXIST)
		{
			hashi++;
			hashi %= _tables.size();
		}
		_tables[hashi]._kv = kv;
		_tables[hashi]._state = EXIST;
		++_size;

		return true;
	}

	HashData<K,V>* Find(const K& key)
	{
		if (_tables.size() == 0)
		{
			return nullptr;
		}

		Hash hash;
		size_t hashi = hash(key) % _tables.size();
		while (_tables[hashi]._state != EMPTY)
		{
			if (_tables[hashi]._state != DELETE && _tables[hashi]._kv.first == key)
			{
				return &_tables[hashi];
			}
			hashi++;
			hashi %= _tables.size();
		}
		return nullptr;
	}

	bool Erase(const K& key)
	{
		HashData<K, V>* ret = Find(key);
		if (ret)
		{
			ret->_state = DELETE;
			--_size;
			return true;
		}
		else
		{
			return false;
		}
	}


private:
	vector<HashData<K, V>> _tables;
	size_t _size = 0;//存储多少有效数据
};

总结

        线性探测优点:实现非常简单
        线性探测缺点:某个位置冲突很多的情况下,互相占用,冲突一片,如下图:

2. 二次探测

二次探测不是探测二次,是探测2的 i 次方:

 二次探测只需要把原来线性探测略微改动一下即可:

bool Insert(const pair<K, V>& kv)
	{
		if (Find(kv.first))
			return false;

		//表为空或载荷因子超70%就扩容
		if (_tables.size() == 0 || 10 * _size / _tables.size() >= 7)
		{
			size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
			HashTable<K, V,Hash> newHT;
			newHT._tables.resize(newSize);
			//旧表数据映射到新表
			for (auto e : _tables)
			{
				if (e._state == EXIST)
				{
					newHT.Insert(e._kv);
				}
			}
			_tables.swap(newHT._tables);
		}

		Hash hash;
		size_t start = hash(kv.first) % _tables.size();
		size_t i = 0;
		size_t hashi = start;
		//二次探测
		while (_tables[hashi]._state == EXIST)
		{
			++i;
			hashi = start + i * i;
			hashi %= _tables.size();
		}
		_tables[hashi]._kv = kv;
		_tables[hashi]._state = EXIST;
		++_size;

		return true;
	}

2.4.2 开散列

开散列概念

        开散列法又叫链地址法(拉链法/哈希桶),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链
接起来,各链表的头结点存储在哈希表中。

哈希桶简单实现:

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

//特化
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			//防止出现abc = cba 这种情况,有大佬提出加每个字符前*131
			val *= 131;
			val += ch;
		}
		return val;
	}
};

namespace HashBucket
{
	template<class K, class V>
	struct HashNode
	{
		pair<K, V> _kv;
		HashNode<K, V>* _next;

		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()
		{
			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;
			}
			Hash hash;
			//负载因子到1就扩容
			if (_size == _tables.size())
			{
				size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newTables;
				newTables.resize(newSize, nullptr);
				//旧表中节点移动映射到新表
				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;
			++_size;

			return true;
		}
		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return nullptr;
			}

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

		bool Erase(const K& key)
		{
			if (_tables.size() == 0)
			{
				return false;
			}

			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					// 1、头删
					// 2、中间删
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}

					delete cur;
					--_size;
					return true;
				}

				prev = cur;
				cur = cur->_next;
			}

			return false;
		}

		size_t Size()
		{
			return _size;
		}

		// 表的长度
		size_t TablesSize()
		{
			return _tables.size();
		}

		// 桶的个数
		size_t BucketNum()
		{
			size_t num = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					++num;
				}
			}

			return num;
		}

		size_t MaxBucketLenth()
		{
			size_t maxLen = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				size_t len = 0;
				Node* cur = _tables[i];
				while (cur)
				{
					++len;
					cur = cur->_next;
				}

				//if (len > 0)
					//printf("[%d]号桶长度:%d\n", i, len);

				if (len > maxLen)
				{
					maxLen = len;
				}
			}

			return maxLen;
		}
	private:
		vector<Node*> _tables;
		size_t _size = 0;//存储有效数据个数
	};
}

3. 封装unorder_map和unorder_set

简单修改上述哈希桶用于封装实现unorder_map和unorder_set.

首先,我们将原来节点中用来存储节点值的 pair键值对 改为 模板参数 T.


// 因为关联式容器中存储的是<key, value>的键值对,因此
// k为key的类型,
// T: 如果是map,则为pair<K, V>; 如果是set,则为k
// KeyOfT: 通过T来获取key的一个仿函数类
namespace Bucket
{
	template<class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;

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

	template<class K,class T, class Hash, class KeyOfT>
	class HashTable
	{
		typedef HashNode<T> Node;
	public:
		~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 T& data)
		{
			Hash hash;
			KeyOfT kot;
			//去重
			if (Find(kot(data)))
			{
				return false;
			}
			//负载因子到1就扩容
			if (_size == _tables.size())
			{
				size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newTables;
				newTables.resize(newSize, nullptr);
				//旧表中节点移动映射到新表
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hash(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}

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

			return true;
		}
		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return nullptr;
			}

			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			if (_tables.size() == 0)
			{
				return false;
			}

			KeyOfT kot;
			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					// 1、头删
					// 2、中间删
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}

					delete cur;
					--_size;
					return true;
				}

				prev = cur;
				cur = cur->_next;
			}

			return false;
		}

		size_t Size()
		{
			return _size;
		}

		// 表的长度
		size_t TablesSize()
		{
			return _tables.size();
		}

		// 桶的个数
		size_t BucketNum()
		{
			size_t num = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					++num;
				}
			}

			return num;
		}

		size_t MaxBucketLenth()
		{
			size_t maxLen = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				size_t len = 0;
				Node* cur = _tables[i];
				while (cur)
				{
					++len;
					cur = cur->_next;
				}

				//if (len > 0)
					//printf("[%d]号桶长度:%d\n", i, len);

				if (len > maxLen)
				{
					maxLen = len;
				}
			}

			return maxLen;
		}
	private:
		vector<Node*> _tables;
		size_t _size = 0;//存储有效数据个数
	};
}

3.1 unorder_map 的封装

#include"HashTable.h"//HashTable.h即为上面哈希桶的实现

namespace zj
{
	template<class K, class V, class Hash = HashFunc<K>>
	class unorder_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		bool Insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}
	private:
		Bucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT> _ht;
	};
}

3.2 unorder_set 的封装

#include"HashTable.h"//HashTable.h即为上面哈希桶的实现

namespace zj
{
	template<class K, class Hash = HashFunc<K>>
	class unorder_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		bool Insert(const K& key)
		{
			return _ht.Insert(key);
		}
	private:
		Bucket::HashTable<K, K, Hash, SetKeyOfT> _ht;
	};
}

3.3 哈希桶迭代器模板

namespace Bucket
{
	template<class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;

		HashNode(const T& data)
			:_data(data)
			,_next(nullptr)
		{}
	};
	//前置声明,不然__HashIterator里定义不了_pht
	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 HashTable<K, T, Hash, KeyOfT> HT;
		typedef __HashIterator<K, T, Hash, KeyOfT> Self;

		Node* _node;
		HT* _pht;

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

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

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

		Self& operator++()
		{
			if (_node->_next)
			{
				// 当前桶中迭代
				_node = _node->_next;
			}
			else
			{
				// 找下一个桶
				Hash hash;
				KeyOfT kot;
				size_t i = hash(kot(_node->_data)) % _pht->_tables.size();
				++i;
				for (; i < _pht->_tables.size(); ++i)
				{
					if (_pht->_tables[i])
					{
						_node = _pht->_tables[i];
						break;
					}
				}

				// 说明后面没有有数据的桶了
				if (i == _pht->_tables.size())
				{
					_node = nullptr;
				}
			}

			return *this;
		}

		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 Hash, class KeyOfT>
	class HashTable
	{
		typedef HashNode<T> Node;

		template<class K, class T, class Hash, class KeyOfT>//模板参数友元要带声明
		friend struct __HashIterator;
	public:
		typedef __HashIterator<K, T, Hash, KeyOfT> iterator;

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

			return end();
		}

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

		~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)
		{
			Hash hash;
			KeyOfT kot;

			// 去重
			iterator ret = Find(kot(data));
			if (ret != end())
			{
				return make_pair(ret, false);
			}

			//负载因子到1就扩容
			if (_size == _tables.size())
			{
				size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newTables;
				newTables.resize(newSize, nullptr);
				//旧表中节点移动映射到新表
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hash(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}

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

			return make_pair(iterator(newnode, this), true);
		}
		iterator Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return end();
			}

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

		bool Erase(const K& key)
		{
			if (_tables.size() == 0)
			{
				return false;
			}

			KeyOfT kot;
			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					// 1、头删
					// 2、中间删
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}

					delete cur;
					--_size;
					return true;
				}

				prev = cur;
				cur = cur->_next;
			}

			return false;
		}

        ....

	private:
		vector<Node*> _tables;
		size_t _size = 0;//存储有效数据个数
	};

}

3.4 unordered_map 加迭代器的封装

#include"HashTable.h"

namespace zj
{
	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename Bucket::HashTable<K, pair<K, V>, Hash, 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:
		Bucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT> _ht;
	};

	void test_map()
	{
		unordered_map<string, string> dict;
		dict.Insert(make_pair("sort", ""));
		dict.Insert(make_pair("string", ""));
		dict.Insert(make_pair("left", ""));

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

	}
}

3.5 unordered_set 加迭代器的封装

#include"HashTable.h"

namespace zj
{
	template<class K, class Hash = HashFunc<K>>
	class unorder_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename Bucket::HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;

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

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

		pair<iterator, bool> Insert(const K& key)
		{
			return _ht.Insert(key);
		}
	private:
		Bucket::HashTable<K, K, Hash, SetKeyOfT> _ht;
	};

	void test_set()
	{
		unordered_set<int> s;
		s.insert(2);
		s.insert(3);
		s.insert(1);
		s.insert(2);
		s.insert(5);

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

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

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

相关文章

MySQL增删改查进阶 — 表的设计

文章目录表的设计1.设计思路2.实体固定关系的套路2.1 一对一关系2.2 一对多关系2.3 多对多关系3.总结表的设计 表的设计实际上要做的工作就是明确一个程序里&#xff0c;需要使用几个数据库&#xff0c;几个表&#xff0c;表里都有哪些列。 1.设计思路 先明确实体再明确实体…

6. Design A Web Crawler

title: Notes of System Design No.10 — Design a Web Crawler description: ‘Design a Web Crawler’ date: 2022-05-13 18:01:58 tags: 系统设计 categories: 系统设计 00. What is Web Crawler? Q &#xff1a;uh just for now lets just do html pagesbut your web cr…

Explaining Deepfake Detection by Analysing Image Matching 翻译

点击查看对应的代码 摘要 本文旨在解释深度伪造检测模型在仅由二进制标签做有监督时如何学习图像的伪迹特征。为此&#xff0c;从图像匹配的角度提出如下三个假设。1、深度伪造检测模型表明基于视觉概念的真/假图片既不与源图片相关也不与目标图片相关而是与伪迹图片相关。2、…

全链路压测效能10倍提升的压测工具实践笔记

背景 创业型公司或创新型项目往往团队资源有限&#xff0c;人员能力水平有限&#xff0c;难以投入专业自动化压测人员&#xff1b; 同时部分业务&#xff08;tob/toc场景&#xff09;长期有中小型活动场景带来小规模流量并发&#xff0c;需要产研能长期保障并及时感知和解决网…

GitHub Star70K登顶,字节内部数据结构与算法笔记,限时上线

为什么学算法 不得不说&#xff0c;现在几乎所有的大厂&#xff0c;比如Google、字节、BAT&#xff0c;面试的时候都喜欢考算法、让人现场写代码&#xff0c;那你有没有真正地想过&#xff0c;为什么这些大公司都喜欢考算法呢&#xff1f; 经常有人说&#xff0c;程序员35岁之…

Java毕业设计MVC:基于SSM实现计算机硬件评测交流平台

作者主页&#xff1a;编程千纸鹤 作者简介&#xff1a;Java、前端、Pythone开发多年&#xff0c;做过高程&#xff0c;项目经理&#xff0c;架构师 主要内容&#xff1a;Java项目开发、毕业设计开发、面试技术整理、最新技术分享 收藏点赞不迷路 关注作者有好处 项目编号&…

(杂)网易云歌单导入到apple music

喜欢apple music的简洁&#xff0c;就想着把网易云的歌单捣鼓进去。 获取歌单歌曲列表&#xff1a;https://yyrcd.com/n2s/ 转移歌单&#xff1a;https://soundiiz.com/zh/&#xff0c;首次使用需要注册&#xff0c;免费版只能一次导入200首。 平台选择apple music 登录授权即可…

Linux下 man命令的使用 及 中文man手册的安装

文章目录1. man命令使用2. 安装中文man手册1. man命令使用 man命令是Linux下最核心的命令之一。而man命令也并不是英文单词“man”的意思&#xff0c;它是单词manual的缩写&#xff0c;即使用手册的意思。man命令会列出一份完整的说明。其内容包括命令语法、各选项的意义及相关…

第三章 线性模型

3.1 基本形式 给定由d个属性描述的示例x(x1; x2; x3; … ; xd)。线性模型试图学得一个通过属性的线性组合来进行预测的函数&#xff0c;即 3.2 线性回归 线性回归试图学得一个线性模型尽可能准确地预测实值输出标记。 对于如何确定w和b&#xff0c;均方误差是回归任务中最常…

这样做时间轴,让你的PPT更出彩!

文章目录**▌方法一&#xff1a;美化时间节点****▌方法二&#xff1a;利用图片中的“轴”****▌方法三&#xff1a;时间轴不一定需要“轴”****▌方法四&#xff1a;把时间轴拆成数页****▌总结**已剪辑自: https://zhuanlan.zhihu.com/p/56672211 嗨&#xff0c;大家好&#…

【Linux】一万七千字详解 —— 基本指令(二)

文章目录前言man 指令cp 指令mv 指令echo 指令(含输出重定向)cat 指令(含输入重定向)wc 指令more 指令less 指令head 和 tail 指令(含管道用法)date 指令cal 指令sort 指令find 和 which 和 whereis 指令alias 指令grep 指令top 指令zip 和 unzip 指令结语前言 今天的主要内容…

C语言源代码系列-管理系统之学生选修课程系统

往期文章分享点击跳转>《导航贴》- Unity手册&#xff0c;系统实战学习点击跳转>《导航贴》- Android手册&#xff0c;重温移动开发 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过…

想归隐啦——与自然生活为伴

目录 一、陶渊明-桃花源记 二、梭罗-瓦尔登湖 结庐在人境&#xff0c;而无车马喧。问君何能尔&#xff1f;心远地自偏。采菊东篱下&#xff0c;悠然见南山 一、陶渊明-桃花源记 晋太元中&#xff0c;武陵人捕鱼为业。缘溪行&#xff0c;忘路之远近。忽逢桃花林&#xff0c;夹…

第五届“传智杯”全国大学生计算机大赛(练习赛)传智杯 #5 练习赛] 平等的交易

[传智杯 #5 练习赛] 平等的交易 题目描述 你有 nnn 件道具可以买&#xff0c;其中第 iii 件的价格为 aia_iai​。 你有 www 元钱。你仅能用钱购买其中的一件商道具。当然&#xff0c;你可以拿你手中的道具换取其他的道具&#xff0c;只是这些商道具的价值之和&#xff0c;不…

Vuecli项目结构,及组件的使用

根目录文件介绍 node_modules &#xff1a;管理项目中使用的依赖 public&#xff1a;存放一些静态资源&#xff0c;webpack打包时会放入dist文件夹内。 src&#xff1a;书写vue源代码【重点】 .gitignore &#xff1a;存放需要被git忽略文件&#xff08;不需要保存&#xff09;…

[附源码]java毕业设计青少年计算机知识学习系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

ArcGIS制作横向图例

ArcGIS制作横向图例 右键栅格图层&#xff0c;Symbology——Stretched 切换布局视图——View——Layout View&#xff0c;添加经纬网并调整 Insert——Legend&#xff0c;一路默认点下去 双击图例&#xff0c;Items——Style&#xff0c;进一步调整 选择图中标注的这个样式&am…

ES6 Symbol 内置值及使用场景

Symbol 基本使用 ES6 引入了一种新的原始数据类型 Symbol&#xff0c;表示独一无二的值。它是 JavaScript 语言的第七种数据类型&#xff0c;是一种类似于字符串的数据类型。 Symbol 特点 1) Symbol 的值是唯一的&#xff0c;用来解决命名冲突的问题 2) Symbol 值不能与其…

Spring之Bean的实例化

文章目录前言一、通过构造方法实例化二、通过简单工厂模式实例化三、通过factory-bean实例化四、通过FactoryBean接口实例化前言 Spring为Bean提供了多种实例化方法&#xff0c;通常包括四种方式。目的是&#xff1a;更加灵活 第一种&#xff1a;通过构造方法实例化第二种&am…

计算机网络——第五章网络层笔记(4)

距离矢量路由选择协议&#xff08;DV算法&#xff09; 每个路由器维护一张表&#xff0c;表中列出了当前已知的到每个目标的最佳距离&#xff0c;以及为了到达那个目标&#xff0c;应该从哪个接口转发。 DV算法是动态的和分布式的&#xff0c;它常被用于小型网络&#xff0c;…