【数据结构】深入理解哈希及其底层数据结构

news2024/9/9 7:22:46

目录

一、unordered系列关联式容器

二、底层结构

2.1 哈希的概念

 2.2 哈希冲突(哈希碰撞)

2.3 哈希函数

2.4 哈希冲突处理

2.4.1 闭散列(开放定址法)

2.4.1.1 代码实现:

2.4.2 开散列(链地址法,较优)

2.4.2.1 扩容

 2.4.2.2 仿函数实现多类型储存

2.4.2.3 代码实现 

 2.4.3 开散列与闭散列比较

 三、哈希表的模拟实现(加迭代器)

1.unordered_set

 2.unordered_map.h

3.test.c


一、unordered系列关联式容器

        在C++11中一共添加了4个unordered系列关联式容器,它们提供了基于哈希表的实现,以平均常数时间复杂度进行元素的查找、插入和删除操作。  分别为

std::unordered_map       std::unordered_multimap
std::unordered_set       std::unordered_multiset

        这些unordered系列的容器与STL中的mapmultimapsetmultiset相对应,但后者是基于红黑树实现的,提供的是有序的元素集合,而前者的实现则基于哈希表,提供的是无序的元素集合,且在平均情况下,对元素的查找、插入和删除操作具有更好的性能。

二、底层结构

unordered系列的容器之所以效率高,归功于它的底层哈希结构。 

2.1 哈希的概念

        在顺序结构或者树中,元素的储存位置与其值没有什么对应关系,一般查找一个值时我们都需要去经过多次比较,时间复杂度为O(n),平衡树中为树的高度,效率与比较次数直接挂钩。

这时我们想有没有一个理想的方法可以不经过任何比较,一次直接从表中得到要搜索的元素呢?

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

 在插入时:根据待插入元素的关键码,用哈希函数计算出该元素的存储位置并存放

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

        大佬们将这种方法称为哈希(或者散列)方法,而哈希方法中计算存储位置的函数称为哈希函数,构造出的表叫做哈希表(散列表)。

例如:

 2.2 哈希冲突(哈希碰撞)

 在插入时有时多个值通过哈希函数的计算会计算处相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

2.3 哈希函数

哈希冲突是无法避免的,但只要我们设计出合理的哈希函数,就能极大的降低哈希冲突的概率 

哈希函数的设计有几个原则:

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

常见的哈希函数:   

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

取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B

优点:简单、均匀

缺点:需要事先知道关键字的分布情况,适合查找比较小且连续的情况

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

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

常用的哈希函数还有数字分析法、平方取中法、折叠法、随机数法等,但上述两种方法最为常用。

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

2.4 哈希冲突处理

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

2.4.1 闭散列(开放定址法)

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

这里我们主要讲解线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止

插入: 通过哈希函数获取待插入元素在哈希表中的位置

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

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

2.4.1.1 代码实现:
template<class K>
class HashFunc
{
public:
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

template<>
class HashFunc<string>
{
public:
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;
		}
		return hash;
	}
};

namespace Open_address
{
	//枚举状态
	enum State
	{
		EMPTY,
		EXIST,
		DELETE
	};

	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
	{
	public:
		HashTable(size_t size = 10)
		{
			_tables.resize(size);
		}
		HashData<K, V>* Find(const K& key)
		{
			Hash hs;
			// 线性探测
			size_t hashi = hs(key) % _tables.size();
			while (_tables[hashi]._state != EMPTY)
			{
				if (key == _tables[hashi]._kv.first
					&& _tables[hashi]._state == EXIST)
				{
					return &_tables[hashi];
				}

				++hashi;
				hashi %= _tables.size();
			}

			return nullptr;
		}
		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;
			if (_n * 10 / _tables.size() >= 7)
			{
				HashTable<K, V, Hash> newHT(_tables.size() * 2);
				for (auto& e : _tables)
				{
					if (e._state == EXIST)
					{
						newHT.Insert(e._kv);
					}
				}
				_tables.swap(newHT._tables);
			}
			Hash hs;
			size_t hashi = hs(kv.first) % _tables.size();
			while (_tables[hashi]._state==EXIST)
			{
				hashi++;
				hashi %= _tables.size();
			}
			_tables[hashi]._kv = kv;
			_tables[hashi]._state = EXIST;
			++_n;

			return true;
		}

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				_n--;
				ret->_state = DELETE;
				return true;
			}
			else
			{
				return false;
			}
		}
	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0;
	};
}

负载因子:表内元素/表的长度 

 对于开放寻址法来说,由于所有元素都存储在哈希表的数组中,并且不使用额外的数据结构(如链表)来处理冲突,因此负载因子的控制尤为重要。一旦负载因子过高,就可能导致哈希表性能急剧下降,因为插入和查找操作可能需要遍历更多的槽位才能找到所需元素或空槽位。

一般控制在0.7~0.8之间 

闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷

2.4.2 开散列(链地址法,较优)

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

开散列中每个桶中放的都是发生哈希冲突的元素

2.4.2.1 扩容

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

//负载因子到1就扩容
if (_n == _tables.size())
{
	vector<Node*> newTables(_tables.size() * 2, nullptr);
	for (size_t i = 0; i < _tables.size(); i++)
	{
		// 取出旧表中节点,重新计算挂到新表桶中
		Node* cur = _tables[i];
		while (cur)
		{
			Node* next = cur->_next;

			// 头插到新表
			size_t hashi = hs(cur->_kv.first) % newTables.size();
			cur->_next = newTables[hashi];
			newTables[hashi] = cur;

			cur = next;
		}
		_tables[i] = nullptr;
	}

	_tables.swap(newTables);
}
 2.4.2.2 仿函数实现多类型储存

如果想要存储各种类型的数据,我们可以通过传仿函数来实现

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


//特化
template<>
class HashFunc<string>
{
public:
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;
		}
		return hash;
	}
};

template<class K,class V,class Hash=HashFunc<K>>
class Hashtable;
2.4.2.3 代码实现 

代码实现:

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

template<>
class HashFunc<string>
{
public:
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;
		}
		return hash;
	}
};

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

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

	template<class K,class V,class Hash=HashFunc<K>>
	class Hashtable
	{
		typedef HashNode<K, V> Node;
	public:
		Hashtable()
		{
			_tables.resize(10, nullptr);
			_n = 10;
		}
		~Hashtable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;

					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

		Node* Find(const K& key)
		{
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
					cur = cur->_next;
			}
			return nullptr;
		}
		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;
			Hash hs;
			//负载因子到1就扩容
			if (_n == _tables.size())
			{
				vector<Node*> newTables(_tables.size() * 2, nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					// 取出旧表中节点,重新计算挂到新表桶中
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						// 头插到新表
						size_t hashi = hs(cur->_kv.first) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}

				_tables.swap(newTables);
			}
			size_t hashi = hs(kv.first) % _tables.size();
			Node* newNode = new Node(kv);

			//头插
			newNode->_next = _tables[hashi];
			_tables[hashi] = newNode;

			_n++;
			return true;
		}
		bool Erase(const K& key)
		{
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					// 删除
					if (prev)
					{
						prev->_next = cur->_next;
					}
					else
					{
						_tables[hashi] = cur->_next;
					}

					delete cur;

					--_n;
					return true;
				}

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

			return false;
		}

	private:
		vector<Node*> _tables;
		size_t _n;
	};

}

 2.4.3 开散列与闭散列比较

        应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。

        事实上, 由于开地址法必须保持大量的空闲空间以确保搜索效率,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间。

 三、哈希表的模拟实现(加迭代器)

1.unordered_set

unordered_set.h

#pragma once
#include"Hashtable.h"

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

		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
		bool insert(const K& key)
		{
			return _ht.Insert(key);
		}
		bool find(const K& key)
		{
			return _ht.Find(key);
		}
		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}
	private:
		hash_bucket::Hashtable<K, const K, SetKeyOfT, Hash> _ht;
	};


	
}

Hashtable.h 

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

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

template<>
class HashFunc<string>
{
public:
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;
		}
		return hash;
	}
};


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

		HashNode(const T& data)
			:_next(nullptr)
			, _data(data)
		{}
	};
	//前置声明
	template<class K, class T, class KeyOfT, class Hash>
	class Hashtable;

	template<class K,class T,class KeyOfT,class Hash>
	struct __HTIterator
	{
		typedef HashNode<T> Node;
		typedef Hashtable<K, T, KeyOfT, Hash> HT;
		typedef __HTIterator<K, T, KeyOfT, Hash> Self;

		Node* _node;
		HT* _ht;

		__HTIterator(Node* node,HT* ht)
			:_node(node)
			,_ht(ht)
		{}
		T& operator*()
		{
			return _node->_data;
		}
		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				KeyOfT kot;
				Hash hs;
				size_t hashi = hs(kot(_node->_data)) %_ht->_tables.size();
				hashi++;

				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}

					hashi++;
				}
				if (hashi == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
	};

	template<class K, class T, class KeyOfT,class Hash>
	class Hashtable
	{
		typedef HashNode<T> Node;
		//友元 
		template<class K, class T, class KeyOfT, class Hash>
		friend struct __HTIterator;
	public:
		typedef __HTIterator<K, T, KeyOfT, Hash> 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()
		{
			_tables.resize(10, nullptr);
			_n = 10;
		}
		~Hashtable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;

					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

		Node* Find(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}
		bool Insert(const T& data)
		{
			KeyOfT kot;
			if(Find(kot(data)))
				return false;
			Hash hs;
			//负载因子到1就扩容
			if (_n == _tables.size())
			{
				vector<Node*> newTables(_tables.size() * 2, nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					// 取出旧表中节点,重新计算挂到新表桶中
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						// 头插到新表
						size_t hashi = hs(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}

				_tables.swap(newTables);
			}
			size_t hashi = hs(kot(data)) % _tables.size();
			Node* newNode = new Node(data);

			//头插
			newNode->_next = _tables[hashi];
			_tables[hashi] = newNode;

			_n++;
			return true;
		}
		bool Erase(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->kot(cur->_data) == key)
				{
					// 删除
					if (prev)
					{
						prev->_next = cur->_next;
					}
					else
					{
						_tables[hashi] = cur->_next;
					}

					delete cur;

					--_n;
					return true;
				}

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

			return false;
		}

	private:
		vector<Node*> _tables;
		size_t _n;
	};
}

 2.unordered_map.h

#pragma once
#include"Hashtable.h"
namespace L
{
	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 hash_bucket::Hashtable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
		iterator begin()
		{
			return _ht.begin();
		}

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

		bool insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}
	private:
		hash_bucket::Hashtable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
	};
	
}

Hashtable.h

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

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

template<>
class HashFunc<string>
{
public:
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;
		}
		return hash;
	}
};


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

		HashNode(const T& data)
			:_next(nullptr)
			, _data(data)
		{}
	};
	//前置声明
	template<class K, class T, class KeyOfT, class Hash>
	class Hashtable;

	template<class K,class T,class KeyOfT,class Hash>
	struct __HTIterator
	{
		typedef HashNode<T> Node;
		typedef Hashtable<K, T, KeyOfT, Hash> HT;
		typedef __HTIterator<K, T, KeyOfT, Hash> Self;

		Node* _node;
		HT* _ht;

		__HTIterator(Node* node,HT* ht)
			:_node(node)
			,_ht(ht)
		{}
		T& operator*()
		{
			return _node->_data;
		}
		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				KeyOfT kot;
				Hash hs;
				size_t hashi = hs(kot(_node->_data)) %_ht->_tables.size();
				hashi++;

				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}

					hashi++;
				}
				if (hashi == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
	};

	template<class K, class T, class KeyOfT,class Hash>
	class Hashtable
	{
		typedef HashNode<T> Node;
		//友元 
		template<class K, class T, class KeyOfT, class Hash>
		friend struct __HTIterator;
	public:
		typedef __HTIterator<K, T, KeyOfT, Hash> 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()
		{
			_tables.resize(10, nullptr);
			_n = 10;
		}
		~Hashtable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;

					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

		Node* Find(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}
		bool Insert(const T& data)
		{
			KeyOfT kot;
			if(Find(kot(data)))
				return false;
			Hash hs;
			//负载因子到1就扩容
			if (_n == _tables.size())
			{
				vector<Node*> newTables(_tables.size() * 2, nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					// 取出旧表中节点,重新计算挂到新表桶中
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						// 头插到新表
						size_t hashi = hs(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}

				_tables.swap(newTables);
			}
			size_t hashi = hs(kot(data)) % _tables.size();
			Node* newNode = new Node(data);

			//头插
			newNode->_next = _tables[hashi];
			_tables[hashi] = newNode;

			_n++;
			return true;
		}
		bool Erase(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->kot(cur->_data) == key)
				{
					// 删除
					if (prev)
					{
						prev->_next = cur->_next;
					}
					else
					{
						_tables[hashi] = cur->_next;
					}

					delete cur;

					--_n;
					return true;
				}

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

			return false;
		}

	private:
		vector<Node*> _tables;
		size_t _n;
	};
}

3.test.c

#include"My_unordered_map.h"
#include"My_unordered_set.h"
//void test_set1()
//{
//	L::unordered_set<int> us;
//	us.insert(3);
//	us.insert(1);
//	us.insert(5);
//	us.insert(15);
//	us.insert(45);
//	us.insert(7);
//
//	L::unordered_set<int>::iterator it = us.begin();
//	while (it != us.end())
//	{
//		//*it += 100;
//		cout << *it << " ";
//		++it;
//	}
//	cout << endl;
//
//	for (auto e : us)
//	{
//		cout << e << " ";
//	}
//	cout << endl;
//}
//
//void test_map1()
//{
//	L::unordered_map<string, string> dict;
//	dict.insert(make_pair("sort", "1"));
//	dict.insert(make_pair("left", "2"));
//	dict.insert(make_pair("right", "3"));
//
//
//	L::unordered_map<string, string>::iterator it = dict.begin();
//	while (it != dict.end())
//	{
//		cout << (*it).first << " " << (*it).second << endl;
//		//cout << *it.first << " " << *it.second << endl;
//		/*pair<string, string> t=*it;
//		cout << t.first << " " << t.second<<endl;*/
//		++it;
//	}
//	cout << endl;
//	//for (auto& kv : dict)
//	//{
//	//	//kv.first += 'x';
//	//	kv.second += 'y';
//
//	//	cout << kv.first << ":" << kv.second << endl;
//	//}
//}

int main()
{
//	L::test_set1();
//	L::test_map1();
	return 0;
}

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

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

相关文章

利用视频识别做一个土粒实时监测系统

要利用视频识别技术构建一个土粒实时监测系统&#xff0c;我们可以参考以下方案&#xff0c;该方案结合了计算机视觉、深度学习以及相关技术的要点。 一、系统概述 土粒实时监测系统基于先进的视频识别技术&#xff0c;旨在实现对土壤颗粒的实时、准确监测。该系统可以应用于…

Android启动优化之精确测量启动各个阶段的耗时

1. 直观地观察应用启动时长 我们可以通过观察logcat日志查看Android应用启动耗时&#xff0c;过滤关键字"Displayed"&#xff1a; ActivityTaskManager: Displayed com.peter.viewgrouptutorial/.activity.DashboardActivity: 797ms 启动时长(在这个例子中797ms)表示…

水库大坝安全监测险情主要内容

水库常见险情主要包括洪水漫顶、脱坡滑坡、坝体裂缝、 散浸、渗漏、漏洞、陷坑、管涌等&#xff0c;此外风浪冲击、水流冲刷等也会加剧险情的扩大。大坝险情万一抢护不及时&#xff0c;易导致发 生溃坝事故&#xff0c;造成极为严重的灾难性后果。要做到及时有效地 抢护大坝险情…

智慧金融-数据可视化

智慧金融-数据可视化 导入所需的库 import numpy as np import numpy_financial as npf import matplotlib.pyplot as plt from pylab import mpl mpl.rcParams[font.sans-serif][FangSong] mpl.rcParams[axes.unicode_minus]False单图曲线图 r 0.05 # 贷款的年利率 n 30…

28.IP核理论知识(Xilinx)

&#xff08;1&#xff09;ip核是什么&#xff1f; IP&#xff08;Intellectual Property&#xff09;即知识产权&#xff0c;在半导体产业中&#xff0c;将IP核定义为“用于ASIC或FPGA中的预先设计好的电路功能模块”&#xff0c;简而言之&#xff0c;这里的IP即电路功能模块。…

使用 `useAppConfig` :轻松管理应用配置

title: 使用 useAppConfig &#xff1a;轻松管理应用配置 date: 2024/7/11 updated: 2024/7/11 author: cmdragon excerpt: 摘要&#xff1a;本文介绍了Nuxt开发中useAppConfig的使用&#xff0c;它便于访问和管理应用配置&#xff0c;支持动态加载资源、环境配置切换、权限…

MacOS 开发 — Packages 程序 macOS新版本 演示选项卡无法显示

MacOS 开发 — Packages 程序 macOS新版本 演示选项卡无法显示 问题描述 &#xff1a; 之前写过 Packages 的使用以及如何打包macOS程序。最近更新了新的macOS系统&#xff0c;发现Packages的演示选项卡无法显示&#xff0c;我尝试从新安转了Packages 也是没作用&#xff0c;…

医院人员管理系统03_上午:JDBC连接数据库,完成简单的增删改查

文章目录 jdbc连接数据库什么是jdbc完成jdbc的步骤导入jar包写三个类DBConn.java加载驱动类&#xff1a;找到对应的然后写上获取连接关闭连接代码解释 最后写一个main方法调用测试一下运行结果 Students.javaStudentDao.java 运行结果![](https://i-blog.csdnimg.cn/direct/ba2…

bC一体化:推拉联动双向引擎促增长

在数字化浪潮的推动下&#xff0c;快消品行业正面临着前所未有的变革。在过去的几十年里&#xff0c;快速消费品的渠道分销模式发展经历了几个重要的阶段。传统渠道分销模式&#xff0c;从多级分销商的“批发制胜”到“深度分销”的人海战术&#xff0c;在过去的几十年内虽各有…

工业智能网关的功能特点及应用

工业智能网关的功能特点及应用 工业智能网关作为工业互联网架构中的关键组件&#xff0c;扮演着数据桥梁与边缘计算核心的角色&#xff0c;它不仅促进了物理设备与云端平台之间的高效通信&#xff0c;还通过集成的高级功能推动了工业4.0时代的智能化转型。以下是对其功能特点及…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第一篇 嵌入式Linux入门篇-第二十五章 Source Insight 的安装和使用

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

智慧景区建设方案PPT(54页)

智慧景区建设方案摘要 背景简述智慧景区建设旨在通过信息技术改进和创新传统旅游景区&#xff0c;整合旅游资源和产业链&#xff0c;实现精准网络营销&#xff0c;提升旅游品牌价值&#xff0c;改善信息共享和业务协同&#xff0c;提高旅游产业链效率。基础设施包括高速光纤网络…

FunAudioLLM SenseVoice语音转录(ASR)与CosyVoice语音合成(TTS)及语音克隆使用案例;webui可视化页面操作使用

参考: https://fun-audio-llm.github.io/ 1、SenseVoice语音转录 在线体验:https://modelscope.cn/studios/iic/CosyVoice-300M 参考:https://github.com/FunAudioLLM/SenseVoice 下载: pip install -U funasr使用: from funasr import AutoModelmodel_dir = "…

Nature Climate Change | 气候暖化使土壤碳库易受微生物快速降解影响

本文首发于“生态学者”微信公众号&#xff01; 整个北半球土壤有机碳总量的一半富集在北极地区&#xff0c;其原因是气温较低导致微生物对永久冻土带土壤有机碳的分解缓慢&#xff0c;有利于有机碳的积累。但由于人类活动的影响&#xff0c;近几十年来北极出现了明显的升温&am…

第三方软件测试报告内容详解,如何获取专业软件测试报告?

在现代社会中&#xff0c;软件已经渗透到了各个行业的方方面面&#xff0c;而软件的质量也成为了企业成功的重要因素之一。然而&#xff0c;在软件开发过程中&#xff0c;由于人力、物力、时间等各种因素的限制&#xff0c;难免会出现一些问题和bug&#xff0c;而这些问题又可能…

【Linux系统】信号量(初次理解)

五个概念 多个执行流&#xff08;进程&#xff09;&#xff0c;能看到的一份资源&#xff1a;共享资源被保护起来的资源可以叫临界资源&#xff08;同步和互斥&#xff09; --- 用互斥的方式保护共享资源就叫临界资源互斥&#xff1a;任何时刻只能有一个进程在访问共享资源资源…

【待补充】【来可USB-CAN】个人使用总结

文章目录 EVN简介10. 环境搭建20. 环境测试 EVN windows7 LIKE USB-CAN简介 官网介绍、资料下载 https://www.njlike.com/product/CAN-interfaces/USBCAN/01121.html 10. 环境搭建 USB-CAN 驱动 下载地址&#xff1a;https://www.njlike.com/uploads/soft/190625/1_1425474881…

Redis实践经验

优雅的Key结构 Key实践约定&#xff1a; 遵循基本格式&#xff1a;[业务名称]:[数据名]:id例&#xff1a;login:user:10长度步超过44字节&#xff08;版本不同&#xff0c;上限不同&#xff09;不包含特殊字符 优点&#xff1a; 可读性强避免key冲突方便管理节省内存&#x…

Pandas基础03:数据排序与增删

上一节我们介绍了通过按行索引和按列索引找出相关数据的方法。本章节将进一步介绍如何筛选数据&#xff0c;并对数据进行排序、增删的方法。 示例表格和上一节相同。 1.数据筛选 Python中可以通过区域筛选&#xff0c;即获取某几行某几列的方法得到数据。例如&#xff0c;我要…

2024年了还在学pytestday1

1、按照博主的说法&#xff0c;提出疑问&#xff1a;应该在电脑本地终端安装还是在pythoncharm终端安装&#xff1f; ------在pythoncharm终端安装就行 避免老是忘记&#xff0c;还是记下来比较好。 2、在公司安装不成功&#xff0c;换豆瓣源也不行&#xff0c;连接手机热点尝…