哈希表及其实现

哈希概念

        顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即 O(log2N),搜索的效率取决于搜索过程中元素的比较次数。

        哈希方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。

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

        例如:数据集合{1,7,6,4,5,9}

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

        用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度非常快。

哈希冲突

对于两个数据元素的关键字key1和 key2,有key1!=key2,但有:Hash(key1) == Hash(key2),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突

常见的哈希函数

1.直接定值法

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

        优点:不会发生Hash冲突,简单。

        缺点:可能会分布不均匀,使Hash表的一部分未被利用,导致浪费空间。

        例如:key={1,2,4,6,10000},哈希函数为Hash(key)= key。

        这样需要开一个大小为10000的数组但是仅仅存储这几个值,中间部分造成了极大的浪费。

2.除留余数法

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

        优点:可以自行规定表的大小,使插入数据可以均匀占据空间。

         缺点:造成哈希冲突。例如:15,22mod7都等于1,都占据1的位置。
        
        本文的Hash表模拟实现选用的Hash函数是 除留余数法。

如何解决Hash冲突?

闭散列的开放定值法

本质上是当前位置冲突,后面找一个合适的位置继续储存

线性探测法

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

例子:{1,4,24,34,7,44,17}(注意顺序!)

需要枚举类型表示一个位置的状态

enum State
{
   
	EMPTY,//空值,一开始一张表里都是空值
	EXIST,//有值,插入值时相应的位置变成有值

	DELETE//删除,在删除后,防止再次查找时,走到该位置停止
};

注意:本质上需要有值和无值即可,但是这里有一个DELETE,它是在查找时起作用

哈希表结构

代码如下: 

enum State
{
	EMPTY,
	EXIST,
	DELETE
};
template<class K,class V>
struct HashData
{
	par<K, V> _data;
	State _state = EMPTY;//开始都标记为空
};
template<class K,class V>
class HashTable
{
public:
	//插入
    ……
	//删除
    ……
	//寻找
private:
	vector<HashData<K, V>> _table;
	size_t _n = 0;实际插入数据的个数
};
 构造函数
		HashTable(size_t size = 10)//默认开10个类型的大小
		{
			_table.resize(size);
		}
插入函数

功能:给定一个值,通过哈希函数找到一个适合这个值位置的地址,插入。

        这种插入方法世间复杂度是O(1)。

        i=key%表的大小,如果i位置已经有值了,就线性往后找到空位置,放进去。

	//插入
	bool Insert(const pair<K,V>& kv)
	{
		size_t hashi = kv.first % _table.size();//根据值找位置

		while (_table[hashi]._state != EMPTY)//找到位置后判断是否发生哈希冲突
		{
			hashi++;//该位置有值(发生哈希冲突),向后移动。
			hashi %= _table.size();//防止越界(线性探测规则)
		}

        //解决哈希冲突后,在该位置插入值
		_table[hashi]._data = kv;
		_table[hashi]._state = EXIST;
		++_n;

		return true;
	}
扩容问题

        空间是有限的,一直插入,总会有空间不够的情况。

负载因子------衡量哈希表的拥挤程度,空间的剩余情况

        负载因子越大,哈希表越拥挤,空间剩余越少,可能造成的哈希冲突越多,效率越低。

计算公式:负载因子=已有数据/表的大小

        一般控制在0.7左右,当负载因子大于等于0.7时扩容。

方法:再建一个两倍大小的哈希表,将原来哈希表的数据重新插入到这个新的哈希表,最后交换一下哈希表的地址。

//扩容问题
//当负载因子到达0.7时,此时扩容
if (10 * _n / _table.size() >= 7)//此处同时乘以10,防止小数进行比较
{
	HashTable<K, V> newHT(2 * _table());//新开一个空间是两倍的哈希表
	for (auto& e : _table)
	{
		if (e._state == EXIST)
		{
			newHT.Insert(e._data);//不会出现无穷递归,因为插入的新表里时负载因子小,不会走扩容,直接走线性探测插入
		}
	}
	_table.swap(newHT._table);//使用的是vector容器的swap
}

完整的插入代码:

		bool Insert(const pair<K,V>& kv)
		{
			//扩容问题
			//当负载因子到达0.7时,此时扩容
			if (10 * _n / _table.size() >= 7)//此处同时乘以10,防止小数进行比较
			{
				HashTable<K, V> newHT(2 * _table());//新开一个空间是两倍的哈希表
				for (auto& e : _table)
				{
					if (e._state == EXIST)
					{
						newHT.Insert(e._data);//不会出现无穷递归,因为插入的新表里时负载因子小,不会走扩容,直接走线性探测插入
					}
				}
				_table.swap(newHT._table);//使用的是vector容器的swap
			}

			//线性探测
			size_t hashi = kv.first % _table.size();
			while (_table[hashi]._state != EMPTY)
			{
				hashi++;
				hashi %= _table.size();
			}
			_table[hashi]._data = kv;
			_table[hashi]._state = EXIST;
			++_n;
			return true;
		}
查找函数

功能:给定key值查找该节点的地址。

        查找的时间复杂度是O(1),因为直接根据哈希函数定位到地址,如果有哈希冲突,进行常阶的移位比较。(几乎不需要比较!)

        i=key%表的大小,如果i不是要查找的key就线性往后查找,直到找到或者遇到空(如果是DELETE,还要继续往后查找)

        如果找到了表的结尾,就要回到头查找。

	HashData<K, V>* Find(const K& key)
		{
			size_t hashi = key % _table.size();

            //开始寻找
			while (_table.[hashi]._state != EMPTY)
			{
				if (_table.[hashi]._state == EXIST
					&& key == _table[hashi]._data.first)
				{
					return &_table[hashi];
				}


				//探测规则(这里是线性探测)
				hashi++;//如果没找到,说明冲突了,向后线性探测。
				hashi %= _tables.size();//hashi向后++时,可能会越界,应该按照取余的方式控制在这个数组长度的范围内
			}
			return nullptr;
		}
删除函数

功能:给定key值,根据key值找到某节点,删除该节点

        很简单,只要用Find找到要删除的即可。

		//删除
		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);//通过key找到节点的位置

			if (ret)//若节点存在
			{
				--_n;
				ret->_state = DELETE;//删除
				return true;
			}
			else
			{
				return false;
			}
		}
总代码

	enum State
	{
		EMPTY,
		EXIST,
		DELETE
	};
	template<class K, class V>
	struct HashData
	{
		pair<K, V> _data;
		State _state = EMPTY;//开始都标记为空
	};

	template<class K, class V>
	class HashTable
	{
	public:
		HashTable(size_t size = 10)
		{
			_table.resize(size);
		}
		//插入
		bool Insert(const pair<K,V>& kv)
		{
			//扩容问题
			//当负载因子到达0.7时,此时扩容
			if (10 * _n / _table.size() >= 7)//此处同时乘以10,防止小数进行比较
			{
				HashTable<K, V> newHT(2 * _table.size());//新开一个空间是两倍的哈希表
				for (auto& e : _table)
				{
					if (e._state == EXIST)
					{
						newHT.Insert(e._data);//不会出现无穷递归,因为插入的新表里时负载因子小,不会走扩容
					}
				}
				_table.swap(newHT._table);
			}

			//线性探测
			size_t hashi = kv.first % _table.size();
			while (_table[hashi]._state != EMPTY)
			{
				hashi++;
				hashi %= _table.size();
			}
			_table[hashi]._data = kv;
			_table[hashi]._state = EXIST;
			++_n;
			return true;
		}
		//寻找
		HashData<K, V>* Find(const K& key)
		{
			size_t hashi = key % _table.size();
			while (_table[hashi]._state != EMPTY)
			{
				if (_table[hashi]._state == EXIST
					&& key == _table[hashi]._data.first)
				{
					return &_table[hashi];
				}

				//探测规则(这里是线性探测)
				hashi++;//如果没找到,说明冲突了,向后线性探测。
				hashi %= _table.size();//hashi向后++时,可能会越界,应该按照取余的方式控制在这个数组长度的范围内
			}
			return nullptr;
		}
		//删除
		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret->_state == EXIST)
			{
				--_n;
				ret->_state = DELETE;
				return true;
			}
			else
			{
				return false;
			}
		}
	private:
		vector<HashData<K, V>> _table;
		size_t _n = 0;
	};

开散列法

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

        本质:当使用除留余数法在同一个位置存储多个值时(发生哈希冲突),则在同一个位置以链表的形式储存多个值。 

例子:{1,4,24,34,7,44,17}(不是很注重顺序)

哈希表结构 

代码如下: 

template<class K,class V>
struct HashNode
{
	HashNode<K, V>* next;
	pair<K, V> _kv;
};

template<class K,class V>
class HashTable
{
	typedef HashNode<K,V> Node;
public:
	//插入

	//寻找

	//删除
private:
	vector<Node*> _table;
	size_t _n = 0;
};

构造函数

		HashTable(size_t size = 10)
		{
			_table.resize(size);
		}

析构函数

        为什么需要写析构函数?对于vector有它自己的析构函数,而我们对每个vector储存的指针指向链表没有析构,会导致内存泄漏。

        下面是对HashNode的节点组成的链表进行析构。

		~HashTable()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];

				while (cur)//将该节点的链表全部删除
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}

插入

功能:给定节点的key值,插入哈希表

原理:先根据key值计算出要插入节点的位置,再头插入链表。

		//插入
		bool Insert(const pair<K, V>& kv)
		{
			size_t hashi = kv.first % _table.size();//计算位置
			Node* newnode = new Node(kv);

			//头插
			newnode->_next = _table[hashi];//将新节点的_next指向最开始的节点,而vector保存的就是开始节点的指针
			_table[hashi] = newnode;//vector存储的指针换成刚插入的节点
			++_n;

			return true;
		}
扩容问题

当实际节点数等于表的大小时,扩容。

原理:开个两倍的新表,将原来节点重新定址后插入新表中,交换两个表。

注:不要重新开节点,直接将老表的节点重新定值插入新表中,防止创建新节点造成过多的开销。

		//插入
		bool Insert(const pair<K, V>& kv)
		{
//--------------------------------------------------------------------------------------
			//扩容
			if (_n == _table.size())//当实际位置的节点个数等于表的大小时进行扩容
			{
                //1.开一个大小为旧表两倍的新表
				vector<Node*> newTable(_table.size() * 2, nullptr);
                //2.遍历旧表每一个vector的每一个链表,将每个节点插入到新表
				for (size_t i = 0; i < _table.size(); i++)//遍历每一个表中vector单元
				{
					//取出旧表中的节点,重新定址后挂到新表桶中
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;//保存第一个节点的下一个节点

						//头插到新表
						size_t hashi = cur->_kv.first % newTable.size();//重新定址
						cur->_next = newTable[hashi];//这个vector单元保存的是第一个节点的指针
						newTable[hashi] = cur;//将vector保存的节点替换成新头插的节点

						cur = next;//再去刚才没有移动的下一个节点
					}
					_table[i] = nullptr;//原表vector处置空
				}
                //3.交换两表
				_table.swap(newTable);
			}
//--------------------------------------------------------------------------------------
			size_t hashi = kv.first % _table.size();//计算位置
			Node* newnode = new Node(kv);

			//头插
			newnode->_next = _table[hashi];//将新节点的_next指向最开始的节点,而vector保存的就是开始节点的指针
			_table[hashi] = newnode;//vector存储的指针换成刚插入的节点
			++_n;

			return true;
		}

查找

功能:给定一个(key)值,找到这个储存这个值的地址。

原理:根据key值计算索引位置,去该索引位置处遍历所挂的链表,直到找到key值所在的节点

		//寻找
		Node* Find(const K& key)
		{
			size_t hashi = key % _table.size();//计算这个值的位置
			Node* cur = _table[hashi];//找到这个位置

			//遍历这个位置挂的节点
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}

			return nullptr;
		}

删除

功能:给定一个(key)值,删除储存该值的节点

原理:根据key值计算出对应vector的位置,然后遍历比较vector下挂的链表,并删除节点

		//删除
		bool Erase(const K& key)
		{
			size_t hashi = key % _table.szie();//计算要删节点的位置
			Node* prev = nullptr;
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)//找到了该值所对应的节点
				{
					if (prev)//删除中间节点
					{
						prev->_next = cur->_next;//跳过cur节点,将prev节点与cur的下一个节点连接
					}
					else//删除第一个节点
					{
						_table[hashi] = cur->_next;//将vector中储存第二个节点
					}

					delete cur;
					--_n;
					return true;
				}
				//向下搜索
				prev = cur;
				cur = cur->_next;
			}

			return false;
		}

总代码

namespace syb1
{

	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 HashTable
	{
		typedef HashNode<K,V> Node;
	public:
		HashTable(size_t size = 10)
		{
			_table.resize(size);
		}
		~HashTable()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];

				while (cur)//将该节点的链表全部删除
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}
		//插入
		bool Insert(const pair<K, V>& kv)
		{
			//扩容
			if (_n == _table.size())//当实际位置的节点个数等于表的大小时进行扩容
			{
				vector<Node*> newTable(_table.size() * 2, nullptr);
				for (size_t i = 0; i < _table.size(); i++)//遍历每一个表中vector单元
				{
					//取出旧表中的节点,重新定址后挂到新表桶中
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;//保存第一个节点的下一个节点

						//头插到新表
						size_t hashi = cur->_kv.first % newTable.size();//重新定址
						cur->_next = newTable[hashi];//这个vector单元保存的是第一个节点的指针
						newTable[hashi] = cur;//将vector保存的节点替换成新头插的节点

						cur = next;//再去刚才没有移动的下一个节点
					}
					_table[i] = nullptr;//原表vector处置空
				}

				_table.swap(newTable);
			}

			size_t hashi = kv.first % _table.size();//计算位置
			Node* newnode = new Node(kv);

			//头插
			newnode->_next = _table[hashi];//将新节点的_next指向最开始的节点,而vector保存的就是开始节点的指针
			_table[hashi] = newnode;//vector存储的指针换成刚插入的节点
			++_n;

			return true;
		}
		//寻找
		Node* Find(const K& key)
		{
			size_t hashi = key % _table.size();//计算这个值的位置
			Node* cur = _table[hashi];//找到这个位置

			//遍历这个位置挂的节点
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}

			return nullptr;
		}
		//删除
		bool Erase(const K& key)
		{
			size_t hashi = key % _table.szie();//计算要删节点的位置
			Node* prev = nullptr;
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev)//删除中间节点
					{
						prev->_next = cur->_next;//跳过cur节点,将prev节点与cur的下一个节点连接
					}
					else//删除第一个节点
					{
						_table[hashi] = cur->_next;//将vector中储存第二个节点
					}

					delete cur;
					--_n;
					return true;
				}
				//向下搜索
				prev = cur;
				cur = cur->_next;
			}

			return false;
		}
	private:
		vector<Node*> _table;
		size_t _n = 0;
	};
}

 

关于不同类型的key值

        对于key值是int型参数时,直接进行取模运算,即可得到映射的位置。但是对于字符串类型(string)的key值,或者是结构体类型的key值,如何确定其映射的位置呢?我们可以利用仿函数将其他类型的key值转化。

插入浮点类型的值

原理:非常简单,直接强转

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

插入字符串类型的值

原理:读取string每一个字符,将其的ASCII值每次乘一个值后加到Hash变量中,最后返回这个值Hash,这样就对每一个不同字符串都得到一个不同的key值。

关于溢出问题:如果字符串太长,通过这样方式转换成整形后溢出怎么办?不用管,自动截断,得到一个key值。

关于每次乘的值:选31 131 1313 13131 131313这些值都可以

struct HashFuncString
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;//可以防止顺序不同造成的位置相同
		}

		return hash;
	}
};

插入结构体类型的值

 原理:同上,将每个元素转换成整形后乘上131加在一起。

struct HashFuncDate
{
	size_t operator()(const Date& d)
	{
		size_t hash = 0;
		hash += d._year;
		hash *= 131;
		
		hash += d._month;
		hash *= 131;

		hash += d._day;
		hash *= 131;
	}
};

总结:以后对于各种各样的key值,我们可以把每个元素划分出来(结构体类型的将每个元素划分出来,string类型的划分成一个个char型)乘上一个特定的数加在一起,从而得到一个int型的key值。

使用:当key值为不同的类型时,使用仿函数作用在key值上,使key值变成int型数据。

以查找函数为例:

		HashData<K, V>* Find(const K& key)//<------传string类型数据
		{
//-----------------------------------------------------------------------------------------
			HashFuncString hs;//类构造一个对象
			size_t hashi = hs(key) % _table.size();用仿函数将key转换成int类型的数据
//-----------------------------------------------------------------------------------------
			while (_table[hashi]._state != EMPTY)
			{
				if (_table[hashi]._state == EXIST
					&& key == _table[hashi]._data.first)
				{
					return &_table[hashi];
				}
				hashi++;
				hashi %= _table.size();
			}
			return nullptr;
		}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】—— 装饰器模式

目录 &#xff08;一&#xff09;什么是装饰器模式 &#xff08;二&#xff09;为什么要使用装饰器模式 &#xff08;三&#xff09;装饰器模式的实现步奏 &#xff08;四&#xff09;代码示例 &#xff08;五&#xff09;装饰器模式优缺点 &#xff08;一&#xff09;什么…

文档翻译-NVIDIA DALI Pipeline

文档地址&#xff1a; Pipeline — NVIDIA DALI 1.12.0 documentation 在DALI中&#xff0c;任何数据处理任务都有一个称为Pipeline的中心对象。Pipeline对象nvidia.dali.Pipeline或其派生类的实例。Pipeline封装了数据处理图和执行引擎。 您可以通过以下方式定义DALI管道&am…

虚拟内存页表和内存保护

前言 大家好我是jiantaoyab&#xff0c;这是我所总结作为学习的笔记第21篇&#xff0c;在这里分享给大家&#xff0c;这篇文章讲虚拟内存和内存之间的页表和内存安全问题。 虚拟内存 前面的文章提到过&#xff0c;程序装载到内存的过程。可以知道&#xff0c;程序并不直接访…

【python】flask基于cookie和session来实现会话控制

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

使用Java版工程行业管理系统源码,提升工程项目的综合管理能力

工程项目管理涉及众多环节和角色&#xff0c;如何实现高效协同和信息共享是关键。本文将介绍一个采用先进技术框架的Java版工程项目管理系统&#xff0c;该系统支持前后端分离&#xff0c;功能全面&#xff0c;可满足不同角色的需求。从项目进度图表到施工地图&#xff0c;再到…

3d模型变形动画怎么做---模大狮模型网

要制作3D模型的变形动画&#xff0c;你可以通过使用动画软件(如Blender、Maya、3ds Max等)中的变形工具和技术来实现。以下是一般的步骤来制作3D模型的变形动画&#xff1a; 创建基础模型&#xff1a;首先&#xff0c;在3D建模软件中创建或导入你想要进行变形的基础模型。这个基…

《InfMAE: A Foundation Model in Infrared Modality》CVPR2024

基础模型vs大模型&#xff1a;大模型&#xff0c;也称基础模型&#xff0c;是指具有大规模参数和复杂计算结构的机器学习模型 以后的研究中必须把大模型和基础模型耦合进来 总结&#xff1a;占坑 1. AB 多光谱的基础模型 红外的基础模型 可见光的基础模型 整体架构差不多…

智慧商显安卓主板MT8788_联发科MTK平台多媒体广告一体机方案

MT8788高性能智能主板&#xff0c;支持Android 9.0操作系统&#xff0c;支持双屏异显功能;MT8788是基于12nm工艺制程四核A73四核A53架构的八核心CPU,主频高达2.0GHz,拥有超强的通用计算性能。 MT8788主板采用10层二阶超高密度PCB板,集成了4G、百兆以太网、2.4G/5G 双频WiFi、蓝…

平时寄快递能够拿到最低的便宜价格吗?

现在快递物流与我们的日常生活联系很紧密了&#xff0c;但是等到我们真正去寄快递的时候就会很烦恼寄快递的价格怎么这么昂贵呢&#xff1f;但是我们又不得不选择去寄快递&#xff0c;所以我们能不能选择一种寄快递又方便&#xff0c;运费又便宜的方式呢&#xff1f; 尤其是我…

图书推荐|图解算法:C语言实现+视频教学版

零负担理解数据结构及其算法的设计&#xff0c;零基础也能快速上手编程。 本书内容 《图解算法&#xff1a;C语言实现视频教学版》是一本综合讲述数据结构及其算法的入门书&#xff0c;力求简洁、清晰、严谨、且易于学习和掌握。 《图解算法&#xff1a;C语言实现视频教学版》…

Flutter开发进阶之瞧瞧State

Flutter开发进阶之瞧瞧State 书接上回 上回说到StatefulWidget会将自身作为参数创建StatefulElement,然后StatefulElement的创建过程首先要调用_state = widget.createState(),会创建并持有一个state通过对其生命周期的管理去对Widget进行刷新,创建state后还会将自身交给st…

查看当前虚拟主机的php环境

近期看到一位用户在论坛的留言想要知道Linux虚拟主机的PHP环境&#xff0c;但是不清楚如何在主机上面查看&#xff0c;对于主机cPanel面板了解不多&#xff0c;因此在论坛寻求帮助。这边看到后详细的告知了这位用户&#xff0c;这边是以Hostease Linux虚拟主机为例的&#xff0…

Vue/Uni-app/微信小程序 v-if 设置出场/退出动画(页面交互不死板,看起来更流畅)

天梦星服务平台 (tmxkj.top)https://tmxkj.top/#/ 在Vue.js中&#xff0c;使用v-if进行条件渲染时设置动画可以通过<transition>组件来实现。 具体操作步骤如下&#xff1a; 包裹条件渲染的元素&#xff1a;您需要将要通过v-if控制显示隐藏的元素包裹在<transition…

02. Java 中的关键字、标识符、运算符、分隔符和注释

关键字 Java 的关键字(keyword、保留字)是 Java 语言中具有特殊含义的单词&#xff0c;它们被保留供 Java 自身使用&#xff0c;不能被用作标识符。例如 public、class、void、int 等都是关键字。 关键字在 Java 语法中起着重要的作用&#xff0c;它们定义了编程的结构、控制…

合并两个有序数组(力扣)

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&#xff1a;最终&#xff0c;合并后数组…

【开源-土拨鼠充电系统】鸿蒙 HarmonyOS 4.0 App+微信小程序+云平台

✨本人自己开发的开源项目&#xff1a;土拨鼠充电系统 ✨踩坑不易&#xff0c;还希望各位大佬支持一下&#xff0c;在Gitee或GitHub给我点个 Start ⭐⭐&#x1f44d;&#x1f44d; ✍Gitee开源项目地址&#x1f449;&#xff1a;https://gitee.com/cheinlu/groundhog-charging…

QT_day2:2024/3/21

作业1&#xff1a;使用QT完成一个登录界面 要求&#xff1a; 1. 需要使用Ui界面文件进行界面设计 2. ui界面上的组件相关设置&#xff0c;通过代码实现 3. 需要添加适当的动图 源代码&#xff1a; #include "widget.h" #include "ui_widget.h"Widget…

蓝桥杯算法练习系统—金属采集(树形dp)

问题描述 人类在火星上发现了一种新的金属&#xff01;这些金属分布在一些奇怪的地方&#xff0c;不妨叫它节点好了。一些节点之间有道路相连&#xff0c;所有的节点和道路形成了一棵树。一共有 n 个节点&#xff0c;这些节点被编号为 1~n 。人类将 k 个机器人送上了火星&…

3/21 work

自由发挥登录窗口的应用场景&#xff0c;实现一个登录窗口界面。&#xff08;不要使用课堂上的图片和代码&#xff0c;自己发挥&#xff0c;有利于后面项目的完成&#xff09; 要求&#xff1a; 1. 需要使用Ui界面文件进行界面设计 2. ui界面上的组件相关设置&#xff0c;通…

头歌实训--机器学习(决策树)

第1关&#xff1a;决策树简述 第2关&#xff1a;决策树算法详解 import numpy as np from sklearn import datasets#######Begin####### # 划分函数 def split(x,y,d,value):index_a(x[:,d]<value)index_b(x[:,d]>value)return x[index_a],x[index_b],y[index_a],y[inde…