【C++】哈希表的实现

news2024/10/6 22:33:41

      • 哈希是什么
      • 理解哈希
      • 哈希所用的容器
      • 计算key值方法
      • 哈希的插入和查找
      • 解决哈希冲突
        • 闭散列也叫开放寻址法
        • 开散列
      • 哈希闭散列实现
          • 闭散列结构
          • 闭散列结构插入
          • 闭散列查找
          • 闭散列删除
      • 哈希开散列实现(链表式)
          • 开散列结构
          • 开散列结构插入
          • 开散列结构查找
          • 开散列结构删除

哈希是什么

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

哈希表的做法其实很简单,就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。

而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位

搜索的效率取决于搜索过程中元素的比较次数,因此顺序结构中查找的时间复杂度为O ( N ) O(N)O(N),平衡树中查找的时间复杂度为树的高度O ( l o g N ) O(logN)O(logN)。

而最理想的搜索方法是,可以不经过任何比较,一次直接从表中得到要搜索的元素,即查找的时间复杂度为O ( 1 ) O(1)O(1)。

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

理解哈希

哈希函数是一将输入(通常是一个大的数据集)映射到固定大小的输出的函数。哈希函数的一个重要特性是,无论输入数据的大小如何,其输出长度都是固定的(如果哈希函数输出长度不固定,将会导致数据分布不均匀或桶的大小不明确,影响哈希表的性能),这使得哈希函数非常有用,因为它可以将大量的数据快速映射到一个较小的范围内。

哈希所用的容器

unordered_map和unordered_set都是C++标准库中的容器,用于存储一组元素。它们的底层实现都是基于哈希表。

unordered_map是一种关联容器,它存储一组键值对(key-value pairs)。每个键都唯一且与一个值相关联。unordered_map使用哈希函数将键映射到特定的存储桶(bucket),并且在桶中存储值。由于使用哈希表实现,unordered_map的插入、查找和删除操作都具有常数平均时间复杂度。

unordered_set是一种集合容器,它存储一组唯一的元素。unordered_set使用哈希函数将元素映射到特定的存储桶,并在桶中存储元素。与unordered_map类似,unordered_set的插入、查找和删除操作也具有常数平均时间复杂度。

unordered_map和unordered_set的区别在于unordered_map存储的是键值对,而unordered_set只存储值。因此,unordered_map可以用来解决需要根据键快速查找对应值的问题,而unordered_set则可以用来快速判断一个值是否存在于集合中。

优点:

  • 查询速度快:由于使用哈希表实现,unordered_map和unordered_set的查询操作具有常数平均时间复杂度。
  • 高效的插入和删除:插入和删除元素的操作也具有常数平均时间复杂度。
  • 灵活性:可以存储不同类型的键和值。

缺点:

  • 内存消耗较大:由于需要维护哈希表,unordered_map和unordered_set通常会消耗比较多的内存空间。
  • 无序性:元素在容器中的存储位置是无序的,无法保证元素的顺序。

计算key值方法

哈希计算存储关键码的方法有以下几种:

  1. 直接定址法(常用):
    取关键字的某个线性函数为哈希地址:H a s h ( K e y ) = A ∗ K e y + B
  • 思路:将关键码直接作为地址来存储,即 H(key) = key。适用于关键码分布比较均匀的情况。
  • 区别:直接定址法不需要计算哈希值,直接使用关键码本身作为地址,因此查找效率很高。
  • 优点:查找操作的平均时间复杂度为O(1),即常数时间复杂度。
  • 缺点:当关键码的分布不均匀时,会导致冲突(多个关键码映射到同一个地址),需要解决冲突的方法。
  1. 数字分析法:
  • 思路:通过对关键码进行分析,选取其中具有代表性的数字作为哈希地址。例如,对身份证号码进行哈希存储时,可以选取其中的年份作为地址。
  • 区别:数字分析法需要对关键码进行分析,并根据分析结果选择适合的数字作为地址,适用于某些特定的数据集。
  • 优点:对于符合特定规律的数据集,数字分析法可以获得较好的哈希效果。
  • 缺点:对于没有明显规律的数据集,数字分析法可能无法获得较好的哈希效果。
  1. 平方取中法:
  • 思路:将关键码平方后,取中间的几位作为哈希地址。例如,对关键码 key 进行平方后,取中间的 m 位作为地址 H(key)。
  • 区别:平方取中法通过平方运算对关键码进行转换,然后取中间的几位作为哈希地址。
  • 优点:相对于直接定址法和数字分析法,平方取中法能够更加均匀地分布关键码,减少冲突的概率。
  • 缺点:平方取中法需要进行额外的平方运算和位数操作,相比直接定址法和数字分析法,会增加一定的计算成本。
  1. 除留余数法(常用):
  • 思路:将关键码除以某个不大于哈希表大小的数,再取余数作为哈希地址。即 H(key) = key%p,其中 p 是一个不大于哈希表大小的素数。
  • 区别:除留余数法通过除法和取余操作来计算哈希地址。
  • 优点:除留余数法相对简单,计算速度快。
  • 缺点:如果选取的素数 p 与关键码的特征相关,则可能导致冲突较多。

哈希的插入和查找

数据集合{1,7,6,4,5,9};

哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小

在这里插入图片描述

当我们在再次插入一个数值位11的时候,这时发现,如果我们已同样的方法进行插入的时候,就会与 1 下标位置的元素起到冲突。这时我们就要解决冲突问题,冲突问题在下方解决

  • 插入元素
    根据待插入元的关键码(在下边将介绍),以此函数计算出该元素的存储位置并按此位置进行存放
//插入两种方法
		//bool Insert(const T& data)
		pair<iterator,bool> Insert(const T& data)
		{
			KeyOfT kot;//将不同对象进行提取
			iterator it = Find(kot(data));
			if (it != end())
			{
				return make_pair(it,false);
			}
			Hash hash;
			//进行扩容
			if (_n == _tables.size())
			{
				//不调用自定义析构的方法
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newtables(newsize, nullptr);
				//for (Node*& cur : _tables)
				for(auto& cur : _tables)
				{
					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.swap(newtables);
			}
			size_t hashi = hash(kot(data)) % _tables.size();
			//头插
			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)
		{
			if (_tables.size() == 0)
			{
				return end();
			}
			Hash hash;//根据类型不同来计算他的整体key值
			KeyOfT kot;//将不同对象进行提取查找对应值
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur,this);
				}
				else
				{
					cur = cur->_next;
				}
			}
			return end();
		}

解决哈希冲突

数据集合{1,7,6,4,5,9};

哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小

在这里插入图片描述

当我们在再次插入一个数值位11的时候,这时发现,如果我们已同样的方法进行插入的时候,就会与 1 下标位置的元素起到冲突。这时我们就要解决冲突问题

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

闭散列也叫开放寻址法

开放寻址法(Open Addressing):在每个哈希桶中直接存储键值对,当发生冲突时,通过一定的探索规则找到下一个可用的桶。常见的探索规则有线性探测、二次探测

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

开放寻址法不需要额外的内存来存储指针,且具有较好的缓存友好性,但当负载因子较高时,可能会导致连续冲突的概率增加,进而影响到性能。
线性探测

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

二次探测
线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为:

Hi=(H0+i ^2)%m ( i = 1 , 2 , 3 , . . . )
H0:通过哈希函数对元素的关键码进行计算得到的位置。
H i:冲突元素通过二次探测后得到的存放位置。
m:表的大小。
在这里插入图片描述

采用二次探测为产生哈希冲突的数据寻找下一个位置,相比线性探测而言,采用二次探测的哈希表中元素的分布会相对稀疏一些,不容易导致数据堆积。

和线性探测一样,采用二次探测也需要关注哈希表的负载因子,例如,采用二次探测将上述数据插入到表长为20的哈希表,产生冲突的次数也会有所减少:
在这里插入图片描述

开散列

在每个哈希桶中使用链表或其他数据结构存储冲突的键值对。当发生冲突时,新的键值对可以简单地添加到链表的末尾。这种方法简单易行,适用于频繁发生冲突的情况。链地址法的缺点是需要额外的内存来存储链表的指针,同时在处理大量冲突时,链表的遍历效率可能较低。

例如,我们用除留余数法将序列{1, 6, 15, 60, 88, 7, 40, 5, 10}插入到表长为10的哈希表中,当发生哈希冲突时我们采用开散列的形式,将哈希地址相同的元素都链接到同一个哈希桶下,插入过程如下:
在这里插入图片描述
闭散列解决哈希冲突,采用的是一种报复的方式,“我的位置被占用了我就去占用其他位置”。而开散列解决哈希冲突,采用的是一种乐观的方式,“虽然我的位置被占用了,但是没关系,我可以‘挂’在这个位置下面”。

与闭散列不同的是,这种将相同哈希地址的元素通过单链表链接起来,然后将链表的头结点存储在哈希表中的方式,不会影响与自己哈希地址不同的元素的增删查改的效率,因此开散列的负载因子相比闭散列而言,可以稍微大一点。

  • 闭散列的开放定址法,负载因子不能超过1,一般建议控制在[0.0, 0.7]之间。
  • 开散列的哈希桶,负载因子可以超过1,一般建议控制在[0.0, 1.0]之间。

在实际中,开散列的哈希桶结构比闭散列更实用,主要原因有两点

  • 哈希桶的负载因子可以更大,空间利用率高。
  • 哈希桶在极端情况下还有可用的解决方案。

哈希桶的极端情况就是,所有元素全部产生冲突,最终都放到了同一个哈希桶中,此时该哈希表增删查改的效率就退化成了O ( N )

在这里插入图片描述

哈希闭散列实现

闭散列结构

我们用枚举来表示它当前的状态

//枚举:标识每个位置的状态
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 HashTable
{
public:
	//...
private:
	vector<HashData<K, V>> _table; //哈希表
	size_t _n = 0; //哈希表中的有效元素个数
};
闭散列结构插入

当进行插入的时候,我们需要注意它的扩容问题,在这里我们的负载因子一般控制在 0 ~ 0.7 之间,如果超过了这个范围,就需要扩容。
当扩容时,我们不是原地扩,而是先设置一个新的容器,然后放大原先的两倍,再将所有数据挪过去,最后交换数组就可以了

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

			// 负载因子超过0.7就扩容
			//if ((double)_n / (double)_tables.size() >= 0.7)
			if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
			{
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				HashTable<K, V> newht;
				newht._tables.resize(newsize);

				// 遍历旧表,重新映射到新表
				for (auto& data : _tables)
				{
					if (data._state == EXIST)
					{
						newht.Insert(data._kv);
					}
				}

				_tables.swap(newht._tables);
			}

			size_t hashi = kv.first % _tables.size();

			// 线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state == EXIST)
			{
				index = hashi + i;
				index %= _tables.size();
				++i;
			}

			_tables[index]._kv = kv;
			_tables[index]._state = EXIST;
			_n++;

			return true;
		}
闭散列查找

在进行查找时,首先需要用哈希函数算出对应key值,然后剩下我们的工作只是遍历元素,查找对应的元素值。
只需要看当前状态是否为存在或者删除,当遇到空时,说明所找元素不存在

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

			size_t hashi = key % _tables.size();

			// 线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state != EMPTY)
			{
				if (_tables[index]._state == EXIST
					&& _tables[index]._kv.first == key)
				{
					return &_tables[index];
				}

				index = hashi + i;
				index %= _tables.size();
				++i;

				// 如果已经查找一圈,那么说明全是存在+删除
				if (index == hashi)
				{
					break;
				}
			}

			return nullptr;
		}
闭散列删除

删除哈希表中的元素非常简单,我们只需要进行伪删除即可,也就是将待删除元素所在位置的状态设置为DELETE。

在哈希表中删除数据的步骤如下:

  • 查看哈希表中是否存在该键值的键值对,若不存在则删除失败。
  • 若存在,则将该键值对所在位置的状态改为DELETE即可。
  • 哈希表中的有效元素个数减一。

注意: 虽然删除元素时没有将该位置的数据清0,只是将该元素所在状态设为了DELETE,但是并不会造成空间的浪费,因为我们在插入数据时是可以将数据插入到状态为DELETE的位置的,此时插入的数据就会把该数据覆盖。

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

哈希开散列实现(链表式)

开散列结构

在开散列的哈希表中,哈希表的每个位置存储的实际上是某个单链表的头结点,即每个哈希桶中存储的数据实际上是一个结点类型,该结点类型除了存储所给数据之外,还需要存储一个结点指针用于指向下一个结点。

//每个哈希桶中存储数据的结构
template<class K, class V>
struct HashNode
{
	pair<K, V> _kv;
	HashNode<K, V>* _next;

	//构造函数
	HashNode(const pair<K, V>& kv)
		:_kv(kv)
		, _next(nullptr)
	{}
};

与闭散列的哈希表不同的是,在实现开散列的哈希表时,我们不用为哈希表中的每个位置设置一个状态字段,因为在开散列的哈希表中,我们将哈希地址相同的元素都放到了同一个哈希桶中,并不需要经过探测寻找所谓的“下一个位置”。

哈希表的开散列实现方式,在插入数据时也需要根据负载因子判断是否需要增容,所以我们也应该时刻存储整个哈希表中的有效元素个数,当负载因子过大时就应该进行哈希表的增容。

//哈希表
template<class K, class V>
class HashTable
{
public:
	//...
private:
	//typedef HashNode<K, V> Node;
	vector<Node*> _table; //哈希表
	size_t _n = 0; //哈希表中的有效元素个数
};
开散列结构插入
  • 若哈希表的负载因子已经等于1了,则先创建一个新的哈希表,该哈希表的大小为原哈希表的两倍,之后遍历原哈希表,将原哈希表中的数据插入到新哈希表,最后将原哈希表与新哈希表交换即可。
  • 重点: 在将原哈希表的数据插入到新哈希表的过程中,不要通过复用插入函数将原哈希表中的数据插入到新哈希表,因为在这个过程中我们需要创建相同数据的结点插入到新哈希表,在插入完毕后还需要将原哈希表中的结点进行释放,多此一举。

实际上,我们只需要遍历原哈希表的每个哈希桶,通过哈希函数将每个哈希桶中的结点重新找到对应位置插入到新哈希表即可,不用进行结点的创建与释放。
在这里插入图片描述

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

			Hash hash;

			// 负载因因子==1时扩容
			if (_n == _tables.size())
			{
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newtables(newsize, nullptr);
				//for (Node*& cur : _tables)
				for (auto& cur : _tables)
				{
					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.swap(newtables);
			}

			size_t hashi = hash(kv.first) % _tables.size();
			// 头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;

			++_n;
			return true;
		}
开散列结构查找

在哈希表中查找数据的步骤如下:

  • 先判断哈希表的大小是否为0,若为0则查找失败。
  • 通过哈希函数计算出对应的哈希地址。
  • 通过哈希地址找到对应的哈希桶中的单链表,遍历单链表进行查找即可。
		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
				return nullptr;

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

				cur = cur->_next;
			}

			return nullptr;
		}
开散列结构删除

在哈希表中删除数据的步骤如下:

  • 通过哈希函数计算出对应的哈希桶编号。
  • 遍历对应的哈希桶,寻找待删除结点。
  • 若找到了待删除结点,则将该结点从单链表中移除并释放。
  • 删除结点后,将哈希表中的有效元素个数减一。
		bool Erase(const K& key)
		{
			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;

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

			return false;
		}

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

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

相关文章

事件循环,还在微任务宏任务?过时了,快看看新版浏览器事件循环event loop(message loop)

浏览器的进程模型 进程&#xff1a;程序运⾏需要有它⾃⼰专属的内存空间&#xff0c;可以把这块内存空间简单的理解为进程。每个应⽤⾄少有⼀个进程&#xff0c;进程之间相互独⽴&#xff0c;即使要通信&#xff0c;也需要双⽅同意。 线程&#xff1a;有了进程&#xff0c;就…

【机器学习习题】估计一个模型在未见过的数据上的性能

您提到的不等式是统计学中的泛化误差界&#xff08;generalization error bound&#xff09;&#xff0c;它用于估计一个模型在未见过的数据上的性能。这个不等式是由Hoeffding不等式和Union Bound组合而成的。在这个不等式中&#xff0c;我们有以下符号&#xff1a; - P[|E_i…

Linux安装JDK1.8并配置环境变量

Linux安装JDK并配置环境变量Linux安装JDK并配置环境变量Linux安装JDK并配置环境变量 一、查询已有JAVA环境版本信息 java -version 二、下载Oracle JDK安装包 https://www.oracle.com/java/technologies/downloads/archive/ 三、安装 配置JDK 以下方式适用于安装各版本JDK&…

第一届电子纸产业创新应用论坛

自从2004年索尼推出全球首款电子纸的应用产品——电纸书阅读器以来&#xff0c;20年间&#xff0c;在各个领域&#xff0c;涌现出众多优秀的电子纸的创新应用&#xff0c;如电子价签、手写本、手机、笔记本、显示器、电子公交站牌等&#xff0c;形成电子纸产业持续蓬勃发展的强…

Java“牵手”义乌购商品详情数据,义乌购商品详情接口,义乌购API接口申请指南

义乌购隶属浙江义乌购电子商务有限公司旗下网站。该平台定位为依托实体市场&#xff0c;服务实体市场&#xff0c;以诚信为根本&#xff0c;将7万网上商铺与实体商铺一一对应绑定&#xff0c;为采购商和经营户提供可控、可信、可溯源的交易保障。 义乌购平台现有商铺商品、市场…

Jmeter系列-控制器Controllers的介绍(8)

Controllers 简介 JMeter是一款功能强大的性能测试工具&#xff0c;而控制器是JMeter中非常重要的一个组件。控制器用于控制测试计划的执行流程&#xff0c;可以根据需求来控制线程的启动、停止、循环等操作。 Jmeter有两种类型的控制器&#xff1a;Samplers&#xff08;取样…

Tomcat架构设计及组件详解

继Tomcat配置详解&#xff08;Tomcat配置server.xml详解&#xff09;Tomcat配置详解&#xff08;Tomcat配置server.xml详解&#xff09;_tomcat xml配置https://blog.csdn.net/imwucx/article/details/132166738文章之后&#xff0c;深入的学习tomcat相关知识&#xff0c;对Tom…

电子会计档案怎么管?电子凭证怎么入账归档?泛微文书定帮您解决

随着数字经济的发展&#xff0c;会计档案领域关键政策不断推进&#xff0c;逐渐向数字化发展。 2015年12月&#xff0c;财政部、国家档案局令第79号《会计档案管理办法》&#xff1a;确定电子会计档案的概念和管理要求&#xff0c;明确会计档案可仅以电子形式归档保存。 2020…

3D医学影像PACS系统源代码

一、系统概述 3D医学影像PACS系统&#xff0c;它集影像存储服务器、影像诊断工作站及RIS报告系统于一身,主要有图像处理模块、影像数据管理模块、RIS报告模块、光盘存档模块、DICOM通讯模块、胶片打印输出等模块组成&#xff0c; 具有完善的影像数据库管理功能&#xff0c;强大…

混合项目管理:如何成功地整合传统与敏捷方法?

若你尚未涉足于混合项目管理这一领域&#xff0c;且暗自以为其难以捉摸、令人费解&#xff0c;我们向你承诺&#xff0c;实则并非如此。 在深入探究混合项目管理这一主题之前&#xff0c;我们先澄清几项基础但关键的专业术语。在当今这个商业竞争愈发激烈的环境中&#xff0c;项…

4G网络广播模块 4G网络广播开发模块

SV-6209 4G网络广播模块 4G网络广播开发模块 一、描述 SV-6209网络音频模块是一款带2*40W功放输出的4G广播音频模块&#xff0c;采用高性能ARM处理器及专业Codec&#xff0c;能接收4G广播音频数据流&#xff0c;转换成音频模拟信号输出。带有一路line in输入&#xff0c;通过外…

jmeter生成html格式接口自动化测试报告

jmeter自带执行结果查看的插件&#xff0c;但是需要在jmeter工具中才能查看&#xff0c;如果要向领导提交测试结果&#xff0c;不够方便直观。 笔者刚做了这方面的尝试&#xff0c;总结出来分享给大家。 这里需要用到ant来执行测试用例并生成HTML格式测试报告。 一、ant下载安…

linux安装sqoop

一 解压安装包 这里提供了网盘资源 链接: https://pan.baidu.com/s/1QkFqVnlvuOJ_aB2bjn-OKg?pwducsy 提取码: ucsy 这里有两个压缩包&#xff0c;sqoop-1.4.7.tar.gz是 sqoop 的安装包&#xff0c;另一个是我们需要里面的一些 jar 包 解压 安装包 tar -zxf ./sqoop-1.4.7.t…

【自动驾驶】PETR 环境安装与测试

1.环境安装 该工程依赖MMCV&#xff0c; MMDetection&#xff0c; MMDetection3d&#xff0c;MMSegmentation Install MMCV pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/{cu_version}/{torch_version}/index.htmlexamples&#xff1a; pip install…

VR全景展示的功能有哪些?你了解多少?

VR全景展示作为一种全新的视觉体验技术&#xff0c;能够为人们带来强烈的视觉效果以及沉浸式的观感&#xff0c;在旅游、房地产、车展、博物馆等都有着十分广泛的应用。这种富媒体技术&#xff0c;具有很好的交互性和沉浸感&#xff0c;能够带给大家更好的体验&#xff0c;那么…

为什么选择事件驱动的微服务架构?

在当今动态的业务环境中&#xff0c;开发人员面临越来越大的压力&#xff0c;需要提供快速、可靠、可扩展的解决方案&#xff0c;以满足不断变化的业务需求&#xff0c;而事实证明&#xff0c;传统应用程序是实现这些目标的障碍。微服务提供了一种易于理解且有前途的替代方案&a…

2023自动化测试需知的4项测试工具!

一般来说学自动化会建议大家先学selenium&#xff0c;因为最早的时候&#xff0c;自动化就代表selenium&#xff0c;进入测试行业就开始做接口测试&#xff0c;而且现在基本每个公司都需要接口测试。今天就和大家聊一下接口测试的工具。 一、Robot Framework 机器人框架。之所…

AI Studio星河社区生产力实践:基于文心一言快速搭建知识库问答

还在寻找基于文心一言搭建本地知识库问答的方案吗&#xff1f;AI Studio星河社区带你实战演练&#xff08;支持私有化部署&#xff09;&#xff01; 相信对于大语言模型&#xff08;LLM&#xff09;有所涉猎的朋友&#xff0c;对于“老网红”知识库问答不会陌生。自从大模型爆火…

亚马逊封买家账号的原因有哪些

亚马逊可能封锁买家账号的原因有多种&#xff0c;主要是出于保护市场和维护平台秩序的考虑。以下是一些可能导致亚马逊封锁买家账号的常见原因&#xff1a; 1、涉及违规行为&#xff1a;如果买家违反了亚马逊的使用政策&#xff0c;如发表虚假评价、滥用退货政策、欺诈或盗窃等…

GitHub 执行定时任务之巴奴会员签到

经常忘记去点那么一下&#xff0c;人到中年记忆力衰退的厉害。所以… 前言 巴奴算是签到算是比较简单的那种&#xff0c;没有太多的限制&#xff0c;可能获取积分的大多是人都还是要去门店的吧&#xff0c;也没必要做太多的限制。 打开签到每日提醒后&#xff0c;自动推送的地…