C++模拟实现unordered_map和unordered_set(哈希)

news2024/11/28 14:38:59

目录

一、unordered系列关联式容器

1.1 unordered_map

1.1.1 unordered_map

1.1.2 unordered_map接口说明

1. unordered_map的容量

2. unordered_map的迭代器

3.unordered_map的元素访问

4. unordered_map的查询

5. unordered_map的修改操作

6. unordered_map的桶操作unordere_set在线文档说明

1.2 unordered_set

1.3 unordered_map与map查找性能比较

二、底层结构

2.1 哈希概念

2.2 哈希冲突

2.3 哈希函数

2.4 哈希冲突解决

2.4.1 闭散列

线性探测

线性探测的实现

线性探测优缺点

2.4.2 开散列

1.概念

2.开散列实现

3.开散列增容

4.非整形key的开散列

三、模拟实现unordered系列

3.1 哈希表的改造

        1.模板参数列表的改造

        2.增加迭代器操作

        3、增加通过key获取value操作

3.2 模拟实现unordered_map

3.3 模拟实现unordered_set


一、unordered系列关联式容器

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

1.1 unordered_map

unordere_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_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问
value。
6. 它的迭代器至少是前向迭代器。

1.1.2 unordered_map接口说明


1. unordered_map的容量

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

2. unordered_map的迭代器

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

3.unordered_map的元素访问

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

注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶
中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,
将key对应的value返回。

4. unordered_map的查询

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

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

5. unordered_map的修改操作

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

6. unordered_map的桶操作unordere_set在线文档说明

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

unordere_set在线文档说明

1.3 unordered_map与map查找性能比较

以下是使用C++的unordered_map和map进行性能比较的示例代码:

cpp
#include <iostream>
#include <map>
#include <unordered_map>
#include <chrono>
#include <string>
int main() {
    std::map<int, std::string> map_container;
    std::unordered_map<int, std::string> unordered_map_container;

    // 填充数据
    for (int i = 0; i < 100000; i++) {
        map_container[i] = "element " + std::to_string(i);
        unordered_map_container[i] = "element " + std::to_string(i);
    }

    // 使用map进行查找
    auto start_time = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 100000; i++) {
        if (map_container.find(i) == map_container.end()) {
            std::cout << "Element not found" << std::endl;
        }
    }
    auto end_time = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
    std::cout << "Time taken by map: " << duration.count() << " microseconds" << std::endl;

    // 使用unordered_map进行查找
    start_time = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 100000; i++) {
        if (unordered_map_container.find(i) == unordered_map_container.end()) {
            std::cout << "Element not found" << std::endl;
        }
    }
    end_time = std::chrono::high_resolution_clock::now();
    duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
    std::cout << "Time taken by unordered_map: " << duration.count() << " microseconds" << std::endl;

    return 0;
}

//在此示例中,我们创建了两个容器,一个使用std::map,另一个使用std::unordered_map。然后,我们向每个容器添加100,000个元素,并使用find()函数在每个容器中查找每个元素。最后,我们使用std::chrono库计算两个操作的时间,并输出结果。

//通常情况下,unordered_map比map更快,因为unordered_map使用哈希表实现,其查找操作的平均时间复杂度为O(1),而map使用红黑树实现,其查找操作的平均时间复杂度为O(log n)。但是,在某些情况下,unordered_map可能会比map慢,例如当元素数量较少时,或者当元素的大小相差很大时。此外,unordered_map需要更多的内存空间来存储哈希表中的元素。因此,在选择使用unordered_map还是map时,需要考虑数据的特点和性能需求。

二、底层结构

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

2.1 哈希概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素
时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即
O(log_2 N),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一映射的关系
,那么在查找时通过该函数可以很快找到该元素!

当插入元素:根据关键码key,用哈希函数计算出元素的存储位置进行存放


当搜索元素:同样通过关键码映射存储位置,在比较判断关键码是否一致

2.2 哈希冲突

哈希冲突:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突
或哈希碰撞。我们可以预想的是哈希冲突无法完全杜绝,只能用更高效的哈希函数算法来减少冲突。下面来谈谈常见的哈希函数!

2.3 哈希函数

1. 直接定址法--(常用)

取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀
缺点:需要事先知道关键字的分布情况
使用场景:适合查找比较小且连续的情况
面试题:字符串中第一个只出现一次字符


2. 除留余数法--(常用) 

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


3. 平方取中法--(了解)

假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;
再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况


4. 折叠法--(了解)

折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这
几部分叠加求和,并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况


5. 随机数法--(了解) 

选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中
random为随机数函数。
通常应用于关键字长度不等时采用此法


6. 数学分析法--(了解)

设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定
相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只
有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散
列地址。例如:


假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是 相同
的,那么我们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还
可以对抽取出来的数字进行反转(如1234改成4321)、右环位移(如1234改成4123)、左环移
位、前两数与后两数叠加(如1234改成12+34=46)等方法。
数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的
若干位分布较均匀的情况

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


2.4 哈希冲突解决

解决哈希冲突的常见方法:闭散列和开散列

2.4.1 闭散列

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

        1.线性探测

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


插入:

1.通过哈希函数将关键字映射到哈希表中位置

2.判断该位置没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素

删除:

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

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

线性探测的实现

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

// 注意:假如实现的哈希表中元素唯一,即key相同的元素不再进行插入
// 为了实现简单,此哈希表中我们将比较直接与元素绑定在一起
template<class K, class V>
class HashTable
{
	struct Elem
	{
		pair<K, V> _val;
		State _state;
	};
public:
	HashTable(size_t capacity = 3)
		: _ht(capacity), _size(0)
	{
		for (size_t i = 0; i < capacity; ++i)
			_ht[i]._state = EMPTY;
	}
	bool Insert(const pair<K, V>& val)
	{
		// 检测哈希表底层空间是否充足
		// _CheckCapacity();
		size_t hashAddr = HashFunc(key);
		// size_t startAddr = hashAddr;
		while (_ht[hashAddr]._state != EMPTY)
		{
			if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first
				== key)
				return false;
			hashAddr++;
			if (hashAddr == _ht.capacity())
				hashAddr = 0;
			/*
			// 转一圈也没有找到,注意:动态哈希表,该种情况可以不用考虑,哈希表中元
			素个数到达一定的数量,哈希冲突概率会增大,需要扩容来降低哈希冲突,因此哈希表中元素是
			不会存满的
			if(hashAddr == startAddr)
			return false;
			*/
		}
		// 插入元素
		_ht[hashAddr]._state = EXIST;
		_ht[hashAddr]._val = val;
		_size++;
		return true;
	}
	int Find(const K& key)
	{
		size_t hashAddr = HashFunc(key);
		while (_ht[hashAddr]._state != EMPTY)
		{
			if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first
				== key)
				return hashAddr;
			hashAddr++;
		}
		return hashAddr;
	}
	bool Erase(const K& key)
	{
		int index = Find(key);
		if (-1 != index)
		{
			_ht[index]._state = DELETE;
			_size++;
			return true;
		}
		return false;
	}
	size_t Size()const;
	bool Empty() const;
	void Swap(HashTable<K, V, HF>& ht);
private:
	size_t HashFunc(const K& key)
	{
		return key % _ht.capacity();
	}
private:
	vector<Elem> _ht;
	size_t _size;
};

 思考:哈希表什么情况下进行扩容?如何扩容?

 扩容代码(思路联想现代拷贝构造):

void CheckCapacity()
{
	if (_size * 10 / _ht.capacity() >= 7)
	{
		HashTable<K, V, HF> newHt(GetNextPrime(ht.capacity));
		for (size_t i = 0; i < _ht.capacity(); ++i)
		{
			if (_ht[i]._state == EXIST)
				newHt.Insert(_ht[i]._val);
		}
		Swap(newHt);
	}
}

线性探测优缺点

线性探测优点:实现非常简单,
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同
关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降
低。


2.4.2 开散列

1.概念

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

 从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素


2.开散列实现

template<class V>
struct HashBucketNode
{
	HashBucketNode(const V& data)
		: _pNext(nullptr), _data(data)
	{}
	HashBucketNode<V>* _pNext;
	V _data;
};
// 本文所实现的哈希桶中key是唯一的
template<class V>
class HashBucket
{
	typedef HashBucketNode<V> Node;
	typedef Node* PNode;
public:
	HashBucket(size_t capacity = 3) : _size(0)
	{
		_ht.resize(GetNextPrime(capacity), nullptr);
	}
	// 哈希桶中的元素不能重复
	PNode* Insert(const V& data)
	{
		// 确认是否需要扩容。。。
		// _CheckCapacity();
		// 1. 计算元素所在的桶号
			size_t bucketNo = HashFunc(data);
		// 2. 检测该元素是否在桶中
		PNode pCur = _ht[bucketNo];
		while (pCur)
		{
			if (pCur->_data == data)
				return pCur;
			pCur = pCur->_pNext;
		}
		// 3. 插入新元素
		pCur = new Node(data);
		pCur->_pNext = _ht[bucketNo];
		_ht[bucketNo] = pCur;
		_size++;
		return pCur;
	}
	// 删除哈希桶中为data的元素(data不会重复),返回删除元素的下一个节点
	PNode* Erase(const V& data)
	{
		size_t bucketNo = HashFunc(data);
		PNode pCur = _ht[bucketNo];
		PNode pPrev = nullptr, pRet = nullptr;
		while (pCur)
		{
			if (pCur->_data == data)
			{
				if (pCur == _ht[bucketNo])
					_ht[bucketNo] = pCur->_pNext;
				else
					pPrev->_pNext = pCur->_pNext;
				pRet = pCur->_pNext;
				delete pCur;
				_size--;
				return pRet;
			}
		}
		return nullptr;
	}
	PNode* Find(const V& data);
	size_t Size()const;
	bool Empty()const;
	void Clear();
	bool BucketCount()const;
	void Swap(HashBucket<V, HF>& ht;
	~HashBucket();
private:
	size_t HashFunc(const V& data)
	{
		return data % _ht.capacity();
	}
private:
	vector<PNode*> _ht;
	size_t _size; // 哈希表中有效元素的个数
};

3.开散列增容

桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可
能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希
表进行增容,那该条件怎么确认呢?开散列最好的情况是:每个哈希桶中刚好挂一个节点,
再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可
以给哈希表增容。

void _CheckCapacity()
{
	size_t bucketCount = BucketCount();
	if (_size == bucketCount)
	{
		vector<Node*> newTable;
		newTable.resize(_table.size() * 2, nullptr);
		//移动结点
		for (auto cur : _table)
		{
			while (cur)
			{
				Node* next = cur->_next;
				size_t hashi = cur->_data % newTable.size();
				//头插入新链表
				cur->_next = newTable[hashi];
				newTable[hashi] = cur;
				cur = next;
			}
		}
		_table.clear();
		//交换数组,自动析构原数组
		swap(newTable, _table);
	}
}

4.非整形key的开散列

1.如何将类型变为size_t类型 

字符串哈希算法!!!

//如果是整形的相似类型,使用强转
template <class K>
class Hash{
public:
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//如果是字符串,模板特化,我们需要自己实现仿函数!
template<>
class Hash<string> 
{
public:
	size_t operator()(const string& key)
	{
		size_t keyi = 0;
		for (int i = 0;i < key.size();i++)
		{
			keyi += key[i];
		}
		return keyi;
	}
};

2.开辟空间开素数个效果好

//素数表
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];
}


三、模拟实现unordered系列

3.1 哈希表的改造

        1.模板参数列表的改造

// K:关键码类型
// V: 不同容器V的类型不同,如果是unordered_map,V代表一个键值对,如果是
unordered_set,V 为 K
// KeyOfValue: 因为V的类型不同,通过value取key的方式就不同,详细见
unordered_map/set的实现
// HF: 哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将Key转换为整形数字才能
取模
template<class K, class V, class KeyOfValue, class HF = DefHashF<T> >
class HashBucket;

        2.增加迭代器操作

//哈希桶中与迭代器相互引用双方,迭代器用到桶需要首先申明一下!
//前置申明模板类,模板也一定要带上!!!
template<class K, class T, class KeyofT, class HashFunc>
class HashTable;
template<class K, class T, class ref, class ptr, class HashFunc, class KeyofT>
struct Hash_iterator
{
	typedef HashNode<T> Node;
	typedef Hash_iterator<K, T, ref, ptr, HashFunc, KeyofT> Self;
	typedef HashTable<K, T, KeyofT, HashFunc> Ht;
	typedef Hash_iterator<K, T, T&, T*, HashFunc, KeyofT> iterator;//申明一下,与std区别

	Node* pnode;
	Ht* _ht;//传哈希表是因为++中需要查找桶!

	Hash_iterator(Node* p, Ht* ht)
		:pnode(p)
		, _ht(ht)
	{}


	Hash_iterator(const iterator& it)
		:pnode(it.pnode)
		,_ht(it._ht)
	{}
	//-->
	Self& operator++()
	{
		//判断是否为该桶链表的尾 1.是 -> 判断下一个桶是否为空 2.否 直接等于next
		if (pnode->_next)
			pnode = pnode->_next;
		else {
		     HashFunc Hash;
		     KeyofT getK;
			size_t hashi = Hash(getK(pnode->_data)) % _ht->_table.size()+1;
			while (hashi < _ht->_table.size())
			{
				//判断桶是否为空桶
				if (_ht->_table[hashi])
				{
					pnode = _ht->_table[hashi];
					break;
				}
				else ++hashi;
			}
			//后面没有桶了
			if (hashi == _ht->_table.size())
				pnode = nullptr;
		}
		return *this;
	}

	ref operator*()
	{
		return pnode->_data;
	}

	ptr operator->()
	{
		return &pnode->_data;
	}

	bool operator==(const Self& p)const
	{
		return pnode == p.pnode;
	}
	bool operator!=(const Self& p)const
	{
		return pnode != p.pnode;
	}
};

        3、增加通过key获取value操作

template<class K,class T,class KeyofT,class HashFunc>
class HashTable
{
public:
	typedef HashNode<T> Node; //结点
	template<class K, class T, class ref,class ptr, class HushFunc,class KeyofT>
	friend struct Hash_iterator;
	template<class K, class T, class ref, class ptr, class HushFunc, class KeyofT>
	friend struct Hash_const_iterator;
//  Key      Val&       Val*     类型->整数映射     Val中取Key
	
	typedef Hash_iterator<K, T,T&, T*,HashFunc,KeyofT> iterator;
	typedef Hash_const_iterator<K, T, const T&, const T*, HashFunc, KeyofT> const_iterator;

	HashTable()
	{
		//__stl_next_prime(0)
		_table.resize(5, nullptr);
		_n = 0;
	}
	pair<iterator,bool> insert(const T& data)
	{
		
		HashFunc Hash;
		KeyofT getK;
		iterator it = Find(getK(data));
		if (it != end())
		return make_pair(it, false);
		//判断负载因子-->扩容?
		if (_n / _table.size() == 1)
		{
			vector<Node*> newTable;
			//__stl_next_prime(_table.size() * 2
			newTable.resize(_table.size()*2,nullptr);
			//移动结点
			for (auto cur : _table)
			{
				while (cur)
				{
					Node* next = cur->_next;
					size_t hashi = Hash(getK(cur->_data)) % newTable.size();
					//头插入新链表
					cur->_next = newTable[hashi];
					newTable[hashi] = cur;
					cur = next;
				}
			}
			_table.clear();
			swap(newTable, _table);
		}
		size_t hashi = Hash(getK(data)) % _table.size();
		Node* newnode = new Node(data);
		if (_table[hashi])
		{
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
		}
		else {
			_table[hashi] = newnode;
		}
		++_n;
		return make_pair(iterator(newnode,this),true);
	}
	
	
	iterator Find(const K& key)
	{
		HashFunc Hash;
		KeyofT getK;
		size_t hashi = Hash(key) % _table.size();
		Node* cur = _table[hashi];
		Node* next=nullptr;
		while (cur)
		{
			next = cur->_next;
			if (getK(cur->_data) == key)
				return iterator(cur,this);
			cur = next;
		}
		return end();
	}
	bool Erase(const K& key)
	{
		HashFunc Hash;
		KeyofT getK;
		size_t hashi = Hash(key) % _table.size();
		Node* cur = _table[hashi];
		Node* prev = nullptr;
		while (cur)
		{
			if (getK(cur->_data) == key)
			{
				if (cur == _table[hashi])
					_table[hashi] = cur->_next;
				else prev->_next = cur->_next;

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

	iterator begin()
	{
		for (int i = 0;i < _table.size();i++)
		{
			if (_table[i])
				return iterator(_table[i], this);//!!! this 太妙了
		}
		return iterator(nullptr,this);
	}
	const_iterator begin()const
	{
		for (int i = 0;i < _table.size();i++)
		{
			if (_table[i])
				return const_iterator(_table[i], this);//!!! this 太妙了
		}
		return const_iterator(nullptr, this);
	}

	iterator end()
	{
		return iterator(nullptr, this);
	}
	const_iterator end()const
	{
		return const_iterator(nullptr, this);
	}
	~HashTable()
	{
		Node* cur = nullptr;
		Node* next = nullptr;
		for (int i = 0;i < _table.size();i++)
		{
			cur = _table[i];
			while (cur)
			{
				next = cur->_next;
				delete cur;
				cur = next;
			}
		}
	}
private:
	vector<Node*> _table;
	size_t _n;
};

3.2 模拟实现unordered_map

namespace wyz
{
	template<class K, class V>
	class unorder_map
	{
	public:
		struct GetKey
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
		typedef HashTable<K, pair<K, V>, GetKey, Hash<K>> hash;

		typedef typename hash::iterator iterator;

		typedef typename hash::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>& val)
		{
			return _ht.insert(val);
		}
		iterator Find(const K& val)
		{
			return _ht.Find(val);
		}

		bool Erase(const K& val)
		{
			return _ht.Erase();
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}
		const V& operator[](const K& key)const
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		hash _ht;//底层是哈希表
	};
}

测试代码:

void Test_unorder_map()
{

	string arr[] = { "苹果", "西瓜", "香蕉", "草莓", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	wyz::unorder_map<string, int> countMap;
	for (auto e : arr)
	{
		countMap[e]++;
	}
	wyz::unorder_map<string, int>::iterator it = countMap.begin();
	while (it != countMap.end())
	{
		
		cout << it->first << ":" << it->second << endl;
		++it;
	}
}

测试map const 迭代器:

void Test_unordermap()
{
	int arr[] = { 9,2,1,10,3,56,78,28,30,29 };
	wyz::unorder_map<int, int> countMap;
	for (auto e : arr)
	{
		countMap[e]++;
	}
	wyz::unorder_map<int, int>::const_iterator it = countMap.begin();
	while (it != countMap.end())
	{
		++(it->second);
		cout << it->first << ":" << it->second << endl;
		++it;
	}
}


3.3 模拟实现unordered_set

template <class K>
class unorder_set
{
public:
	struct GetKey
	{
		const K& operator()(const K& val)
		{
			return val;
		}
	};
	typedef HashTable<K, K, GetKey,Hash<K>> hash;
	typedef typename hash::const_iterator iterator;
	typedef typename hash::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 K& k)
	{
		//注意ret类型中的迭代器是普通迭代器 
		pair<typename hash::iterator, bool> ret = _ht.insert(k);
		//我们这里需要用到普通迭代器拷贝构造const迭代器!!!
		return make_pair(iterator(ret.first), ret.second);
	}

	bool Find(const K& val)
	{
		return _ht.Find(val);
	}

	bool Erase(const K& val)
	{
		return _ht.Erase();
	}

private:
	hash _ht;
};

测试代码:

void Test_unorder_set()
{
	wyz::unorder_set<int> myset;
	int arr[10] = { 9,8,5,3,2,1,6,7,4,11 };
	for (auto e : arr)
	{
		myset.insert(e);
	}
	wyz::unorder_set<int>::iterator it = myset.begin();
	while (it != myset.end())
	{
		//++(*it);
		cout << *it << ' ';
		++it;
	}
}

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

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

相关文章

ros::catkin_create_pkg

用下面的命令即可 catkin_create_pkg first_pkg rospy roscpp std_msg -m ur-email-name

HBase(一)HBase v2.2 高可用多节点搭建

最近刚刚完成了HBase相关的一个项目,作为项目的技术负责人,完成了大部分的项目部署,特性调研工作,以此系列文章作为上一阶段工作的总结. 前言 其实目前就大多数做应用的情况来讲,我们并不需要去自己搭建一套HBase的集群,现有的很多云厂商提供的服务已经极大的方便日常的应用使…

接口测试工具——Postman使用详解

目录 Postman简介 Postman主界面 菜单栏 工具栏 请求管理区 环境管理区 请求设计区 发送请求 发送GET请求 Postman发送GET请求 发送表单格式POST请求 发送JSON格式POST请求 发送XML格式POST请求 发送文件上传类型的请求 响应 环境和变量 环境变量设置 环境变量…

【Ceph的介绍】

目录 1、存储基础1、单机存储设备2、单机存储的问题3、商业存储解决方案4、分布式存储&#xff08;软件定义的存储 SDS&#xff09;1、分布式存储的类型 2、Ceph 简介3、Ceph 优势4、Ceph 架构5、Ceph 核心组件1、Pool中数据保存方式支持两种类型2、Pool、PG 和 OSD 的关系 6、…

测试用例设计方法-场景法详解

01、定义 场景法是通过运用场景来对系统的功能点或业务流程的描述&#xff0c;从而提高测试效果的一种方法。 场景法一般包含基本流和备用流&#xff0c;从一个流程开始&#xff0c;通过描述经过的路径来确定的过程&#xff0c;经过遍历所有的基本流和备用流来完成整个场景。…

SOPC之NiosⅡ系统(四)

NIOS Ⅱ系统实例&#xff0c;参考自特权同学《勇敢的芯-伴你玩转NIOS Ⅱ》 一些基础操作就不再赘述 目录 1.创建Quartus项目 1.2 进入Platform Designer添加组件并设置 1.2.1 设置时钟频率50MHz&#xff1b; 1.2.2 添加Nios Ⅱ组件 1.2.3 添加RAM组件 1.2.4 设置Nios Ⅱ…

【每日随笔】摩托车安全驾驶 ① ( 摩托车骑行准备 | 买好保险 | 摩托车必要改装 - 护杠 + 行车记录仪 | 骑行护具 )

文章目录 一、摩托车骑行准备1、买好保险2、摩托车必要改装 - 护杠 行车记录仪3、骑行护具 德州考驾照归来 , 提了一辆 铃木 UY125 , 注意安全驾驶 , 以后上班就骑摩托车了 ; 由于居住证上的地址是海淀区 , 目前住在学院路 , 导致无法把车落户到自己名下 , 只能上公户了 ; 车…

G1垃圾收集器-JVM(十三)

上篇文章说了CMS垃圾收集器使用以及三色标记如何解决cms的一些问题。分别有初始标记&#xff0c;并发标记&#xff0c;重新标记&#xff0c;并发清理&#xff0c;并发重置。 CMS垃圾收集器&三色标记-JVM&#xff08;十二&#xff09; G1收集器&#xff08;Garbage-First&a…

浅析缓存一致性的解析方案

各位同学们平时开发的时候除了使用到数据库&#xff08;这里以mysql为例&#xff09;还会用到相关的缓存&#xff08;这里以redis为例&#xff09;操作。 举一个常用的场景当我们写的接口性能相对比较慢的时候&#xff08;高并发场景需要响应速度很快&#xff09;为了保证性能的…

LeetCode144. 二叉树的前序遍历

144. 二叉树的前序遍历 文章目录 [144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/)一、题目二、思路及代码&#xff08;1&#xff09;递归&#xff08;2&#xff09;迭代&#xff08;两种方法&#xff09; 一、题目 给你二叉树的根节点…

AlienSwap 首期 Launchpad — 偶像女团 NFT+RWA 的创新探索

NFT 是整个加密市场一致看好&#xff0c;并认为会继续爆发的领域。随着更多的 NFT 平台和 NFT 项目的推出&#xff0c;NFT 市场的格局也在不断变化。从开始的 OpenSea 占据绝对领先地位&#xff0c;到 Blur 的横空出世风头无两&#xff0c;在加密领域&#xff0c;局势更迭总是在…

【Java面试丨并发编程】线程中并发安全

一、Synchronized关键字的底层原理 1. Synchronized的作用 Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】&#xff0c;其他线程再想获取这个【对象锁】时就会阻塞住 2. Monitor Synchronized【对象锁】底层是由Monitor实现&#xff0c;…

泰裤辣!这是什么操作,自动埋点,还能传参?

目录 前言 参数放在注释中 准备入口文件 编写插件 运行代码 完整代码 参数放在局部作用域中 准备源代码 编写插件 运行代码 完整代码 总结 前言 在上篇文章讲了如何通过手写babel插件自动给函数埋点之后&#xff0c;就有同学问我&#xff0c;自动插入埋点的函数怎么…

基于IMX6ULL的AP3216C的QT动态数据曲线图显示

前言&#xff1a;本文为手把手教学 LinuxQT 的典型基础项目 AP3216C 的数据折线图显示&#xff0c;项目使用正点原子的 IMX6ULL 阿尔法( Cortex-A7 系列)开发板。项目需要实现 AP3216C 在 Linux 系统下的驱动&#xff0c;使用 QT 设计 AP3216C 的数据显示页面作为项目的应用层。…

消息中间件RabbitMQ简介

1.1消息队列中间件简介 消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合&#xff0c;异步消息&#xff0c;流量削锋等问题实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性[架构] 使用较多的消息队列有ActiveMQ&#xff0c;RabbitMQ&#xff…

人工智能安全风险:零信任的作用

人工智能&#xff08;AI&#xff09;和机器学习技术飞速发展&#xff0c;我们所处的时代正在经历前所未有的创新。但是&#xff0c;技术飞速发展的同时也带来了各种挑战。人工智能技术越来越复杂&#xff0c;与之相关的网络安全风险也越来越棘手&#xff0c;随之产生了一个新的…

TortoiseGit 入门指南10:贮藏

有时&#xff0c;当你在项目的一部分上已经工作一段时间后&#xff0c;所有东西都进入了混乱的状态&#xff0c; 而这时你想要切换到另一个分支做一点别的事情。 问题是&#xff0c;你不想仅仅因为过会儿回到这一点而为做了一半的工作创建一次提交。 针对这个问题的答案是贮藏 …

【Linux指令集】---unzip指令(超详细)

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【Linux专栏】&#x1f388; 本专栏旨在分享学习Linux的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 演示环境&#xff1…

JVM系统优化实践(19):GC生产环境案例(二)

您好&#xff0c;这里是「码农镖局」CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e; 接昨天的问题继续来说&#xff0c;在高并发场景中&#xff0c;对象过多容易导致OOM。由于高并发导致Young GC存活对象过多&#xff0c;因此会有太多对象进入老年代&#xf…

关于unity Content Size Fitter 套 Content Size Fitter

首先&#xff1a;最好不要unity Content Size Fitter 套 Content Size Fitter 这样最后得到的变化可能会错误 unity也提示了&#xff0c;父物体如果有了&#xff0c;那么子物体就不要再加了。 但是你们要的需求&#xff1a; 一级父物体 ➡自适应大小➡二级父物体&#xff08…