【C++】STL——用一个哈希表封装出unordered_map和unordered_set

这篇具有很好参考价值的文章主要介绍了【C++】STL——用一个哈希表封装出unordered_map和unordered_set。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

用一个哈希表(桶)封装出unordered_map和unordered_set

【C++】STL——用一个哈希表封装出unordered_map和unordered_set

一、哈希表源码

根据先前对unordered_map和unordered_set的学习,我们得知其底层是借助哈希表来实现的,接下来我们会使用上篇博客实现的开散列哈希表来模拟实现unordered_map和unordered_set,哈希表源代码链接:

Hash/Hash/HashBucket.h · wei/cplusplus - 码云 - 开源中国 (gitee.com)

下面我们对哈希表进行改造,使其能够很好的封装unordered_map与unordered_set。


二、哈希函数模板参数的控制

我们都清楚unorderded_map是KV模型,而unordered_set是K模型,而先前实现的哈希表(桶)是pair键值对的KV模型,很显然不适用unordered_set的K模型,因此要实现一个泛型,这就需要我们对哈希表的模板参数进行控制。

更改哈希节点的模板参数:

  • 为了与原哈希表的模板参数进行区分,这里将哈希表的第二个模板参数设为T,如果后续传入节点的类型为K模型,则T为K模型,若为pair键值对KV模型,则为KV模型

【C++】STL——用一个哈希表封装出unordered_map和unordered_set

template<class T>
struct HashNode
{
	T _data;
	HashNode<T>* _next;
	//构造函数
	HashNode(const T& data)
		:_data(data)
		, _next(nullptr)
	{}
};

更改哈希表的模板参数:

  • 这里我们把第二个模板参数设为T,便于识别后续传入的数据类型
//unordered_map -> HashTable<K, pair<K, V>> _ht;
//unordered_set -> HashTable<K, K> _ht;
template<class K, class T, class Hash = HashFunc<K>>

unordered_set的参数控制:

  • 如果上层使用的是unordered_set容器,那么哈希表的参数对应的就是K,K类型。
template<class K>
class unordered_set
{
private:
	HashBucket_realize::HashBucket<K, K, Hash, SetKeyOfT> _hb;
};

unordered_map的参数控制:

  • 如果上层使用的是unordered_map容器,那么哈希表的参数对应的就是K,pair<K, V>类型。
template<class K, class V>
class unordered_map
{
private:
	HashBucket_realize::HashBucket<K, pair<const K, V>, Hash, MapKeyOfT> _hb;
};

三、对上层容器构建仿函数便于后续映射

在哈希映射的过程中,我们需要获得元素的键值,然后通过对应的哈希函数计算获得映射的地址,上一步为了适配unordered_set和unordered_map,我们对模板参数进行了整改,现在哈希节点存储的数据类型是T,T可能是一个键值,也可能是一个键值对,底层的哈希并不知道传入的数据是啥类型,因此我们需要对上层容器套一层仿函数来告诉底层哈希。

unordered_set的仿函数:

  • 针对于unordered_set这种K类型的容器,我们直接返回key即可。
template<class K>
class unordered_set
{
	//仿函数
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
private:
	HashBucket_realize::HashBucket<K, K, Hash, SetKeyOfT> _hb;
};

注意:

虽然unordered_set容器传入哈希表的T就是键值,但是底层哈希表并不知道上层容器的种类,底层哈希表在获取键值时会统一通过传入的仿函数进行获取,因此unordered_set容器也需要向底层哈希表提供一个仿函数。

unordered_map的仿函数:

  • unordered_map的数据类型是pair键值对,我们只需要取出其键值对中的第一个数据key然后返回即可。
template<class K, class V>
class unordered_map
{
	//仿函数
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
private:
	HashBucket_realize::HashBucket<K, pair<const K, V>, Hash, MapKeyOfT> _hb;
};

注意:

现在由于我们在哈希结点当中存储的数据类型是T,这个T可能就是一个键值,也可能是一个键值对,对于底层的哈希表来说,它并不知道哈希结点当中存储的数据究竟是什么类型,因此需要由上层容器提供一个仿函数,用于获取T类型数据当中的键值。

更改底层哈希的模板参数:

  • 上层容器的仿函数已经套好,下面需要整改下底层哈希的模板参数来接收上层容器的数据类型。
template<class K, class T, class Hash, class KeyOfT>
class HashBucket
  • 第一个参数K:key的类型就是K。查找函数是根据key来查找的,所以需要K。

  • 第二个参数T:哈希表结点存储的数据类型。比如int,double,pair,string等。

  • 第三个参数KeyOfT:拿到T类型(结点数据类型)的key。

  • 第四个参数Hash:表示使用的哈希函数


四、string类型无法取模问题

  • 字符串无法取模,是哈希问题中最常见的问题。

经过上面的分析后,我们让哈希表增加了一个模板参数,此时无论上层容器是unordered_set还是unordered_map,我们都能够通过上层容器提供的仿函数获取到元素的键值。

但是在我们日常编写的代码中,用字符串去做键值key是非常常见的事,比如我们用unordered_map容器统计水果出现的次数时,就需要用各个水果的名字作为键值。

而字符串并不是整型,也就意味着字符串不能直接用于计算哈希地址,我们需要通过某种方法将字符串转换成整型后,才能代入哈希函数计算哈希地址。

但遗憾的是,我们无法找到一种能实现字符串和整型之间一对一转换的方法,因为在计算机中,整型的大小是有限的,比如用无符号整型能存储的最大数字是4294967295,而众多字符能构成的字符串的种类却是无限的。

鉴于此,无论我们用什么方法将字符串转换成整型,都会存在哈希冲突,只是产生冲突的概率不同而已。

经过前辈们实验后发现,BKDRHash算法无论是在实际效果还是编码实现中,效果都是最突出的。该算法由于在Brian Kernighan与Dennis Ritchie的《The C Programing Language》一书被展示而得名,是一种简单快捷的hash算法,也是Java目前采用的字符串的hash算法。

因此,现在我们需要在哈希表的模板参数中再增加一个仿函数,用于将键值key转换成对应的整型。

template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
class HashBucket

若是上层没有传入该仿函数,我们则使用默认的仿函数,该默认仿函数直接返回键值key即可,但是用字符串作为键值key是比较常见的,因此我们可以针对string类型写一个类模板的特化,此时当键值key为string类型时,该仿函数就会根据BKDRHash算法返回一个对应的整型。

template<class K>
struct Hash
{
	size_t operator()(const K& key) //返回键值key
	{
		return key;
	}
};
//string类型的特化
template<>
struct Hash<string>
{
	size_t operator()(const string& s) //BKDRHash算法
	{
		size_t value = 0;
		for (auto ch : s)
		{
			value = value * 131 + ch;
		}
		return value;
	}
};

五、哈希表默认成员函数实现

1.构造函数

哈希表中有两个成员变量,当我们实例化一个对象时:

  • _table会自动调用vector的默认构造函数进行初始化。
  • _n会根据我们所给的缺省值被设置为0。
vector<Node*> _table; //哈希表
size_t _n = 0; //哈希表中的有效元素个数

我们写一个构造函数,并且用素数表的第一个数据初始化相应的空间。

//构造函数
//HashBucket() = default; //显示指定生成默认构造函数
HashBucket()
	:_n(0)
{
	//_tables.resize(10);
	_tables.resize(__stl_next_prime(0));
}

注意:

如果我们不初始化空间,我们就不需要编写构造函数,使用默认生成的构造函数就足够了,但是由于我们后面需要编写拷贝构造函数,编写了拷贝构造函数后,默认的构造函数就不会生成了,此时我们需要使用default关键字显示指定生成默认构造函数。


2.拷贝构造函数

哈希表在拷贝时需要进行深拷贝,否则拷贝出来的哈希表和原哈希表中存储的都是同一批结点。

哈希表的拷贝构造函数实现逻辑如下:

  1. 将哈希表的大小调整为ht._table的大小。
  2. 将ht._table每个桶当中的结点一个个拷贝到自己的哈希表中。
  3. 更改哈希表当中的有效数据个数。
//拷贝构造函数
HashBucket(const HashBucket& hb)
{
	//1、将哈希表的大小调整为hb._tables的大小
	_tables.resize(hb._tables.size());
	//2、将hb._tables每个桶当中的结点一个个拷贝到自己的哈希表中(深拷贝)
	for (size_t i = 0; i < hb._tables.size(); i++)
	{
		if (ht._tables[i]) //桶不为空
		{
			Node* cur = hb._tables[i];
			while (cur) //将该桶的结点取完为止
			{
				Node* copy = new Node(cur->_data); //创建拷贝结点
				//将拷贝结点头插到当前桶
				copy->_next = _tables[i];
				_tables[i] = copy;
				cur = cur->_next; //取下一个待拷贝结点
			}
		}
	}
	//3、更改哈希表当中的有效数据个数
	_n = hb._n;
}

3.赋值运算符重载函数

实现赋值运算符重载函数时,可以通过参数间接调用拷贝构造函数,之后将拷贝构造出来的哈希表和当前哈希表的两个成员变量分别进行交换即可,当赋值运算符重载函数调用结束后,拷贝构造出来的哈希表会因为出了作用域而被自动析构,此时原哈希表之前的数据也就顺势被释放了。

//赋值运算符重载函数
HashBucket& operator=(HashBucket hb)
{
	//交换哈希表中两个成员变量的数据
	_table.swap(hb._table);
	swap(_n, hb._n);

	return *this; //支持连续赋值
}

4.析构函数

因为哈希表当中存储的结点都是new出来的,因此在哈希表被析构时必须进行结点的释放。在析构哈希表时我们只需要依次取出非空的哈希桶,遍历哈希桶当中的结点并进行释放即可。

//析构函数
~HashBucket()
{
	//将哈希表当中的结点一个个释放
	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; //将该哈希桶置空
	}
}

六、哈希表底层迭代器的实现

1.迭代器基本框架

哈希表的正向迭代器实际上就是对哈希结点指针进行了封装,但是由于在实现++运算符重载时,需要在哈希表中去寻找下一个非空哈希桶,因此每一个正向迭代器中除了存放节点指针外,还都应该存储一个哈希表的地址。最后写个构造函数,初始化_ node和_hb。

你会发现这样一个现象,迭代器里面用了哈希表,哈希表里用了迭代器,也即两个类互相引用

  • 如果迭代器写在哈希表前面,那么编译时编译器就会发现哈希表是无定义的(编译器只会往前/上找标识符)。

  • 如果哈希表写在迭代器前面,那么编译时编译器就会发现迭代器是无定义的。

这里我们的迭代器的位置放在了哈希表(HashBucket)的上面,而我迭代器内部又使用了HashBucket,因为编译器是向上寻找,按照此位置摆放,迭代器的类里是找不到HashBucket的,所以我们需要把HashBucket的类在迭代器前面进行声明

// 哈希表前置声明
template<class K, class T, class Hash, class KeyOfT>
class HashBucket;
//正向迭代器
template<class K, class T, class Hash, class KeyOfT>
struct __HTIterator
{
	typedef HashNode<T> Node; //哈希节点的类型
	typedef __HTIterator<K, T, Hash, KeyOfT> Self; //正向迭代器的类型
	typedef HashBucket<K, T, Hash, KeyOfT> HB; // 哈希表

	Node* _node; // 节点指针
	HB* _hb; // 哈希表地址
	// 构造函数
	__HTIterator(Node* node, HB* hb);
	// *运算符重载
	T& operator*();
	// ->运算符重载
	T* operator->();
	//==运算符重载
	bool operator==(const Self& s) const;
	//!=运算符重载
	bool operator!=(const Self& s) const;
	//++运算符重载
	Self& operator++();
    
    Self& operator++(int);
};

2.++运算符重载

假设此时的哈希表结构如图所示:

【C++】STL——用一个哈希表封装出unordered_map和unordered_set

注意这里是一个哈希桶结构,每一个桶都是一串单链表,在单个桶中,我们拿到头节点指针,可以挨个遍历++it走完这个链表,但是这是多串单链表,我们需要保证在一个桶走完后要到下一个桶去,因此在++运算符重载的设定上要按照如下规则:

  • 若当前结点不是当前哈希桶中的最后一个结点,则++后走到当前哈希桶的下一个结点。
  • 若当前结点是当前哈希桶的最后一个结点,则++后走到下一个非空哈希桶的第一个结点。
//++运算符重载
Self& operator++()
{
	if (_node->_next)
	{
		_node = _node->_next;
	}
	else//当前桶已经走完,需要到下一个不为空的桶
	{
		KeyOfT kot;//取出key数据
		Hash hash;//转换成整型
		size_t hashi = hash(kot(_node->_data)) % _hb->_tables.size();
        ++hashi;
		while(hashi < _hb->_tables.size())
        {
			if (_hb->_tables[hashi])//更新节点指针到非空的桶
			{
				_node = _hb->_tables[hashi];
				break;
			}
            else
            {
                hashi++;
            }
		}
		//没有找到不为空的桶,用nullptr去做end标识
		if (hashi == _hb->_tables.size())
		{
			_node = nullptr;
		}
	}
	return *this;
}

// 后置++
Self& operator++(int) 
{
	Self tmp = *this;
	operator++();
	return tmp;
	}

由于正向迭代器中++运算符重载函数在寻找下一个结点时,会访问哈希表中的成员变量_ tables,而 _tables成员变量是哈希表的私有成员,因此我们需要将正向迭代器类声明为哈希表类的友元。

template<class K, class T, class KeyOfT, class HashFunc>
class HashTable
{
	//把迭代器设为HashTable的友元
	template<class K, class T, class KeyOfT, class HashFunc>
	friend class __HTIterator;

	typedef HashNode<T> Node;//哈希结点类型
public:
	//……
}

注意:哈希表的迭代器是单向迭代器,没有–运算符重载。


3.== 和 != 运算符重载

比较两迭代器是否相等,只需要判断两迭代器所封装的节点是否是同一个即可。

//!=运算符重载
bool operator!=(const Self& s) const
{
	return _node != s._node;
}
//==运算符重载
bool operator==(const Self& s) const
{
	return _node == s._node;
}

4.* 和 -> 运算符重载

  • *运算符返回的是哈希节点数据的引用
  • ->运算符返回的是哈希节点数据的地址
//*运算符重载
T& operator*()
{
	return _node->_data;//返回哈希节点中数据的引用
}
//->运算符重载
T* operator->()
{
	return &(_node->_data);//返回哈希节点中数据的地址
}

七、哈希表的begin和end

这里我们需要进行正向迭代器类型的typedef,需要注意的是,为了让外部能够使用typedef后的正向迭代器类型iterator,我们需要在public区域进行typedef。typedef之后,就可以实现begin()和end()的函数了。

template<class K, class T, class KeyOfT, class Hash>
class HashBucket
{
 //把迭代器设为HashTable的友元
	template<class K, class T, class KeyOfT, class Hash>
	friend class __HTIterator;

	typedef HashNode<T> Node;//哈希结点类型
public:
	typedef __HTIterator<K, T, KeyOfT, Hash> iterator;//正向迭代器的类型
}

begin():

  1. 遍历哈希表,返回第一个不为空的桶的第一个节点的迭代器位置,借助正向迭代器的构造函数完成,还得传this指针(哈希表的指针)
  2. 若遍历结束没找到,说明哈希表里没有一个是空,直接返回end()尾部的迭代器位置。
//begin
iterator begin()
{
	for (size_t i = 0; i < _tables.size(); i++)
	{
		Node* cur = _tables[i];
		//找到第一个不为空的桶的节点位置
		if (cur)
		{
			return iterator(cur, this);
		}
	}
	return end();
}

end():

  • 哈希表的end()直接返回迭代器的构造函数(节点指针为空,哈希表指针为this)即可。
//end()
iterator end()
{
	return iterator(nullptr, this);
}

八、哈希表的优化(素数表)

在除留余数法时,最好模一个素数,这样模完后不会那么容易出现哈希冲突的问题,因此我们可以专门写一个素数表来解决。

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] =
	{
		53ul, 97ul, 193ul, 389ul, 769ul,
		1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
		49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
		1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
		50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
		1610612741ul, 3221225473ul, 4294967291ul
	};
    
	// 获取比prime大那一个素数
	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_map的数据类型是KV模型,其插入的是一个pair键值对,这里要区别于unordered_set,实现方式也有所区别。

【C++】STL——用一个哈希表封装出unordered_map和unordered_set

unordered_set插入的是key值,我们这里要对它们insert的返回值做出修改:

【C++】STL——用一个哈希表封装出unordered_map和unordered_set

【C++】STL——用一个哈希表封装出unordered_map和unordered_set

因为unordered_set没有[]运算符重载,所以不必提供该函数,只有在 unordered_map 中提供此函数。

  1. 首先调用insert函数插入键值对返回迭代器ret
  2. 通过返回的迭代器ret调用元素值value

注意键值对的第一个参数为用户传入的 key 值,第二个参数是用户声明的第二个模板参数的默认构造函数

//[]运算符重载
V& operator[](const K& key)
{
	pair<iterator, bool> ret = insert(make_pair(key, V()));
	return ret.first->second;
}

接下来我们要对哈希表的 Insert 的返回值进行改动,进而契合 unordered_map 的 pair 数据类型。改动有两处,如下:

【C++】STL——用一个哈希表封装出unordered_map和unordered_set


十、哈希表(修改版)源码链接

修改完善后的哈希表源代码链接:
HashBucket.h · wei/cplusplus - 码云 - 开源中国 (gitee.com)


十一、unordered_set、unordered_map的模拟实现代码

1.unordered_set的代码

#pragma once
#include "HashBucket.h"

namespace unordered_set_realize
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		typedef typename HashBucket_realize::HashBucket<K, K, Hash, SetKeyOfT>::iterator iterator;

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

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

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

	private:
		HashBucket_realize::HashBucket<K, K, Hash, SetKeyOfT> _hb;
	};

	void test_unordered_set()
	{
		unordered_set<int> us;
		us.insert(13);
		us.insert(3);
		us.insert(23);
		us.insert(5);
		us.insert(5);
		us.insert(6);
		us.insert(15);
		us.insert(223342);
		us.insert(22);

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

		for (auto e : us)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

2.unordered_map的代码

#pragma once
#include "HashBucket.h"

namespace unordered_map_realize
{
	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 HashBucket_realize::HashBucket< K, pair<const K, V>, Hash, MapKeyOfT>::iterator iterator;
	
		iterator begin()
		{
			return _hb.begin();
		}

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

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

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _hb.Insert(make_pair(key, V()));
			return ret.first->second;
		}

	private:
		HashBucket_realize::HashBucket<K, pair<const K, V>, Hash, MapKeyOfT> _hb;
	};

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

参考博客:

1、STL详解(十三)—— 用一个哈希表同时封装出unordered_map和unordered_set_2021 dragon文章来源地址https://www.toymoban.com/news/detail-417523.html

到了这里,关于【C++】STL——用一个哈希表封装出unordered_map和unordered_set的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】开散列哈希表封装实现unordered_map和unordered_set

    在未达成目的之前,一切具有诱惑力的事物都显得那么不堪一击 1. 在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2 N l o g 2 ​ N ,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。 最好的查询是

    2023年04月09日
    浏览(84)
  • C++利用开散列哈希表封装unordered_set,unordered_map

    1.之前我们已经实现了开散列的哈希表,今天我们来用它封装unordered_set,unordered_map 2.本文的封装比利用红黑树封装set和map更加复杂 建议大家先去看我的红黑树封装set和map再来看本文 因为有很多地方跟红黑树封装set和map时是同样的思路和方法,所以本文不会太去赘述一遍 因为un

    2024年03月24日
    浏览(56)
  • 【C++学习】哈希表的底层实现及其在unordered_set与unordered_map中的封装

    🎉个人名片: 🐼作者简介:一名乐于分享在学习道路上收获的大二在校生 🙈个人主页🎉:GOTXX 🐼个人WeChat:ILXOXVJE 🐼本文由GOTXX原创,首发CSDN🎉🎉🎉 🐵系列专栏:零基础学习C语言----- 数据结构的学习之路----C++的学习之路 🐓每日一句:如果没有特别幸运,那就请特

    2024年04月16日
    浏览(47)
  • 改造哈希表,封装unordered_map和unordered_set

    正文开始前给大家推荐个网站,前些天发现了一个巨牛的 人工智能 学习网站, 通俗易懂,风趣幽默 ,忍不住分享一下给大家。点击跳转到网站。 unordered_map是存的是pair是K,V型的,而unordered_set是K型的,里面只存一个值,那我们如何利用一个数据结构将他们都封装出来呢?

    2024年02月03日
    浏览(48)
  • 从C语言到C++_31(unordered_set和unordered_map介绍+哈希桶封装)

    目录 1. unordered_set和unordered_map 1.1 unordered_map 1.2 unordered_set 1.3 unordered系列写OJ题 961. 在长度 2N 的数组中找出重复 N 次的元素 - 力扣(LeetCode) 349. 两个数组的交集 - 力扣(LeetCode) 217. 存在重复元素 - 力扣(LeetCode) 884. 两句话中的不常见单词 - 力扣(LeetCode) 2. 实现unorder

    2024年02月13日
    浏览(41)
  • 【C++】用哈希桶模拟实现unordered_set和unordered_map

    顺序结构中(数组)查找一个元素需要遍历整个数组,时间复杂度为O(N);树形结构中(二叉搜索树)查找一个元素,时间复杂度最多为树的高度次logN。理想的搜索方法: 可以不经过任何比较,一次直接从表中得到要搜索的元素。 构造一种存储结构, 通过某种函数使元素的

    2024年04月11日
    浏览(52)
  • 【C++】unordered_set与unordered_map的封装

    🌇个人主页:平凡的小苏 📚学习格言:命运给你一个低的起点,是想看你精彩的翻盘,而不是让你自甘堕落,脚下的路虽然难走,但我还能走,比起向阳而生,我更想尝试逆风翻盘 。 🛸 C++专栏 : C++内功修炼基地 家人们更新不易,你们的👍点赞👍和⭐关注⭐真的对我真

    2024年02月08日
    浏览(48)
  • 【C++】unordered_set 和 unordered_map 使用 | 封装

    unordered_map官方文档 unordered_set 官方文档 set / map与unordered_set / unordered_map 使用功能基本相同,但是两者的底层结构不同 set/map底层是红黑树 unordered_map/unordered_set 底层是 哈希表 红黑树是一种搜索二叉树,搜索二叉树又称为排序二叉树,所以 迭代器遍历是有序的 而哈希表对应的

    2024年02月06日
    浏览(48)
  • C++ 哈希+unordered_map+unordered_set+位图+布隆过滤器(深度剖析)

    想象一下,你有一个巨大的图书馆,里面摆满了成千上万本书。如果你想要找到其中一本特定的书,你会怎么做?如果你按照书的编号来有序地排列它们,找到特定的书本可能会很容易。但是,如果书籍是随机地摆放,你可能需要逐本地查找,这个过程会非常耗时。 而哈希函

    2024年02月21日
    浏览(55)
  • 【C++进阶】使用一个哈希桶封装出unordered_map和uordered_set

    由于要使用一个哈希桶封装出unordered_map和uordered_set,所以要对哈希桶的模版参数进行改造,由于unordered_map是kv模型,而uordered_set是k模型,所以必须在实现哈希桶的时候提供模版,在封装uordered_set和unordered_map时传入不同的模版参数。 还要要修改的是,如果同时封装出,在获得

    2024年02月12日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包