哈希——unordered系列关联式容器

news2024/11/18 8:31:10

 

目录

unordered系列关联式容器

概念

unordered_map

无序+去重

operator[]

unordered_set

无序+去重

OJ练习题

重复n次的元素

两个数组的交集 

两个数的交集二

 底层结构

概念

 哈希冲突

闭散列

结点的定义

扩容

字符串取模

插入

查找

删除

闭散列完整代码 

开散列

结点定义

释放桶(析构函数)

扩容

插入

查找

删除

开散列完整代码

用开散列哈希表封装unordered系列容器

HashTable.h

UnorderSet.h

UnorderMap.h


unordered系列关联式容器

概念

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 log2n,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。map与set的底层是红黑树,然而unordered_map和unordered_set的底层使用的是哈希表。

unordered_map

无序+去重

我们需要注意的是unordered_map中存放的是键值对,并且去重也是根据key来进行去重的。

int main()
{
	unordered_map<int, int> ump;
	ump.insert(make_pair<int, int>(8, 0));
	ump.insert(make_pair<int, int>(6, 0));
	ump.insert(make_pair<int, int>(4, 0));
	ump.insert(make_pair<int, int>(8, 0));
	ump.insert(make_pair<int, int>(5, 0));
	ump.insert(make_pair<int, int>(0, 0));
	ump.insert(make_pair<int, int>(8, 0));
	for (auto e : ump)
	{
		cout << e.first << ":" << e.second << endl;
	}
	return 0;
}

operator[]

operator[]是unordered_map独有的,unordered_set中并没有这个操作符,operator[]的主要作用是通过key可以访问并且修改value的值,非常好用。

int main()
{
	unordered_map<int, int> ump;
	ump.insert(make_pair<int, int>(8, 0));
	ump.insert(make_pair<int, int>(6, 0));
	ump.insert(make_pair<int, int>(4, 0));
	ump.insert(make_pair<int, int>(8, 0));
	ump.insert(make_pair<int, int>(5, 0));
	ump.insert(make_pair<int, int>(0, 0));
	ump.insert(make_pair<int, int>(8, 0));

	ump[5] = 10;
	
	for (auto& e : ump)
	{
		cout << e.first << ":" << e.second << endl;
	}
	return 0;
}

unordered_set

无序+去重

这个跟unordered_map一样,只不过unordered_map传入的是键值对pair<K,V>然而unordered_set传入的则是K(实质是pair<K,V>)。

并且unordered_set并不支持operator[]。

OJ练习题

重复n次的元素

力扣

方法一:巧用unordered_set中的count函数来操作。

count函数具体作用,详见C++—— set、map、multiset、multimap_袁百万的博客-CSDN博客

class Solution {
public:
//分析题目非常关键,题中明确说明一共有2*n个元素,其中包含
//n+1个不同的元素,且有一个元素重复了n次,那么由此我们可以得出
//除了重复n次那个元素,其余元素都不可能重复。
//那么只要找到有重复的 其值就是我们要找的重复n次的数
    int repeatedNTimes(vector<int>& nums) 
    {
        unordered_set<int> found;
        for (auto num: nums) 
        {
            //如果遍历的时候此数已经在found中了
            //说明次数已经出现两次了,返回此数
            if (found.count(num)) 
            {
                return num;
            }
            //反之,没有出现次数就将其插入found
            found.insert(num);
        }
        
        return -1;
    }
};

 方法二:使用unordered_map中的[]来统计每个数出现的次数,根据次数求得答案。

class Solution {
public:
    int repeatedNTimes(vector<int>& nums) 
    {
        unordered_map<int,int> ump;
        //利用[]来对每个数进行计数
        for(auto& e:nums)
        {
            ump[e]++;
        }
        
        int n=nums.size()/2;
        for(auto& e:ump)
        {
            //若某个数的个数为n则说明该数就是重复n次的数
            if(e.second==n)
            return e.first;
        }
        return 0;
    }
};

两个数组的交集 

力扣

class Solution 
{
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) 
    {
        vector<int> v;
        //分别对nums1和nums2进行去重
        unordered_set<int> ust1,ust2;
        for(auto e: nums1)
        {
            ust1.insert(e);
        }
        for(auto e: nums2)
        {
            ust2.insert(e);
        }

        // 遍历ust2,,若ust2的值在ust1也存在则存入v中
        for(auto e : ust2)
        {
            if(ust1.count(e))
            {
                v.push_back(e);
            }
        }
        // 返回v
        return v;
         

    }
};

两个数的交集二

力扣

class Solution 
{
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) 
    {
       unordered_map<int,int> ump1;
       unordered_map<int,int> ump2;
       vector<int> v;
       for(auto e : nums1)
       {
           ump1[e]++;
       }
       for(auto e : nums2)
       {
           ump2[e]++;
       }

        for(auto e : ump2)
        {
            if(ump1.count(e.first))
            {
                int count=ump1[e.first]>=ump2[e.first]?ump2[e.first]:ump1[e.first];
                // int count=0;
                // if(ump1[e.first]>=ump2[e.first])
                // count=ump2[e.first];
                // else
                // count=ump1[e.first];
                for(int i=0;i<count;i++)
                v.push_back(e.first);
            }
        }
        return v;

    }
};

 底层结构

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

概念

哈希方法是以映射的方式搜索或者插入元素,因此效率极高。哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)。

 哈希冲突

虽然是通过映射(取模)的方式去找自己的位置,但是总有映射到相同位置的数据,该现象称为哈希冲突或者哈希碰撞。

闭散列

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

 注意:当我们删除元素的时候不能直接删除,这样会影响其他元素的查找。比如删除了元素4,那么我们在查找44的时候就很难去寻找了。因此我们选择来使用伪删除的方式,给每个空间设置一个状态位,通过修改他们的状态来达到删除的作用。

enum State
	{
		EMPTY,//0
		EXIST,//1
		DELETE,//2
	};

结点的定义

template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;//自定义类型会调用他的默认构造,内置类型不处理
	};

扩容

我们是根据负载因子的大小进行扩容的,负载因子=填入表中的元素个数/散列表的长度。

一般情况下我们是控制在0.7的,这样能降低哈希冲突。

//注意判断载荷因子的时候类型需要转换
			if (_n * 1.0 / _tables.size() >= 0.7)
			{
				//扩容之后映射位置发生改变,不能直接把数据给拷贝进来
				//因此需要搞一个新表,重新去算映射的位置
				//这里还是线性探测,如果遇到位置有值,就向后探测
				// 
				//旧表数据,重新计算,映射到新表
				/*vector<Data> newTable;
				newTable.resize(2 * _tables.size());
				for ()
				{

				}*/

				//创建新表
				HashTable<K, V, Hash> newHT;//局部对象出作用域销毁
				//大小扩容二倍
				newHT._tables.resize(_tables.size() * 2);
				//将旧表数据插入到新表中
				for (auto& e : _tables)//&减少拷贝
				{
					//旧表中位置存在才插入
					if (e._state == EXIST)
					{
						//复用了旧表的插入	
						newHT.Insert(e._kv);
					}
				}
				//最后交换旧表与新表,由于新表是局部对象,出作用域自动销毁
				_tables.swap(newHT._tables);
			}

字符串取模

我们在学习map和set的时候了解到有时我们会使用map来统计水果的次数,其实unordered_map也可以统计水果的次数。那么就避免不了key传的是string,也就是pair<string,int>这种情况,我们在插入数据的时候需要注意的是需要对key进行取模,根据取模得到的数据来在表中找出对应的值,那么如果我们传的是字符串的话应该怎么取模呢。

我们在学习红黑树的时候我们知道,key的作用是来比较大小的,也根据比较大小来确定这个结点的位置。那么在哈希表这一节课中,我们的key就是用来取模的,因为只有你能取模才能找到映射的位置,进而才能插入数据等等。

我们这里使用仿函数来解决这个问题。

//仿函数
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 hash = 0;
		//BKDRhash
		for (auto ch : key)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
		
	}
};

至于为什么将每一位*131然后相加后取模请查看以下文档,这个是C语言之父写的一本书中提到的并且这个BKDRhash的方法是最优的。

https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html

插入

bool Insert(const pair<K, V>& kv)//传引用减少拷贝
		{
			//如果已经存在我们需要插入的值,则直接返回false
			if (Find(kv.first))
				return false;
			//注意判断载荷因子的时候类型需要转换
			if (_n * 1.0 / _tables.size() >= 0.7)
			{
				//扩容之后映射位置发生改变,不能直接把数据给拷贝进来
				//因此需要搞一个新表,重新去算映射的位置
				//这里还是线性探测,如果遇到位置有值,就向后探测
				// 
				//旧表数据,重新计算,映射到新表
				/*vector<Data> newTable;
				newTable.resize(2 * _tables.size());
				for ()
				{

				}*/

				//创建新表
				HashTable<K, V, Hash> newHT;//局部对象出作用域销毁
				//大小扩容二倍
				newHT._tables.resize(_tables.size() * 2);
				//将旧表数据插入到新表中
				for (auto& e : _tables)//&减少拷贝
				{
					//旧表中位置存在才插入
					if (e._state == EXIST)
					{
						//复用了旧表的插入	
						newHT.Insert(e._kv);
					}
				}
				//最后交换旧表与新表,由于新表是局部对象,出作用域自动销毁
				_tables.swap(newHT._tables);
			}

			Hash hf;
			//hashi为存入的数据对表长进行取模
			size_t hashi = hf(kv.first) % _tables.size();

			while (_tables[hashi]._state == EXIST)
			{
				//线性探测
				hashi++;
				//探测完之后需要再次模size
				hashi %= _tables.size();
			}
			//将该结点赋给hashi位置
			_tables[hashi]._kv = kv;
			//改变状态
			_tables[hashi]._state = EXIST;
			//表中的有效数据++
			_n++;

			return true;
		}

当我们明白了如何对字符串进行取模后,其实插入就迎刃而解了。先取模找到该元素的位置,然后去寻找合适的位置插入。

查找

//这里返回所找到结点的地址是为了便于删除操作
		Data* Find(const K& key)
		{
			Hash hf;
			size_t hashi = hf(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;
		}

我们这的查找返回Data*是为了便于进行下面的删除操作,在这里需要注意的是极端情况下,哈希表是满的并且全是存在或者删除状态,也就是没有地方插入,那么hashi已经遍历了一圈之后就让其break;就行了。

删除

bool Erase(const K& key)
		{
			Data* ret = Find(key);//如果找到了返回找到的地址
			if (ret->_state != EMPTY)//检查状态是否存在
			{
				ret->_state = DELETE;
				_n--;//把有效数据数量-1
				return true;
			}
			else
				return false;/*删除失败返回false*/
		}

复用我们前面实现的查找,如果找到了就将其状态位设置为DELETE并且将_n减一,并且返回true如果删除失败返回false即可。

闭散列完整代码 

//仿函数
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 hash = 0;
		//BKDRhash
		for (auto ch : key)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
		
	}
};
namespace closehash
{
	//状态
	enum State
	{
		EMPTY,//0
		EXIST,//1
		DELETE,//2
	};


	//
	//struct HashFuncString
	//{
	//	size_t operator()(const string& key)
	//	{
	//		size_t hash = 0;
	//		for (auto ch : key)
	//		{
	//			hash += ch;
	//		}
	//		return hash;
	//		//return key[0];
	//	}
	//};
	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;//自定义类型会调用他的默认构造,内置类型不处理
	};

	template<class K, class V, class Hash = HashFunc<K>>//并且给个缺省的
	class HashTable
	{
		typedef HashData<K, V> Data;
	public:
		//构造函数用于初始化
		HashTable()
			:_n(0)
		{
			_tables.resize(10);
		}


		bool Insert(const pair<K, V>& kv)//传引用减少拷贝
		{
			//如果已经存在我们需要插入的值,则直接返回false
			if (Find(kv.first))
				return false;
			//注意判断载荷因子的时候类型需要转换
			if (_n * 1.0 / _tables.size() >= 0.7)
			{
				//扩容之后映射位置发生改变,不能直接把数据给拷贝进来
				//因此需要搞一个新表,重新去算映射的位置
				//这里还是线性探测,如果遇到位置有值,就向后探测
				// 
				//旧表数据,重新计算,映射到新表
				/*vector<Data> newTable;
				newTable.resize(2 * _tables.size());
				for ()
				{

				}*/

				//创建新表
				HashTable<K, V, Hash> newHT;//局部对象出作用域销毁
				//大小扩容二倍
				newHT._tables.resize(_tables.size() * 2);
				//将旧表数据插入到新表中
				for (auto& e : _tables)//&减少拷贝
				{
					//旧表中位置存在才插入
					if (e._state == EXIST)
					{
						//复用了旧表的插入	
						newHT.Insert(e._kv);
					}
				}
				//最后交换旧表与新表,由于新表是局部对象,出作用域自动销毁
				_tables.swap(newHT._tables);
			}

			Hash hf;
			//hashi为存入的数据对表长进行取模
			size_t hashi = hf(kv.first) % _tables.size();

			while (_tables[hashi]._state == EXIST)
			{
				//线性探测
				hashi++;
				//探测完之后需要再次模size
				hashi %= _tables.size();
			}
			//将该结点赋给hashi位置
			_tables[hashi]._kv = kv;
			//改变状态
			_tables[hashi]._state = EXIST;
			//表中的有效数据++
			_n++;

			return true;
		}
		//这里返回所找到结点的地址是为了便于删除操作
		Data* Find(const K& key)
		{
			Hash hf;
			size_t hashi = hf(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* ret = Find(key);//如果找到了返回找到的地址
			if (ret->_state != EMPTY)//检查状态是否存在
			{
				ret->_state = DELETE;
				_n--;//把有效数据数量-1
				return true;
			}
			else
				return false;/*删除失败返回false*/
		}


	private:
		//该vector里面存的是结点的值
		vector<Data> _tables;
		size_t _n = 0;  //表中存储的有效数据的个数
	};

	void TestHT1()
	{
		HashTable<int, int> ht;

		int a[] = { 18,8,7,27,57,38 };
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Insert(make_pair(17, 17));
		ht.Insert(make_pair(18, 18));
		ht.Insert(make_pair(5, 5));
		ht.Insert(make_pair(2, 2));
		ht.Insert(make_pair(1, 1));
		ht.Erase(1);
		cout << "Find(1):" << ht.Find(1) << endl;

		/*for (auto e : ht)
		{
			cout << e << " ";
		}
		cout << endl;*/
	}
	void TestHT2()
	{
		//这里是为了统计水果的次数
		//但是有一个致命的问题就是我们的key是一个string类型的
		//在通过key找该结点在哈希表中的位置的时候需要取模
		//我们忽略了一个问题,string对int取模,很明显是行不通的
		string arr[] = { "苹果","梨","桃子" };

		//countHT只是一个哈希表
		//HashTable<string, int, HashFuncString> countHT;
		HashTable<string, int> countHT;
		//我们要对结点里面的对象进行操作的话
		//需要定义一个结点的对象来接受Find返回的指针
		for (auto& e : arr)
		{
			HashData<string, int>* ret = countHT.Find(e);
			if (ret)//如果条件为真,就是找到了
				//ret是指向结点的指针
			{
				//通过ret找到_kv再找到second
				ret->_kv.second++;
			}
			else
				countHT.Insert(make_pair(e, 1));
		}
		HashFunc<string> hf;
		cout << hf("abc") << endl;
		cout << hf("bac") << endl;
		cout << hf("acb") << endl;
		cout << hf("add") << endl;
	}
}

开散列

开散列才是我们这章学习的重点,unordered系列的容器底层就是这种开散列的方式。这种方式的优越性极强。其意思就是在表的每个位置挂一个单链表,当具有相同映射位置时将它们串成一个链表链接到一起。每一个子集称为一个桶,各个桶(单链表)的头结点存放在哈希表中。

 每个桶中存放的都是发生哈希冲突的元素。

结点定义

template<class K, class V>
	//结点的结构体里面存放一个指针
	struct HashNode
	{
		pair<K, V> _kv;
		HashNode* _next;

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

由于哈希表每个位置放的是一个哈希桶,因此结点中应该定义一个_next用来遍历每个哈希桶中的数据。

释放桶(析构函数)

~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				//释放桶~HashTable()

				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] == nullptr;
			}
		}

遍历并释放每个桶中的每个结点,最后将每个桶的头结点置空。

扩容

大小

当我们去看源码的时候发现源码中为扩容后哈希表的大小给了特定了一些值。这些值均为素数,由于素数具有特殊的性质因此一定条件下可以减小哈希冲突。因此我将源码中的这个数组应用于我写的这个扩容。

注意:并且在开散列中负载因子不将遵循小于0.7的限制走,而是负载因子等于1的时候就扩容。

方式

在以下代码中是有两种方式的:

第一种:建立新表,新表的大小开到我们所定义数组中的下一个素数的位置。

遍历旧表,将遍历的值挨个插入到新表中。然后交换两个表,此时新表会由于是局部对象,调用默认的析构函数将其释放空间。

第二种:直接使用旧表中的结点链接到新表中,通过遍历旧表完成。

第二种的优点:第一种每次遍历旧表插入到新表的时候都会挨个创建每个结点的值,到最后还是会调用析构函数进行销毁,那这些结点创建的意义就非常小。

然而第二种方法,并不用创建新的结点,提高了效率,倘若我们需要插入10000个数据,那么就少创建了10000个结点,效率较高,因此我们在这里选择第二种。

 

//负载因子控制在1,超过1就扩容
			if (_tables.size() == _n)
			{

				第一种情况,创建一个新的哈希表是为了使用
				哈希表中的insert函数
				//	//创建一个新的哈希表
				//	
				//	//创建新表
				//	//局部对象出作用域销毁
				//	
				//	//这一步会调用我们所给的构造函数去对newHT里面的成员变量进行初始化
				//	HashTable<K, V, Hash> newHT;
				//	newHT._tables.resize(_tables.size() * 2);
				//	//将旧表数据插入到新表中
				//	for (auto& cur : _tables)//&减少拷贝
				//	{
				//		//遍历旧表的数据
				//		while (cur)
				//		{
				//			newHT.Insert(cur->_kv);
				//			cur = cur->_next;
				//		}
				//	}
				//	//最后交换旧表与新表,由于新表是局部对象,出作用域自动销毁
				//	_tables.swap(newHT._tables);




				vector<Node*> newTables;
				//newTables.resize(2 * _tables.size(), nullptr);
				// 遍历旧表将旧表中的结点头插到新表中。
				// 这样就不用创建新的结点。 
				newTables.resize(__stl_next_prime(_tables.size()), 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);




			}




		inline unsigned long __stl_next_prime(unsigned long n)
		{
			static const int __stl_num_primes = 28;
			static const unsigned long __stl_prime_list[__stl_num_primes] =
			{
			  53,         97,         193,       389,       769,
			  1543,       3079,       6151,      12289,     24593,
			  49157,      98317,      196613,    393241,    786433,
			  1572869,    3145739,    6291469,   12582917,  25165843,
			  50331653,   100663319,  201326611, 402653189, 805306457,
			  1610612741, 3221225473, 4294967291
			};
			for (int i = 0; i < __stl_num_primes; i++)
			{
				if (__stl_prime_list[i] > n)
				{
					return __stl_prime_list[i];
				}
			}
			return __stl_prime_list[__stl_num_primes - 1];
		}

插入

扩容解决后插入就很简单了,遍历哈希表,找到该数据对应的桶。然后通过我们之前学习单链表的方式进行链接操作。

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

			//负载因子控制在1,超过1就扩容
			if (_tables.size() == _n)
			{

				第一种情况,创建一个新的哈希表是为了使用
				哈希表中的insert函数
				//	//创建一个新的哈希表
				//	
				//	//创建新表
				//	//局部对象出作用域销毁
				//	
				//	//这一步会调用我们所给的构造函数去对newHT里面的成员变量进行初始化
				//	HashTable<K, V, Hash> newHT;
				//	newHT._tables.resize(__stl_next_prime(_tables.size());
				//	//将旧表数据插入到新表中
				//	for (auto& cur : _tables)//&减少拷贝
				//	{
				//		//遍历旧表的数据
				//		while (cur)
				//		{
				//			newHT.Insert(cur->_kv);
				//			cur = cur->_next;
				//		}
				//	}
				//	//最后交换旧表与新表,由于新表是局部对象,出作用域自动销毁
				//	_tables.swap(newHT._tables);




				vector<Node*> newTables;
				//newTables.resize(2 * _tables.size(), nullptr);
				// 遍历旧表将旧表中的结点头插到新表中。
				// 这样就不用创建新的结点。 
				newTables.resize(__stl_next_prime(_tables.size()), 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);



			}

			//Hash hf;
		//用仿函数进行转换然后取模
			size_t hashi = Hash()(kv.first) % _tables.size();
			//定义一个新结点
			//这一步会调用构造函数对kv和_next进行初始化
			Node* newnode = new Node(kv);
			//链接--头插
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;


			_n++;
			return true;

		}

查找

遍历,返回。

Node* Find(const K& key)
		{
			size_t hashi = Hash()(key) % _tables.size();

			//cur定义为映射后hashi映射位置哈希桶的头结点
			Node* cur = _tables[hashi];

			//遍历桶里面的每个结点,看是否与key相同
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				else
				{
					cur = cur->_next;
				}
			}
			return nullptr;
		}

删除

复用查找。

注意:当我们需要删除一个节点的时候我们需要将它的前驱结点与后继结点进行链接,因此我们需要定义一个前驱指针。

	bool Erase(const K& key)
		{
			size_t hashi = Hash()(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				//找到了
				if (cur->_kv.first == key)
				{
					//准备删除
					//如果需要删除的就是头结点
					if (cur == _tables[hashi])
					{
						_tables[hashi] = cur->_next;
					}
					//需要删除的非头结点
					else
					{
						//将自己的前驱指针指向自己的后继指针		
						prev->_next = cur->_next;
					}
					delete cur;
					_n--;//元素-1
					return true;

				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

开散列完整代码

#pragma once
//仿函数
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 hash = 0;
		//BKDRhash
		for (auto ch : key)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
		/*size_t hash = 0;
		for (auto ch : key)
		{
			hash += ch;
		}
		return hash;*/
	}
};
namespace closehash
{
	//状态
	enum State
	{
		EMPTY,//0
		EXIST,//1
		DELETE,//2
	};


	//
	//struct HashFuncString
	//{
	//	size_t operator()(const string& key)
	//	{
	//		size_t hash = 0;
	//		for (auto ch : key)
	//		{
	//			hash += ch;
	//		}
	//		return hash;
	//		//return key[0];
	//	}
	//};
	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;//自定义类型会调用他的默认构造,内置类型不处理
	};

	template<class K, class V, class Hash = HashFunc<K>>//并且给个缺省的
	class HashTable
	{
		typedef HashData<K, V> Data;
	public:
		//构造函数用于初始化
		HashTable()
			:_n(0)
		{
			_tables.resize(10);
		}


		bool Insert(const pair<K, V>& kv)//传引用减少拷贝
		{
			//如果已经存在我们需要插入的值,则直接返回false
			if (Find(kv.first))
				return false;
			//注意判断载荷因子的时候类型需要转换
			if (_n * 1.0 / _tables.size() >= 0.7)
			{
				//扩容之后映射位置发生改变,不能直接把数据给拷贝袭来
				//因此需要搞一个新表,重新去算映射的位置
				//这里还是线性探测,如果遇到位置有值,就向后探测
				// 
				//旧表数据,重新计算,映射到新表
				/*vector<Data> newTable;
				newTable.resize(2 * _tables.size());
				for ()
				{

				}*/

				//创建新表
				HashTable<K, V, Hash> newHT;//局部对象出作用域销毁
				//大小扩容二倍
				newHT._tables.resize(_tables.size() * 2);
				//将旧表数据插入到新表中
				for (auto& e : _tables)//&减少拷贝
				{
					//旧表中位置存在才插入
					if (e._state == EXIST)
					{
						//复用了旧表的插入	
						newHT.Insert(e._kv);
					}
				}
				//最后交换旧表与新表,由于新表是局部对象,出作用域自动销毁
				_tables.swap(newHT._tables);
			}

			Hash hf;
			//hashi为存入的数据对表长进行取模
			size_t hashi = hf(kv.first) % _tables.size();

			while (_tables[hashi]._state == EXIST)
			{
				//线性探测
				hashi++;
				//探测完之后需要再次模size
				hashi %= _tables.size();
			}
			//将该结点赋给hashi位置
			_tables[hashi]._kv = kv;
			//改变状态
			_tables[hashi]._state = EXIST;
			//表中的有效数据++
			_n++;

			return true;
		}
		//这里返回所找到结点的地址是为了便于删除操作
		Data* Find(const K& key)
		{
			Hash hf;
			size_t hashi = hf(key) % _tables.size();
			while (_tables[hashi]._state != EMPTY)
			{
				if (_tables[hashi]._state == EXIST
					&& _tables[hashi]._kv.first == key)
				{
					return &_tables[hashi];//返回这个位置的地址
				}
				hashi++;
				hashi %= _tables.size();
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			Data* ret = Find(key);//如果找到了返回找到的地址
			if (ret->_state != EMPTY)//检查状态是否存在
			{
				ret->_state = DELETE;
				_n--;//把有效数据数量-1
			}
			else
				return false;/*删除失败返回false*/
		}


	private:
		//该vector里面存的是结点的值
		vector<Data> _tables;
		size_t _n = 0;  //表中存储的有效数据的个数
	};

	void TestHT1()
	{
		HashTable<int, int> ht;

		int a[] = { 18,8,7,27,57,38 };
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Insert(make_pair(17, 17));
		ht.Insert(make_pair(18, 18));
		ht.Insert(make_pair(5, 5));
		ht.Insert(make_pair(2, 2));
		ht.Insert(make_pair(1, 1));
		ht.Erase(1);
		cout << "Find(1):" << ht.Find(1) << endl;

		/*for (auto e : ht)
		{
			cout << e << " ";
		}
		cout << endl;*/
	}
	void TestHT2()
	{
		//这里是为了统计水果的次数
		//但是有一个致命的问题就是我们的key是一个string类型的
		//在通过key找该结点在哈希表中的位置的时候需要取模
		//我们忽略了一个问题,string对int取模,很明显是行不通的
		string arr[] = { "苹果","梨","桃子" };

		//countHT只是一个哈希表
		//HashTable<string, int, HashFuncString> countHT;
		HashTable<string, int> countHT;
		//我们要对结点里面的对象进行操作的话
		//需要定义一个结点的对象来接受Find返回的指针
		for (auto& e : arr)
		{
			HashData<string, int>* ret = countHT.Find(e);
			if (ret)//如果条件为真,就是找到了
				//ret是指向结点的指针
			{
				//通过ret找到_kv再找到second
				ret->_kv.second++;
			}
			else
				countHT.Insert(make_pair(e, 1));
		}
		HashFunc<string> hf;
		cout << hf("abc") << endl;
		cout << hf("bac") << endl;
		cout << hf("acb") << endl;
		cout << hf("add") << endl;
	}
}

namespace buckethash
{
	template<class K, class V>
	//结点的结构体里面存放一个指针
	struct HashNode
	{
		pair<K, V> _kv;
		HashNode* _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()
			:_n(0)
		{
			_tables.resize(__stl_next_prime(0));
		}

		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				//释放桶~HashTable()

				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,超过1就扩容
			if (_tables.size() == _n)
			{

				第一种情况,创建一个新的哈希表是为了使用
				哈希表中的insert函数
				//	//创建一个新的哈希表
				//	
				//	//创建新表
				//	//局部对象出作用域销毁
				//	
				//	//这一步会调用我们所给的构造函数去对newHT里面的成员变量进行初始化
				//	HashTable<K, V, Hash> newHT;
				//	newHT._tables.resize(__stl_next_prime(_tables.size());
				//	//将旧表数据插入到新表中
				//	for (auto& cur : _tables)//&减少拷贝
				//	{
				//		//遍历旧表的数据
				//		while (cur)
				//		{
				//			newHT.Insert(cur->_kv);
				//			cur = cur->_next;
				//		}
				//	}
				//	//最后交换旧表与新表,由于新表是局部对象,出作用域自动销毁
				//	_tables.swap(newHT._tables);




				vector<Node*> newTables;
				//newTables.resize(2 * _tables.size(), nullptr);
				// 遍历旧表将旧表中的结点头插到新表中。
				// 这样就不用创建新的结点。 
				newTables.resize(__stl_next_prime(_tables.size()), 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);



			}

			//Hash hf;
		//用仿函数进行转换然后取模
			size_t hashi = Hash()(kv.first) % _tables.size();
			//定义一个新结点
			//这一步会调用构造函数对kv和_next进行初始化
			Node* newnode = new Node(kv);
			//链接--头插
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;


			_n++;
			return true;

		}
		Node* Find(const K& key)
		{
			size_t hashi = Hash()(key) % _tables.size();

			//cur定义为映射后hashi映射位置哈希桶的头结点
			Node* cur = _tables[hashi];

			//遍历桶里面的每个结点,看是否与key相同
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				else
				{
					cur = cur->_next;
				}
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			size_t hashi = Hash()(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				//找到了
				if (cur->_kv.first == key)
				{
					//准备删除
					//如果需要删除的就是头结点
					if (cur == _tables[hashi])
					{
						_tables[hashi] = cur->_next;
					}
					//需要删除的非头结点
					else
					{
						//将自己的前驱指针指向自己的后继指针		
						prev->_next = cur->_next;
					}
					delete cur;
					_n--;//元素-1
					return true;

				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}
		

		inline unsigned long __stl_next_prime(unsigned long n)
		{
			static const int __stl_num_primes = 28;
			static const unsigned long __stl_prime_list[__stl_num_primes] =
			{
			  53,         97,         193,       389,       769,
			  1543,       3079,       6151,      12289,     24593,
			  49157,      98317,      196613,    393241,    786433,
			  1572869,    3145739,    6291469,   12582917,  25165843,
			  50331653,   100663319,  201326611, 402653189, 805306457,
			  1610612741, 3221225473, 4294967291
			};
			for (int i = 0; i < __stl_num_primes; i++)
			{
				if (__stl_prime_list[i] > n)
				{
					return __stl_prime_list[i];
				}
			}
			return __stl_prime_list[__stl_num_primes - 1];
		}

	private:
		//指针数组 数组的每一个元素是一个指针
		vector<Node*> _tables;//指针数组
		size_t _n = 0;
	};


	void TestHT1()
	{
		HashTable<int, int> ht;

		int a[] = { 18,8,7,27,57,3,38,18,17,88,38,28 };
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}
		ht.Insert(make_pair(5, 5));
		ht.Erase(17);
		ht.Erase(57);
	}

	void TestHT2()
	{
		string arr[] = { "苹果","梨","桃子" };
		HashTable<string, int> countHT;
		for (auto& e : arr)
		{
			HashNode<string, int >* ret = countHT.Find(e);
			//auto ret = countHT.Find(e);
			if (ret)
			{
				ret->_kv.second++;
			}
			else
				countHT.Insert(make_pair(e, 1));
		}
	}
}

用开散列哈希表封装unordered系列容器

仅为了解,并包含迭代器。

HashTable.h

#pragma once
//仿函数
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 hash = 0;
		//BKDRhash
		for (auto ch : key)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
		
	}
};
namespace closehash
{
	//状态
	enum State
	{
		EMPTY,//0
		EXIST,//1
		DELETE,//2
	};


	//
	//struct HashFuncString
	//{
	//	size_t operator()(const string& key)
	//	{
	//		size_t hash = 0;
	//		for (auto ch : key)
	//		{
	//			hash += ch;
	//		}
	//		return hash;
	//		//return key[0];
	//	}
	//};
	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;//自定义类型会调用他的默认构造,内置类型不处理
	};

	template<class K, class V, class Hash = HashFunc<K>>//并且给个缺省的
	class HashTable
	{
		typedef HashData<K, V> Data;
	public:
		//构造函数用于初始化
		HashTable()
			:_n(0)
		{
			_tables.resize(10);
		}


		bool Insert(const pair<K, V>& kv)//传引用减少拷贝
		{
			//如果已经存在我们需要插入的值,则直接返回false
			if (Find(kv.first))
				return false;
			//注意判断载荷因子的时候类型需要转换
			if (_n * 1.0 / _tables.size() >= 0.7)
			{
				//扩容之后映射位置发生改变,不能直接把数据给拷贝进来
				//因此需要搞一个新表,重新去算映射的位置
				//这里还是线性探测,如果遇到位置有值,就向后探测
				// 
				//旧表数据,重新计算,映射到新表
				/*vector<Data> newTable;
				newTable.resize(2 * _tables.size());
				for ()
				{

				}*/

				//创建新表
				HashTable<K, V, Hash> newHT;//局部对象出作用域销毁
				//大小扩容二倍
				newHT._tables.resize(_tables.size() * 2);
				//将旧表数据插入到新表中
				for (auto& e : _tables)//&减少拷贝
				{
					//旧表中位置存在才插入
					if (e._state == EXIST)
					{
						//复用了旧表的插入	
						newHT.Insert(e._kv);
					}
				}
				//最后交换旧表与新表,由于新表是局部对象,出作用域自动销毁
				_tables.swap(newHT._tables);
			}

			Hash hf;
			//hashi为存入的数据对表长进行取模
			size_t hashi = hf(kv.first) % _tables.size();

			while (_tables[hashi]._state == EXIST)
			{
				//线性探测
				hashi++;
				//探测完之后需要再次模size
				hashi %= _tables.size();
			}
			//将该结点赋给hashi位置
			_tables[hashi]._kv = kv;
			//改变状态
			_tables[hashi]._state = EXIST;
			//表中的有效数据++
			_n++;

			return true;
		}
		//这里返回所找到结点的地址是为了便于删除操作
		Data* Find(const K& key)
		{
			Hash hf;
			size_t hashi = hf(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* ret = Find(key);//如果找到了返回找到的地址
			if (ret->_state != EMPTY)//检查状态是否存在
			{
				ret->_state = DELETE;
				_n--;//把有效数据数量-1
				return true;
			}
			else
				return false;/*删除失败返回false*/
		}


	private:
		//该vector里面存的是结点的值
		vector<Data> _tables;
		size_t _n = 0;  //表中存储的有效数据的个数
	};

	void TestHT1()
	{
		HashTable<int, int> ht;

		int a[] = { 18,8,7,27,57,38 };
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Insert(make_pair(17, 17));
		ht.Insert(make_pair(18, 18));
		ht.Insert(make_pair(5, 5));
		ht.Insert(make_pair(2, 2));
		ht.Insert(make_pair(1, 1));
		ht.Erase(1);
		cout << "Find(1):" << ht.Find(1) << endl;

		/*for (auto e : ht)
		{
			cout << e << " ";
		}
		cout << endl;*/
	}
	void TestHT2()
	{
		//这里是为了统计水果的次数
		//但是有一个致命的问题就是我们的key是一个string类型的
		//在通过key找该结点在哈希表中的位置的时候需要取模
		//我们忽略了一个问题,string对int取模,很明显是行不通的
		string arr[] = { "苹果","梨","桃子" };

		//countHT只是一个哈希表
		//HashTable<string, int, HashFuncString> countHT;
		HashTable<string, int> countHT;
		//我们要对结点里面的对象进行操作的话
		//需要定义一个结点的对象来接受Find返回的指针
		for (auto& e : arr)
		{
			HashData<string, int>* ret = countHT.Find(e);
			if (ret)//如果条件为真,就是找到了
				//ret是指向结点的指针
			{
				//通过ret找到_kv再找到second
				ret->_kv.second++;
			}
			else
				countHT.Insert(make_pair(e, 1));
		}
		HashFunc<string> hf;
		cout << hf("abc") << endl;
		cout << hf("bac") << endl;
		cout << hf("acb") << endl;
		cout << hf("add") << endl;
	}
}

namespace buckethash
{
	template<class T>
	//结点的结构体里面存放一个指针
	struct HashNode
	{
		//pair<K, V> _kv;
		T _data;
		HashNode<T>* _next;

		//HashNode(const pair<K, T>& kv)
		//	:_kv(kv)
		//	, _next(nullptr)
		//{}
		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{}
	};

	//迭代器用哈希表  ——  哈希表用迭代器
	//所以要使用前置声明
	//前置声明
	template<class K, class T, class Hash, class KeyOfT>
	class HashTable;


	//为什么const迭代器没有复用普通迭代器
	template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>
	struct __HTIterator
	{

		typedef HashNode<T> Node;

		typedef __HTIterator<K, T, Ref, Ptr, Hash, KeyOfT> Self;

		typedef HashTable<K, T, Hash, KeyOfT> HT;

		Node* _node;
		HT* _ht;

		__HTIterator(Node* node, HT* ht)
			:_node(node)
			, _ht(ht)
		{}
		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}
		bool operator!=(const Self& s)const
		{
			return _node != s._node;
		}
		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			//当前桶走完了,要找下一个桶的第一个
			else
			{
				KeyOfT kot;
				Hash hash;
				//记录当前的位置
				size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
				hashi++;
				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}
					else
					{
						hashi++;
					}
				}
				//后面没有桶了
				if (hashi == _ht->_tables.size())
					_node = nullptr;

			}
			return *this;


		}


	};




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

		template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>
		friend struct __HTIterator;

	public:
		typedef __HTIterator<K, T, T&, T*, Hash, KeyOfT> iterator;
		typedef __HTIterator<K, T, const T&, const T*, Hash, KeyOfT> const_iterator;

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

		const_iterator begin() const
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				if (_tables[i])
				{
					return const_iterator(_tables[i], this);
				}
			}
			return const_iterator(nullptr, this);
		}
		const_iterator end() const
		{
			return const_iterator(nullptr, this);
		}
		HashTable()
			:_n(0)
		{
			_tables.resize(__stl_next_prime(0));
		}

		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				//释放桶~HashTable()

				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}
		pair<iterator, bool> Insert(const T& data)
		{
			KeyOfT kot;
			iterator it = Find(kot(data));
			if (it != end())
				return make_pair(it, false);

			//负载因子控制在1,超过1就扩容
			if (_tables.size() == _n)
			{

				第一种情况,创建一个新的哈希表是为了使用
				哈希表中的insert函数
				//	//创建一个新的哈希表
				//	
				//	//创建新表
				//	//局部对象出作用域销毁
				//	
				//	//这一步会调用我们所给的构造函数去对newHT里面的成员变量进行初始化
				//	HashTable<K, V, Hash> newHT;
				//	//大小扩容二倍
				//	newHT._tables.resize(_tables.size() * 2);
				//	//将旧表数据插入到新表中
				//	for (auto& cur : _tables)//&减少拷贝
				//	{
				//		//遍历旧表的数据
				//		while (cur)
				//		{
				//			newHT.Insert(cur->_kv);
				//			cur = cur->_next;
				//		}
				//	}
				//	//最后交换旧表与新表,由于新表是局部对象,出作用域自动销毁
				//	_tables.swap(newHT._tables);




				vector<Node*> newTables;
				//newTables.resize(2 * _tables.size(), nullptr);
				// 遍历旧表将旧表中的结点头插到新表中。
				// 这样就不用创建新的结点。 
				newTables.resize(__stl_next_prime(_tables.size()), 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);

			}

			//Hash hf;
		//用仿函数进行转换然后取模
			//TODO
			size_t hashi = Hash()(kot(data)) % _tables.size();
			//定义一个新结点
			//这一步会调用构造函数对kv和_next进行初始化
			Node* newnode = new Node(data);
			//链接--头插
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;


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

		}
		iterator Find(const K& key)
		{
			KeyOfT kot;
			size_t hashi = Hash()(key) % _tables.size();

			//cur定义为映射后hashi映射位置哈希桶的头结点
			Node* cur = _tables[hashi];

			//遍历桶里面的每个结点,看是否与key相同
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}
				else
				{
					cur = cur->_next;
				}
			}
			return end();
		}

		bool Erase(const K& key)
		{
			size_t hashi = Hash()(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				//找到了
				if (cur->_kv.first == key)
				{
					//准备删除
					//如果需要删除的就是头结点
					if (cur == _tables[hashi])
					{
						_tables[hashi] = cur->_next;
					}
					//需要删除的非头结点
					else
					{
						//将自己的前驱指针指向自己的后继指针		
						prev->_next = cur->_next;
					}
					delete cur;
					_n--;//元素-1
					return true;

				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}


		inline unsigned long __stl_next_prime(unsigned long n)
		{
			static const int __stl_num_primes = 28;
			static const unsigned long __stl_prime_list[__stl_num_primes] =
			{
			  53,         97,         193,       389,       769,
			  1543,       3079,       6151,      12289,     24593,
			  49157,      98317,      196613,    393241,    786433,
			  1572869,    3145739,    6291469,   12582917,  25165843,
			  50331653,   100663319,  201326611, 402653189, 805306457,
			  1610612741, 3221225473, 4294967291
			};
			for (int i = 0; i < __stl_num_primes; i++)
			{
				if (__stl_prime_list[i] > n)
				{
					return __stl_prime_list[i];
				}
			}
			return __stl_prime_list[__stl_num_primes - 1];
		}

	private:
		//指针数组 数组的每一个元素是一个指针
		vector<Node*> _tables;//指针数组
		size_t _n = 0;
	};


	//void TestHT1()
	//{
	//	HashTable<int, int> ht;

	//	int a[] = { 18,8,7,27,57,3,38,18,17,88,38,28 };
	//	for (auto e : a)
	//	{
	//		ht.Insert(make_pair(e, e));
	//	}
	//	ht.Insert(make_pair(5, 5));
	//	ht.Erase(17);
	//	ht.Erase(57);
	//}

	//void TestHT2()
	//{
	//	string arr[] = { "苹果","梨","桃子" };
	//	HashTable<string, int> countHT;
	//	for (auto& e : arr)
	//	{
	//		HashNode<string, int >* ret = countHT.Find(e);
	//		//auto ret = countHT.Find(e);
	//		if (ret)
	//		{
	//			ret->_kv.second++;
	//		}
	//		else
	//			countHT.Insert(make_pair(e, 1));
	//	}
	//}
}

UnorderSet.h

#pragma once
#include"HashTable.h"

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

		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
		const_iterator begin() const
		{
			return _ht.begin();
		}
		const_iterator end() const
		{
			return _ht.end();
		}

		pair<iterator, bool> insert(const K& key)
		{
			return _ht.Insert(key);
		}
	private:
		buckethash::HashTable<K, K, Hash, SetKeyOfT> _ht;
	};
	/*void Func(const unordered_set<int>& us)
	{
		unordered_set<int>::const_iterator it = us.begin();
		while (it != us.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}*/
	void test_unordered_set()
	{
		unordered_set<int> us;
		us.insert(13);
		us.insert(3);
		us.insert(666);
		us.insert(23);
		us.insert(5);
		us.insert(5);
		us.insert(60);
		us.insert(15);

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

UnorderMap.h

#pragma once
#include"HashTable.h"

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

		typedef typename buckethash::HashTable<K, pair<const K, V>, Hash,
			MapKeyOfT>::iterator iterator;
		typedef typename buckethash::HashTable<K, pair<const K, V>, Hash,
			MapKeyOfT>::const_iterator const_iterator;

		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
		const_iterator begin() const
		{
			return _ht.begin();
		}
		const_iterator end() const
		{
			return _ht.end();
		}


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

		iterator find(const K& key)
		{
			return _ht.Find(key);
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		buckethash::HashTable<K, pair<const K, V>, Hash, MapKeyOfT> _ht;
	};
	void test_unordered_map()
	{
		string arr[] = { "苹果", "西瓜", "苹果"
		, "西瓜", "苹果", "苹果", "西瓜"
		, "苹果", "香蕉", "苹果", "香蕉" };
		unordered_map<string, int> countMap;

		for (auto& e : arr)
		{
			countMap[e]++;
		}
		for (const auto& kv : countMap)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
}

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

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

相关文章

安卓远程控制软件哪个好用

如果您曾希望将个人电脑放在口袋里&#xff0c;那么您可能只需要安卓远程访问软件。 没有远程访问应用程序&#xff1a;使用和控制计算机的唯一方法是坐在计算机前并手动输入命令。 使用远程访问应用程序&#xff1a;您可以在世界任何地方通过 Internet 连接从您的安卓平板电…

【30天python从零到一】---第七天:列表和元组

&#x1f34e; 博客主页&#xff1a;&#x1f319;披星戴月的贾维斯 &#x1f34e; 欢迎关注&#xff1a;&#x1f44d;点赞&#x1f343;收藏&#x1f525;留言 &#x1f347;系列专栏&#xff1a;&#x1f319; Python专栏 &#x1f319;请不要相信胜利就像山坡上的蒲公英一样…

计算机组成原理---第五章中央处理器

&#xff08;一&#xff09;CPU 的功能和组成 CPU 的功能 Ⅰ 概述&#xff1a;当程序指令装入内存储器后&#xff0c;CPU 用来自动完成取指令和执行指令的任务。 Ⅱ CPU 的功能&#xff1a;①指令控制 ②操作控制 ③时间控制 ④数据加工 2.CPU 的基本组成 CPU 的基本部分为运…

【论文阅读】[JBHI] VLTENet、[ISBI]

[JBHI] VLTENet 论文连接&#xff1a;VLTENet: A Deep-Learning-Based Vertebra Localization and Tilt Estimation Network for Automatic Cobb Angle Estimation | IEEE Journals & Magazine | IEEE Xplore Published in: IEEE Journal of Biomedical and Health Infor…

9.1 相关分析

学习目标&#xff1a; 如果我要学习相关分析&#xff0c;我可能会按照以下步骤进行&#xff1a; 确定学习相关分析的目的和应用场景&#xff0c;例如研究两个变量之间的相关性、了解变量之间的关系、预测未来趋势等。学习相关分析的基本概念和原理&#xff0c;包括相关系数、…

VS——Visual Studio 2022 社区版——快捷键

VS——Visual Studio 2022 社区版——快捷键官网简介PDF完整PDF编辑编辑&#xff1a;常用快捷方式菜单栏 会显示 快捷键功能搜索大纲 折叠 展开Ctrl M M 切换官网 https://learn.microsoft.com/zh-cn/visualstudio/ide/default-keyboard-shortcuts-in-visual-studio?viewvs-2…

数据结构 — 【排序算法】

目录 1.排序的概念及其运用 1.1排序的概念 1.2排序运用 1.3 常见的排序算法 2.常见排序算法的实现 2.1 插入排序 直接插入排序 希尔排序 2.2 选择排序 直接选择排序 堆排序 2.3 交换排序 冒泡排序 快速排序 2.4 归并排序 2.5 非比较排序 计数排序 基数排序 3.排序算法…

【Unity入门】12.MonoBehaviour事件函数

【Unity入门】MonoBehaviour事件函数 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;常用的事件函数 &#xff08;1&#xff09;start和update方法 之前我们写的脚本&#xff0c;会默认帮助…

4.3 分部积分法

学习目标&#xff1a; 学习分部积分法&#xff0c;我可能会按照以下步骤进行&#xff1a; 理解分部积分法的基本思想。分部积分法是一种通过对积分式中的不同部分进行乘积分解&#xff0c;然后对乘积中的某一项进行积分&#xff0c;对另一项进行微分&#xff0c;从而将原积分式…

NumPy 秘籍中文第二版:五、音频和图像处理

原文&#xff1a;NumPy Cookbook - Second Edition 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 在本章中&#xff0c;我们将介绍 NumPy 和 SciPy 的基本图像和音频&#xff08;WAV 文件&#xff09;处理。 在以下秘籍中&#xff0c;我们将使用 NumPy 对声音和图像进…

叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践

导读&#xff1a; 随着叮咚买菜业务的发展&#xff0c;不同的业务场景对数据分析提出了不同的需求&#xff0c;他们希望引入一款实时 OLAP 数据库&#xff0c;构建一个灵活的多维实时查询和分析的平台&#xff0c;统一数据的接入和查询方案&#xff0c;解决各业务线对数据高效实…

一键构建分布式云原生平台

目录专栏导读一、分布式云原生平台1、应用无所不能2、运行无处不在3、服务千行白业二、分布式云原生平台关键要素1、统一应用管理2、统一流量自治3、统一数据管理4、统一运维三、多云多集群已经广泛应用四、分布式云的优势&#xff1a;1、避免厂商锁定2、满足合规化要求3、增强…

收藏!7个国内「小众」的程序员社区

技术社区是大量开发者的集聚地&#xff0c;在技术社区可以了解到行业的最新进展&#xff0c;学习最前沿的技术&#xff0c;认识有相同爱好的朋友&#xff0c;在一起学习和交流。 国内知名的技术社区有CSDN、博客园、开源中国、51CTO&#xff0c;还有近两年火热的掘金&#xff…

基于决策树及集成算法的回归与分类案例

基于决策树及集成算法的回归与分类案例 描述 本任务基于决策树及集成算法分别实现鲍鱼年龄预测案例和肿瘤分类案例。鲍鱼年龄预测案例是建立一个回归模型&#xff0c;根据鲍鱼的特征数据&#xff08;长度、直径、高度、总重量、剥壳重量、内脏重量、壳重&#xff09;等预测其…

Python:超级大全网上面试题搜集整理(四)

转载参考&#xff1a; python 面试题(高级)_python高级面试题_梦幻python的博客-CSDN博客 cpython pypy_介绍Cython&#xff0c;Pypy Cpython Numba各有什么缺点【面试题详解】_函明的博客-CSDN博客 Cython、PyPy专题开篇 - 知乎 Python抽象类和接口类_python 接口类_代码输…

蓝桥杯客观题知识点

一、异步和同步的在于 有无统一的时钟信号 异步无 同步有 RS485 半双工、异步、串行、差分输入------多级通信&#xff08;USB\键盘等外设&#xff09; RS232 全双工、异步、串行、单端输入------一对一通信 二、组合逻辑电路和时序逻辑电路的区别 组合&#xff1a;任意时…

使用反射重新执行不同的方法

0. 用到的技术 反射获取正在执行的方法名称Class[]数组的获取 1. 为什么要这样做? 情况如下: 当我调用sendCommands方法发送请求时可能会收到errorCode为403也就是代码中的MDS_ERROR,就是当token(mds)失效了这种情况,我们就需要重新刷新token,并且重新执行该方法 假设还有1…

SYN FLOOD攻击和HTTP慢速攻击实验笔记

SYN_FLOOD攻击和HTTP慢速攻击是DDOS攻击的两种方式。 SYN Flood攻击 SYN Flood攻击的原理就是阻断TCP三次握手的第三次ACK包&#xff0c;即不对服务器发送的SYNACK数据包做出应答。由于服务器没有收到客户端发来的确认响应&#xff0c;就会一直保持连接直到超时&#xff0c;当…

产品营销软文怎么写吸引人?

随着互联网的发展&#xff0c;人们获取信息的渠道变得越来越多&#xff0c;其中软文营销成为了众多企业推广自己产品的主要方式之一。那么&#xff0c;软文营销怎么写才能吸引人呢&#xff1f;这里有一些建议&#xff0c;可以帮助你解决这个问题。 要想写出一篇成功的软文&…

自拍的照片不太清晰怎么办?拍摄的模糊照片如何修复高清?

如果您的人像照片不太清晰&#xff0c;可能是由于手持相机时快门速度过慢、摄像机抖动或者焦点不准确等原因造成的。 自己拍摄的照片总是感觉不太清晰&#xff0c;放大看的话更是模糊&#xff0c;该如何是好&#xff1f; 以下是一些避免自拍照片模糊的方法&#xff1a; 1、使…