【哈希】闭散列的线性探测和开散列的哈希桶解决哈希冲突(C++两种方法模拟实现哈希表)(2)

news2024/10/5 16:19:20
图片名称
🎉博主首页: 有趣的中国人

🎉专栏首页: C++进阶

🎉其它专栏: C++初阶 | Linux | 初阶数据结构

在这里插入图片描述

小伙伴们大家好,本片文章将会讲解 哈希函数与哈希 之 哈希桶解决哈希冲突 的相关内容。

如果看到最后您觉得这篇文章写得不错,有所收获,麻烦点赞👍、收藏🌟、留下评论📝。您的支持是我最大的动力,让我们一起努力,共同成长!

🎉系列文章: 1. 闭散列的线性探测实现哈希表

文章目录

  • `0. 前言`
  • `1. 何为开散列`
    • ==<font color = blue><b>🎧1.1 开散列的概念🎧==
    • ==<font color = blue><b>🎧1.2 开散列哈希表图示🎧==
  • `2. 开散列哈希表的实现`
    • ==<font color = blue><b>🎧2.1 开散列哈希表的结构🎧==
    • ==<font color = blue><b>🎧2.2 哈希桶插入Insert🎧==
    • ==<font color = blue><b>🎧2.3 哈希桶查找Find🎧==
    • ==<font color = blue><b>🎧2.4 哈希桶删除Erase🎧==
  • `3. 字符串哈希与仿函数`
  • `4.哈希桶实现哈希表完整代码`



0. 前言


在上一篇文章中我们详细描述了如何用 开放寻址法(闭散列)的线性探测 的方法来实现哈希表。此篇文章我们将用 开散列的哈希桶 来实现哈希表。




1. 何为开散列


🎧1.1 开散列的概念🎧


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

🎧1.2 开散列哈希表图示🎧


在这里插入图片描述

插入元素44

在这里插入图片描述

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




2. 开散列哈希表的实现


🎧2.1 开散列哈希表的结构🎧


很明显,这个哈希表中存储了一个指针数组,我们可以用vector来实现,数组中的每个位置存储了一个节点类型的指针每个节点相当于是链表的一个节点,即:节点中有一个链表类型的指针,还有一个存放值的位置。

哈希节点和哈希表结构代码:

// 定义节点类型
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 HashFunc = HashFunc<K>>
class HashTable
{
public:
	typedef HashNode<K, V> Node;

	HashTable(size_t n = 10)
	{
		_tables.resize(n);
	}
private:
	// 指针数组
	vector<Node*> _tables;
	// 存储的元素个数
	size_t _n = 0;
};

🎧2.2 哈希桶插入Insert🎧


插入元素的思路:

  1. 利用 哈希函数 计算出 要插入的值应该存放在哪个桶里面
  2. 之后在对应的桶中进行链表的头插:
    • 首先new一个哈希表的节点newnode
    • newnode->_next= _tables[i]
    • 再让newnode当作头:_tables[i] = newnode
  3. ++_n

关于哈希桶的扩容:

在线性探测中,当负载因子 load_factor 0.75 0.75 0.75 左右的时候就要进行扩容,但是在哈希桶中,我们可以适当让负载因子大一点,在STL库中,哈希桶的扩容是当负载因子等于 1 1 1 的时候进行扩容,即: n = = t a b l e . s i z e ( ) n == table.size() n==table.size()

注意:哈希桶中的负载因子是可以大于1的,因为一个桶中可能存储的不止一个值。


扩容思路1:

我们可以继续利用在线性探测的扩容思路:

  1. 新定义一个HashTable的对象newht,表的容量还是两倍;
  2. 遍历原始的HashTable中的vector _tables
    • 如果_tables[i]不为空,那么就调用newht.Insert()函数;
      • 定义一个节点类型的指针Node* cur = _tables[i]
      • 调用newht.Insert(cur->_kv);
      • 再让cur = cur->_next
    • 如果_tables[i]为空,就让i++
  3. 直到 i == _tables.size(),则newht插入完成;
  4. 最后两个_tables进行交换:_tables.swap(newht._tables)

但是这样扩容虽然可以,但是会很麻烦,因为:

  1. 由于每个哈希节点是new出来的,因此不能直接使用vector的析构函数,要自己写一个析构函数,不然会有内存泄漏;
  2. 每次调用newht.Insert()的时候都会重新new一个节点,原始的节点都会被释放,因此这样操作就会很麻烦编译器。

扩容代码(version1):

// 手动进行析构
~HashTable()
{
	for (size_t i = 0; i < _tables.size(); ++i)
	{
		Node* cur = _tables[i];
		Node* next = nullptr;
		while (cur)
		{
			next = cur->_next;
			delete cur;
			cur = next;
		}
	}
}

// 扩容代码
if (_n == _tables.size())
{
	// 方法1:新定义一个对象
	size_t newsize = 2 * _tables.size();
	HashTable<K, V> newht(newsize);
	for (size_t i = 0; i < _tables.size(); i++)
	{
		Node* cur = _tables[i];
		Node* next = nullptr;
		while (cur)
		{
			next = cur->_next;
			newht.Insert(cur->_kv);
			cur = next;
		}
	}
	_tables.swap(newht._tables);
}

扩容思路2:

  1. 定义一个新表vector newtables,表的容量还是两倍;
  2. 遍历旧表,如果当前位置不为空,在新表中进行插入,思路如下:
    • 定义一个哈希节点指针Node* cur = _tables[i]
    • 通过cur->_kv.first 和 哈希函数 计算出 应该插入到新表的哪个桶中(hashi);
    • 由于插入之后会找不到下一个节点的位置,所以应该再定义一个Node* next = cur->next
    • 在新表中头插cur,还是同样的思路:
      • cur->_next = newtables[hashi]cur的下一个指向原始的头节点);
      • 接着让 newtables[hashi] = cur(让cur当头);
      • 插入完成让cur = next
      • 直到cur == nullptr,说明此桶中的节点都在新表中插入完成;
    • 让旧表中的_tables[i] = nullptr; (这部也可以不做,因为表不会调用析构函数,但是最好还是置空一下)
  3. 如果当前位置为空,则i++
  4. 直到 i == _tables.size(),说明此表的所有元素在新表中插入完成;
  5. 最后两表进行交换:_tables.swap(newtables)

扩容代码(version2):

if (_n == _tables.size())
{
	vector<Node*> newtable;
	// 两倍的旧表容量
	size_t newsize = 2 * _tables.size();
	newtable.resize(newsize);
	for (size_t i = 0; i < _tables.size(); ++i)
	{
		Node* cur = _tables[i];
		Node* next = nullptr;
		while (cur)
		{
			// 记录下一个位置
			next = cur->_next;
			// 计算在新表中的位置
			size_t hashi = cur->_kv.first % newtable.size();
			// cur的下一个位置指向原来的头
			cur->_next = newtable[hashi];
			// cur当头
			newtable[hashi] = cur;
			// 更新cur的位置
			cur = next;
		}
		// 旧表置空
		_tables[i] = nullptr;
	}
	_tables.swap(newtable);
}

完整的插入逻辑代码:

bool Insert(const pair<K, V>& kv)
{
	// 这边就是上一篇文章的仿函数
	HashFunc hf;
	// 查找思路待会实现
	if (Find(kv.first))
	{
		return false;
	}
	// 判断负载因子扩容
	// 负载因子为1扩容
	if (_n == _tables.size())
	{
		// 方法1:新定义一个对象
		/*size_t newsize = 2 * _tables.size();
		HashTable<K, V> newht(newsize);
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			Node* next = nullptr;
			while (cur)
			{
				next = cur->_next;
				newht.Insert(cur->_kv);
				cur = next;
			}
		}
		_tables.swap(newht._tables);*/

		// 方法2:新定义一个表
		vector<Node*> newtable;
		size_t newsize = 2 * _tables.size();
		newtable.resize(newsize);
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			Node* cur = _tables[i];
			Node* next = nullptr;
			while (cur)
			{
				next = cur->_next;
				size_t hashi = hf(cur->_kv.first) % newtable.size();
				cur->_next = newtable[hashi];
				newtable[hashi] = cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
		_tables.swap(newtable);
	}
	size_t hashi = hf(kv.first) % _tables.size();
	Node* newnode = new Node(kv);
	// 头插
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	++_n;
	return true;
}

🎧2.3 哈希桶查找Find🎧


查找实现思路如下:

  1. 根据 key 和 哈希函数计算出对应的桶(hashi);
  2. 在此桶中进行寻找:
    • 定义一个哈希节点类型的指针Node* cur = _tables[hashi]
    • 一直向后寻找,直到找到或者 cur == nullptr(没有此元素)。
    • 找到返回此位置的指针,找不到返回空。

完整的查找逻辑代码:

Node* Find(const K& key)
{
	HashFunc hf;
	// 根据 `key` 和 哈希函数计算出对应的桶(`hashi`)
	size_t hashi = hf(key) % _tables.size();

	Node* cur = _tables[hashi];
	while (cur)
	{
		if (cur->_kv.first == key)
		{
			return cur;
		}
		else
		{
			cur = cur->_next;
		}
	}
	return nullptr;
}

🎧2.4 哈希桶删除Erase🎧


删除实现思路如下:

  1. 根据 key 和 哈希函数计算出对应的桶(hashi);
  2. 在此桶中进行查找,这里要考虑要删除的节点的前一个节点是否为空;
  3. 如果前一个节点不为空,直接让prev->_next = cur->_next
  4. 如果前一个节点为空,就让 _tables[i] = cur->_next
  5. delete cur; cur = nullptr;
  6. 如果一直到 cur == nullptr 最后都未曾找到,则返回false
  7. 最后 --_n

完整的删除逻辑代码:

bool Erase(const K& key)
{
	HashFunc hf;
	//  根据 `key` 和 哈希函数计算出对应的桶(`hashi`);
	size_t hashi = hf(key) % _tables.size();
	Node* cur = _tables[hashi];
	Node* prev = nullptr;
	while (cur)
	{
		if (cur->_kv.first == key)
		{
			// 如果前一个节点为空,就让 `_tables[i] = cur->_next`;
			if (prev == nullptr)
			{
				_tables[hashi] = cur->_next;
			}
			// 如果前一个节点为空,就让 `_tables[i] = cur->_next`
			else
			{
				prev->_next = cur->_next;
			}
			delete cur;
			return true;
		}
		else
		{
			prev = cur;
			cur = cur->_next;
		}
	}
	return false;
}		



3. 字符串哈希与仿函数


字符串哈希我们上一篇文章讲过::

  1. 当我们插入数字的类型,例如:double、float、int、 char、unsigned用的是一种类型的哈希函数
  2. 当我们插入字符串类型string的时候用的是另一种类型的哈希函数
  3. 🔎遇到这种情况的时候我们一般用仿函数来解决问题!!!🔍

因此我们要加一个仿函数的模板参数:class HashFunc

对于数字类型的仿函数代码:

template<class K>
struct Hash
{
	size_t operator()(const K& key)
	{
		// 强转即可
		return (size_t)key;
	}
};

对于string类型的仿函数代码:

这里先写一下,待会再细谈:

struct StringFunc
{
	size_t operator()(const string& str)
	{
		size_t ret = 0;
		for (auto& e : str)
		{
			ret *= 131;
			ret += e;
		}
		return ret;
	}
};

由于string类型的哈希我们经常用,因此可以用模板的特化,并将此模板用缺省参数的形式传递,这样我们就不用在每次用的时候传入仿函数了。

template<class K>
struct Hash
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

template<>
struct Hash<string>
{
	size_t operator()(const string& str)
	{
		size_t ret = 0;
		for (auto& e : str)
		{
			ret *= 131;
			ret += e;
		}
		return ret;
	}
};




4.哈希桶实现哈希表完整代码



🎧有需要的小伙伴自取哈,博主已经检测过了,无bug🎧

🎨博主gitee链接: Jason-of-carriben 哈希桶实现哈希表完整代码

在这里插入图片描述

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

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

template<>
struct HashFunc<string>
{
	size_t operator()(const string& str)
	{
		size_t hash_value = 0;
		for (auto& e : str)
		{
			hash_value = hash_value * 131 + e;
		}
		return hash_value;
	}
};


namespace hash_bucket
{
	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 HashFunc = HashFunc<K>>
	class HashTable
	{
	public:
		typedef HashNode<K, V> Node;

		HashTable(size_t n = 10)
		{
			_tables.resize(n);
		}

		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				Node* cur = _tables[i];
				Node* next = nullptr;
				while (cur)
				{
					next = cur->_next;
					delete cur;
					cur = next;
				}
			}
		}

		bool Insert(const pair<K, V>& kv)
		{
			HashFunc hf;
			if (Find(kv.first))
			{
				return false;
			}
			// 判断负载因子扩容
			// 负载因子为1扩容
			if (_n == _tables.size())
			{
				// 方法1:新定义一个对象
				/*size_t newsize = 2 * _tables.size();
				HashTable<K, V> newht(newsize);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					Node* next = nullptr;
					while (cur)
					{
						next = cur->_next;
						newht.Insert(cur->_kv);
						cur = next;
					}
				}
				_tables.swap(newht._tables);*/

				// 方法2:新定义一个表
				vector<Node*> newtable;
				size_t newsize = 2 * _tables.size();
				newtable.resize(newsize);
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					Node* next = nullptr;
					while (cur)
					{
						next = cur->_next;
						size_t hashi = hf(cur->_kv.first) % newtable.size();
						cur->_next = newtable[hashi];
						newtable[hashi] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newtable);
			}
			size_t hashi = hf(kv.first) % _tables.size();
			Node* newnode = new Node(kv);
			// 头插
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			return true;
		}

		Node* Find(const K& key)
		{
			HashFunc hf;

			size_t hashi = hf(key) % _tables.size();

			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				else
				{
					cur = cur->_next;
				}
			}
			return nullptr;

			/*for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				Node* next = nullptr;
				while (cur)
				{
					next = cur->_next;
					if (cur->_kv.first == key)
					{
						return cur;
					}
					else
					{
						cur = next;
					}
				}
			}
			return nullptr;*/
		}

		bool Erase(const K& key)
		{
			HashFunc hf;

			size_t hashi = hf(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			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;
			//for (size_t i = 0; i < _tables.size(); ++i)
			//{
			//	Node* prev = nullptr;
			//	Node* cur = _tables[i];
			//	//Node* next = nullptr;
			//	while (cur)
			//	{
			//		if (cur->_kv.first == key)
			//		{
			//			if (prev == nullptr)
			//			{
			//				_tables[i] = cur->_next;
			//			}
			//			else
			//			{
			//				prev->_next = cur->_next;
			//			}
			//			delete cur;
			//			return true;
			//		}
			//		else
			//		{
			//			prev = cur;
			//			cur = cur->_next;
			//		}
			//	}
			//}
			//return false;
		}



	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};
	void HashTest1()
	{
		int a[] = { 10001,11,55,24,19,12,31,93,67,26 };
		HashTable<int, int> ht;
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Insert(make_pair(32, 32));
		//ht.Insert(make_pair(32, 32));
		ht.Erase(31);
		ht.Erase(10001);

	}

	void HashTest2()
	{
		string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
	"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
		HashTable<string, string> countMap;
		for (auto& e : arr)
		{
			countMap.Insert(make_pair(e, e));
		}
	}
}

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

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

相关文章

SpringSecurity6从入门到实战之SpringSecurity快速入门

SpringSecurity6从入门到实战之SpringSecurity快速入门 环境准备 依赖版本号springsecurity6.0.8springboot3.0.12JDK17 这里尽量与我依赖一致,免得在学习过程中出现位置的bug等 创建工程 这里直接选择springboot初始化快速搭建工程,导入对应的jdk17进行创建 直接勾选一个web…

20240529代码沉思--------聊聊清单革命

以下内容取自百度&#xff1a; 清单革命 清单革命是一场观念革命&#xff0c;旨在通过列出清晰、明确的清单来避免犯错和提高效率。以下是关于清单革命的一些核心观点和原则&#xff1a; 核心观点&#xff1a; 人类的错误主要分为两类&#xff1a;“无知之错”和“无能之错…

[ C++ ] 类和对象( 下 )

初始化列表 初始化列表&#xff1a;以一个冒号开始&#xff0c;接着是一个以逗号分隔的数据成员列表&#xff0c;每个"成员变量"后面跟 一个放在括号中的初始值或表达式。 class Date { public: Date(int year, int month, int day): _year(year), _month(month), _d…

网络编程基础(四)

目录 前言 二、多点通信 2.1 单播 2.2 广播 2.2.1 广播得发送端实现--》类似与UDP的客户端 2.3 组播 2.3.1 组播发送端流程--》类似于UDP的客户端流程 2.3.2 组播的接收端流程---》类似于UDP的服务器端流程 前言 多点通信 一、套接字选项得获取和设置 int getsockopt(int…

感知觉训练:解锁独立生活的钥匙

在日新月异的科技时代&#xff0c;一款名为“蝙蝠避障”的辅助软件以其独到之处&#xff0c;为盲人朋友的日常生活平添了诸多便利&#xff0c;不仅实现了实时避障&#xff0c;还通过拍照识别功能扩展了信息获取的边界。然而&#xff0c;科技辅助之外&#xff0c;提升盲人朋友的…

《征服数据结构》目录

我们知道要想学好算法&#xff0c;必须熟练掌握数据结构&#xff0c;数据结构常见的有 8 大类&#xff0c;分别是数组&#xff0c;链表&#xff0c;队列&#xff0c;栈&#xff0c;散列表&#xff0c;树&#xff0c;堆&#xff0c;图。但如果细分的话就比较多了&#xff0c;比如…

「西安邀请媒体参会」媒体宣发专访报道

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 一、媒体邀约目标 为提升活动的知名度和影响力&#xff0c;我们计划邀请西安地区的主流媒体、行业媒体以及网络媒体参与活动&#xff0c;并进行现场报道和专访。通过媒体的力量&#xff…

【软件设计师】网络与多媒体基础知识

1.多媒体网络 JPEG累进&#xff08;或增量、渐进、递增&#xff09;编码模式&#xff0c;实现图像内容的方式传输&#xff0c;在浏览器上的直观效果就是无需过久等待即可看到模糊图像&#xff0c;然后图像显示和内容由模糊逐渐变得清晰 GIF图像文件格式以数据块为单位来存储图像…

src挖掘技巧--别人能挖到,你不来看看吗?

漏洞类型&#xff1a;拒绝服务漏洞 原理&#xff1a;通过控制修改验证码的长和宽&#xff0c;请求大量资源&#xff0c;导致拒绝服务漏洞&#xff0c;可以通过数据包的返回量值和返回时间来判断是否存在该漏洞。 实战报告 在获取验证码的时候进行抓包 右键打开验证码图片&am…

玩转STM32-直接存储器DMA(详细-慢工出细活)

文章目录 一、DMA介绍1.1 DMA简介1.2 DMA结构 二、DMA相关寄存器&#xff08;了解&#xff09;三、DMA的工作过程&#xff08;掌握&#xff09;四、DMA应用实例4.1 DMA常用库函数4.2 实例程序 一、DMA介绍 1.1 DMA简介 DMA用来提供外设与外设之间、外设与存储器之间、存储器与…

HTML静态网页成品作业(HTML+CSS)——家乡芷江侗族自治县介绍网页(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

深入解析数据库中的连接方法:四种关键技巧

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、连接方法的重要性 二、左连接&#xff08;Left Join&#xff09; 三、右连接&#xff…

干货 | 学习网络安全,推荐6个常用的安全知识在线手册(非常详细)零基础入门到精通,收藏这一篇就够了

排名不分先后&#xff0c;欢迎各位小伙伴下方留言评论补充 **VulDoc ** 包含&#xff1a;IOT安全&#xff0c;Web安全&#xff0c;系统安全 地址&#xff1a;http://47.112.148.3:8000/ **滴水逆向学习笔记 ** 包含 汇编 C C Win32 MFC 网络编程 数据库 数据…

开源自定义表单系统源码 一键生成表单工具 可自由DIY表单模型+二开

分享一款开源自定义表单系统源码&#xff0c;能够实现99%各行业的报名、预约、加盟申请、调查等应用&#xff0c;而且同时多开创建多个表单&#xff0c;支持自定义各种字段模型&#xff0c;市面上需要的表单模型都含了&#xff0c;随便自定义啦&#xff0c;含完整的代码包和详细…

Windows找出权限维持的后门

Windows权限维持主要包含活动隐藏、自启动等技术。 隐藏文件 利用文件属性 最简单的一种隐藏文件的方式&#xff0c;文件右键属性&#xff0c;勾选隐藏&#xff0c;点击确定后&#xff0c;在这个文件里看不到刚刚的文件了。 如果要让文件显示出来&#xff0c;就点击查看&…

图像处理中的维度元素复制技巧

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、维度元素复制的基本概念 三、如何实现维度元素复制 1. 方法介绍 2. 代码示…

java大学城水电管理系统源码(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的大学城水电管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 大学城水电管理系统的…

C++ | Leetcode C++题解之第118题杨辉三角

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<vector<int>> generate(int numRows) {vector<vector<int>> ret(numRows);for (int i 0; i < numRows; i) {ret[i].resize(i 1);ret[i][0] ret[i][i] 1;for (int j 1; j &…

批量视频剪辑神器:高效提取随机秒数画面,轻松实现视频素材精准筛选!

在数字化时代&#xff0c;视频内容已成为我们生活中不可或缺的一部分。无论是个人创作者还是专业团队&#xff0c;都需要对视频素材进行高效处理。然而&#xff0c;面对大量的视频文件&#xff0c;如何快速提取出我们所需的画面片段&#xff0c;却成为了一个令人头疼的问题。今…

【Unity入门】认识Unity编辑器

Unity 是一个广泛应用于游戏开发的强大引擎&#xff0c;从 1.0 版本开始到现在&#xff0c;其编辑器的基本框架一直保持稳定。其基于组件架构的设计&#xff0c;使得界面使用起来直观且高效。为了更好地理解 Unity 的界面&#xff0c;我们可以将其比喻为搭建一个舞台。以下是对…