011 数据结构_哈希

news2024/9/25 5:21:56

前言

本文将会向你介绍哈希概念,哈希方法,如何解决哈希冲突,以及闭散列与开散列的模拟实现

1. 哈希概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即 O( l o g 2 N log_2N log2N),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一 一映射的关系,那么在查找时通过该函数可以很快找到该元素。 当向该结构中: 插入元素根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放搜索元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功
该方式即为哈希方法,哈希方法中使用的转换函数称为哈希函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表
例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:hash(key) = key % size; size为存储元素底层空间总的大小。
在这里插入图片描述

2. 哈希方法

哈希方法:我们通常对关键码key进行转换来确定存储的位置,比如由字符串abc转换成一个整数作为存储的位置,这个转换的方法称为哈希方法,哈希方法中运用的函数叫做哈希函数

(1)直接定址法

ps:哈希方法是一个广义的概念,而哈希函数是哈希方法的一种具体实现。

1、直接定址法 值和位置关系唯一关系,每个值都有一个唯一位置,但是值很分散,直接定址会导致空间开很大,导致空间浪费
(此方法运用于关键字范围集中,量不大的情况,关键字和存储位置是一对一的关系,不存在哈希冲突)

在这里插入图片描述

引入哈希冲突

哈希冲突概念:不同关键字通过相同的哈希函数计算出相同的哈希存储位置(不同的值映射到相同的位置上去),这种现象被称为哈希冲突或哈希碰撞,哈希冲突的发生与哈希函数的设计有关

(2)除留余数法

主要应用于关键字可以很分散,量可以很大,关键字和存储位置是多对一的关系的情况,但是存在哈希冲突
在这里插入图片描述

3. 解决哈希冲突

(1)闭散列

概念: 闭散列又称开放定址法,指当前位置被占用(哈希冲突),开放空间里按照某种规则,找一个没有被占用的位置存储
1、线性探测
从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止 Hashi = hashi + i(i>=0)
2、二次探测
探测公式发生变化 hashi + i^2(i>=0)

(2)开散列

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地
址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
如图可观察到,val值为44的节点和节点val值为4的节点发生哈希冲突
开散列中每个桶中放大都是发生哈希冲突的元素

在这里插入图片描述

引入负载因子

负载因子:存储个数/空间的大小(注意这里的空间的大小是size而不是capacity)
由于在哈希表中,operator[]操作会根据已有的元素数量(即size())进行检查。因此,在计算负载因子时,要使用已有元素的个数除以哈希表的大小(即size())
size()函数返回的是当前哈希表中实际存储的元素数量,而capacity()函数返回的是哈希表的容量(即内部存储空间的大小)
负载因子:存储关键字个数/空间大小 负载因子太大,冲突可能会剧增,冲突增加,效率降低 负载因子太小,冲突降低,但是空间利用率就低了

5. 哈希表扩容

扩容的核心是先开辟新空间,然后遍历旧空间的数据,按照hashi = hashi % Newsize重新建立映射,然后将旧空间的数据拷贝到新空间去,最后交换新旧哈希表,本质上我们还是要对旧哈希表进行扩容,因此最后要swap交换两表
在这里插入图片描述

6. 哈希表插入

三种状态EMPTY、EXIST、DELETE

EMPTY,表示该位置为空。
EXIST,表示该位置被占用了。
DELETE,表示该位置被删除了。

删除状态存在的含义

或许你会有疑问:删除为什么不能直接设为空状态,而是将被删除的状态设置为DELETE

在这里插入图片描述

7. 闭散列模拟实现

数据结构

struct Elem
{
	pair<K, V> _val;
	State _state = EMPTY;
};
vector<Elem<K, V>> _ht;

在这里插入图片描述

闭散列插入

闭散列的插入步骤是:判断是否存在,判断是否需要扩容(结合负载因子),遍历旧空间拷贝数据
关于闭散列的模拟实现,核心步骤在上文都有讲,这里就不再多作赘述,具体可看下面的代码与注释

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

	//因为字符串做键值非常常见,库里面也特化了一份
	//BKDR算法,这里不会展开来讲
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t hashi = 0;
			for (auto ch : key)
			{
				hashi = hashi * 31 + ch;
			}
			return hashi;
		}
	};

	enum State 
	{ 
		EMPTY
		,EXIST
		,DELETE
	};
	template <class K, class V>
	struct Elem
	{
		pair<K, V> _val;
		State _state = EMPTY;
	};
	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable(size_t capacity = 3)
			: _ht(capacity)
			,_size(0)
			, _totalSize(0)
		{
			for (size_t i = 0; i < capacity; ++i)
				_ht[i]._state = EMPTY;
		}

		// 插入
		bool Insert(const pair<K, V>& val)
		{
			Hash hf;
			_size = _ht.size();
			//已有
			if (Find(val.first))
			{
				return false;
			}
			else
			{
				//扩容,负载因子==0.6
				if ((double)_totalSize / _size >= 0.6)
				{
					//开辟新空间
					size_t newsize = _size * 2;
					HashTable<K, V, Hash> NewHt;
					NewHt._ht.resize(newsize);

					//遍历旧空间
					for (int i = 0; i < _size; i++)
					{
						if (_ht[i]._state == EXIST)
						{
							NewHt.Insert(_ht[i]._val);
						}
					}
					NewHt._ht.swap(_ht);
				}
				size_t hashi = hf(val.first) % _size;
				//不为空,向后查找
				while (_ht[hashi]._state == EXIST)
				{
					hashi++;
					//如果超出数组长度
					hashi %= _size;
				}
				//为空,插入
				_ht[hashi]._val.first = val.first;
				_ht[hashi]._val.second = val.second;
				_ht[hashi]._state = EXIST;
				++_totalSize;
				return true;
			}
		}

		// 查找
		Elem<K, V>* Find(const K& key)
		{
			Hash hf;
			//线性探测
			size_t hashi = hf(key) % _ht.size();
			while (_ht[hashi]._state != EMPTY)
			{	
				if (_ht[hashi]._state == EXIST 
					&& _ht[hashi]._val.first == key)
				{
					return &_ht[hashi];
				}
				hashi++;
				//超出数组长度
				hashi %= _ht.size();

			}
			//没有找到a
			return nullptr;
		}

		// 删除
		bool Erase(const K& key)
		{
			Elem<K, V>* ret = Find(key);
			//不为空就说明找到
			if (ret)
			{
				ret->_state = DELETE;
				--_totalSize;
				return true;
			}
			else return false;
		}

	private:
		size_t HashFunc(const K& key)
		{
			return key % _ht.capacity();
		}

		void CheckCapacity();
	private:
		vector<Elem<K, V>> _ht;
		size_t _size;
		size_t _totalSize;  // 哈希表中的所有元素:有效和已删除, 扩容时候要用到
	};
}

测试

		void Print()
		{
			for (int i = 0; i < _ht.size(); i++)
			{
				if (_ht[i]._state == EXIST)
				{
					//printf("[%d]->%d\n", i, _tables[i]._kv.first);
					cout << "[" << i << "]->" << _ht[i]._val.first << ":" << _ht[i]._val.second << endl;
				}
				else if (_ht[i]._state == EMPTY)
				{
					printf("[%d]->\n", i);
				}
				else
				{
					printf("[%d]->D\n", i);
				}
			}
			
void TestHT1()
{
	Close_Hash::HashTable<int, int> ht;
	int a[] = { 4,14,24,34,5,7,1 };
	for (auto e : a)
	{
		ht.Insert(make_pair(e, e));
	}
	ht.Print();
	ht.Insert(make_pair(3, 3));
	ht.Insert(make_pair(3, 3));
	ht.Insert(make_pair(-3, -3));
	ht.Print();
	cout << endl;

	ht.Erase(3);;
	ht.Print();

	if (ht.Find(3))
	{
		cout << "3存在" << endl;
	}
	else
	{
		cout << "3不存在" << endl;
	}
	ht.Insert(make_pair(23, 3));
	ht.Insert(make_pair(3, 3));
	if (ht.Find(3))
	{
		cout << "3存在" << endl;
	}
	else
	{
		cout << "3不存在" << endl;
	}
	ht.Print();
}

8. 开散列模拟实现

数据结构

	struct HashNode
	{
		HashNode* _next;
		pair<K, V> _val;
		HashNode(const pair<K, V>& val)
			:_next(nullptr)
			,_val(val)
		{}
	};
	typedef HashNode<K, V> Node;
	vector<Node*> _ht;

在这里插入图片描述

开散列插入

插入的主要逻辑是:先查找是否存在,判断是否需要扩容(依据平衡因子),开辟新空间然后遍历旧空间,将旧空间的数据拷贝到新空间上(需要根据新的映射关系,待会会细讲),最后插入节点

bool Insert(const pair<K, V>& val)
{
	Hash hf;
	//已有
	if (Find(val.first))
	{
		return false;
	}
	//扩容,负载因子==1
	if (_totalSize == _ht.size())
	{
		//开辟新空间
		size_t newsize = _ht.size() * 2;
		vector<Node*> NewHt;
		NewHt.resize(newsize);

		//遍历旧空间
		for (int i = 0; i < _ht.size(); i++)
		{
			Node* cur = _ht[i];
			while (cur)
			{
				//保存下一个结构体指针
				Node* next = cur->_next;
				size_t hashi = hf(cur->_val.first) % NewHt.size();
				//将新空间上hashi位置处的哈希桶链接到需要处理的当前节点
				cur->_next = NewHt[hashi];
				NewHt[hashi] = cur;
				//处理旧空间上哈希桶的下一个节点
				cur = next;
			}
			//防止出现悬空指针的问题
			_ht[i] = nullptr;
		}   
		_ht.swap(NewHt);
	}
		//插入节点
		size_t hashi = hf(val.first) % _ht.size();
		Node* newnode = new Node(val);
		//头插
		newnode->_next = _ht[hashi];
		_ht[hashi] = newnode;
		++_totalSize;
		return true;
}

以下是遍历旧空间,拷贝数据的图解
在这里插入图片描述
插入过程图解
在这里插入图片描述
全部代码


namespace Open_Hash
{
	template<class T>
	struct HashFunc
	{
		size_t operator()(const T& key)
		{
			if (key >= 0)
			{
				return (size_t)key;
			}
			else
			{
				return abs(key);
			}
		}
	};

	//字符串哈希算法这里不展开讲,采用的是BKDR算法
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& key)
		{
			size_t hashi = 0;
			for (auto ch : key)
			{
				hashi = hashi * 31 + ch;
			}
			return hashi;
		}
	};
	template <class K, class V>
	struct HashNode
	{
		HashNode* _next;
		pair<K, V> _val;
		HashNode(const pair<K, V>& val)
			:_next(nullptr)
			,_val(val)
		{}
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:	
		HashTable()
		{
			_ht.resize(10);
		}
		~HashTable()
		{
			for (int i = 0; i < _ht.size(); i++)
			{
				Node* cur = _ht[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				//将当前哈希桶置空
				_ht[i] = nullptr;
			}
		}
		typedef HashNode<K, V> Node;
		// 插入
		bool Insert(const pair<K, V>& val)
		{
			Hash hf;
			//已有
			if (Find(val.first))
			{
				return false;
			}
			//扩容,负载因子==1
			if (_totalSize == _ht.size())
			{
				//开辟新空间
				size_t newsize = _ht.size() * 2;
				vector<Node*> NewHt;
				NewHt.resize(newsize);

				//遍历旧空间
				for (int i = 0; i < _ht.size(); i++)
				{
					Node* cur = _ht[i];
					while (cur)
					{
						//保存下一个结构体指针
						Node* next = cur->_next;
						size_t hashi = hf(cur->_val.first) % NewHt.size();
						//将新空间上hashi位置处的哈希桶链接到需要处理的当前节点
						cur->_next = NewHt[hashi];
						NewHt[hashi] = cur;
						//处理旧空间上哈希桶的下一个节点
						cur = next;
					}
					//防止出现悬空指针的问题
					_ht[i] = nullptr;
				}
				_ht.swap(NewHt);
			}
				//插入节点
				size_t hashi = hf(val.first) % _ht.size();
				Node* newnode = new Node(val);
				//头插
				newnode->_next = _ht[hashi];
				_ht[hashi] = newnode;
				++_totalSize;
				return true;
		}

		//查找
		Node* Find(const K& key)
		{
			Hash hf;
			//线性探测
			size_t hashi = hf(key) % _ht.size();
			Node* cur = _ht[hashi];
			//遍历对应hashi位置处的哈希桶
			while (cur)
			{
				if (cur->_val.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			//没有找到
			return nullptr;
		}
		// 删除
		bool Erase(const K& key)
		{
			Hash hf;
			Node* ret = Find(key);
			size_t hashi = hf(key) % _ht.size();
			//不为空就说明找到
			if (ret)
			{
				Node* cur = _ht[hashi];
				Node* prev = nullptr;
				//遍历当前哈希桶
				while (cur)
				{
					if (cur->_val.first == key)
					{
						//判断是头删还是中间位置处的删除
						if (prev == nullptr)
						{
							_ht[hashi] = cur->_next;
						}
						else
						{
							prev->_next = cur->_next;
						}
						delete cur;
						return true;
					}
					prev = cur;
					cur = cur->_next;
				}
			}
			//未找到
			return false;
		}

	private:
			vector<Node*> _ht;
			Node* _next = nullptr;
			size_t _totalSize = 0;  // 哈希表中的所有元素:有效和已删除, 扩容时候要用到
	};
}

测试

		//打印
		void Print1()
		{
			for (int i = 0; i < _ht.size(); i++)
			{
				Node* cur = _ht[i];
				cout << "[" << i << "]:";
				//哈希桶不为空
				while(cur)
				{
					cout << "(" << cur->_val.first << "," << cur->_val.second << ")" << "->";
					cur = cur->_next;
				}
				cout << endl;
			}
			cout << endl;
		}

		void Print2()
		{
			for (int i = 0; i < _ht.size(); i++)
			{
				Node* cur = _ht[i];
				//哈希桶不为空
				while (cur)
				{
					cout << cur->_val.first << ":"<< cur->_val.second << " ";
					cur = cur->_next;
				}
			}
			cout << endl;
		}
//测试
		void TestHT1()
		{
			HashTable<int, int> ht;
			int a[] = { 4,14,24,34,5,7,1 };
			for (auto e : a)
			{
				ht.Insert(make_pair(e, e));
			}

			ht.Insert(make_pair(3, 3));
			ht.Insert(make_pair(3, 3));
			ht.Insert(make_pair(-3, -3));
			ht.Print1();

			ht.Erase(3);
			ht.Print1();

			if (ht.Find(3))
			{
				cout << "3存在" << endl;
			}
			else
			{
				cout << "3不存在" << endl;
			}

			ht.Insert(make_pair(3, 3));
			ht.Insert(make_pair(23, 3));
			//ht.Insert(make_pair(-9, -9));
			ht.Insert(make_pair(-1, -1));
			ht.Print1();
		}

		void TestHT2()
		{
			string arr[] = { "香蕉", "甜瓜","苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
			//HashTable<string, int, HashFuncString> ht;
			HashTable<string, int> ht;
			for (auto& e : arr)
			{
				//auto ret = ht.Find(e);
				HashNode<string, int>* ret = ht.Find(e);
				if (ret)
				{
					ret->_val.second++;
				}
				else
				{
					ht.Insert(make_pair(e, 1));
				}
			}

			ht.Print2();

			ht.Insert(make_pair("apple", 1));
			ht.Insert(make_pair("sort", 1));

			ht.Insert(make_pair("abc", 1));
			ht.Insert(make_pair("acb", 1));
			ht.Insert(make_pair("aad", 1));

			ht.Print2();
		}

		void Some()
		{
				const size_t N = 100;
				vector<int> v;
				v.reserve(N);
				srand(time(0));
				for (size_t i = 0; i < N; ++i)
				{
					//v.push_back(rand()); // N比较大时,重复值比较多
					v.push_back(rand()%100+i); // 重复值相对少
					//v.push_back(i); // 没有重复,有序
				}
				HashTable<int, int> ht;
				for (auto e : v)
				{
					ht.Insert(make_pair(e, e));
				}
				ht.Print1();
		}

小结

今日的分享就到这里啦,后续将会向你带来位图与布隆过滤器的知识,如果本文存在疏漏或错误的地方还请您能够指出,另外如果你存在疑问,也可以评论留言哦!

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

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

相关文章

Spring容器中scope为prototype类型Bean的回收机制

文章目录 一、背景二、AutowireCapableBeanFactory 方法 autowireBean 分析三、Spring 容器中 scope 为 prototype 类型 Bean 的回收机制四、总结 一、背景 最近做 DDD 实践时&#xff0c;遇到业务对象需要交给 Spring 管理才能做一些职责内事情。假设账号注册邮箱应用层代码流…

【ARM Trace32(劳特巴赫) 使用介绍 1.2 - ARM 系统调试中常见的挑战】

请阅读【Trace32 ARM 专栏导读】 文章目录 ARM 系统调试中常见的挑战ARM 系统调试接口简例DAP-Debug Access portDAP 状态检查多核调试虚拟/物理地址Cache 数据一致性问题系统异常系统复位系统死机PC 采样Memory 采样RAM/Core Dump 分析小概率问题ARM 系统调试中常见的挑战 调试…

PyTorch官网demo解读——第一个神经网络(1)

神经网络如此神奇&#xff0c;feel the magic 今天分享一下学习PyTorch官网demo的心得&#xff0c;原来实现一个神经网络可以如此简单/简洁/高效&#xff0c;同时也感慨PyTorch如此强大。 这个demo的目的是训练一个识别手写数字的模型&#xff01; 先上源码&#xff1a; fr…

数据结构奇妙旅程之栈和队列

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

Linux---重定向命令

1. 重定向命令的介绍 重定向也称为输出重定向&#xff0c;把在终端执行命令的结果保存到目标文件。 2. 重定向命令的使用 命令说明>如果文件存在会覆盖原有文件内容&#xff0c;相当于文件操作中的‘w’模式>>如果文件存在会追加写入文件末尾&#xff0c;相当于文件…

[C++] 虚函数、纯虚函数和虚析构(virtual)

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/weixin_43197380&#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;本文由 Loewen丶原创&#xff0c;首发于 CSDN&#xff0c;转载注明出处&#x1f649;&…

http正向代理测试,nginx反向代理中转正向代理服务器

有3台服务器如下&#xff1a; 192.168.111.201&#xff08;反向代理到正向代理服务器&#xff09; 192.168.111.202&#xff08;正向代理服务器&#xff09; 192.168.111.203&#xff08;目标WEB系统&#xff09; 防火墙网络策略如图所示: 1、192.168.111.200 只能访问 192.168…

【送书活动】智能汽车、自动驾驶、车联网的发展趋势和关键技术

文章目录 前言01 《智能汽车》推荐语 02 《SoC底层软件低功耗系统设计与实现》推荐语 03 《SoC设计指南》推荐语 05 《智能汽车网络安全权威指南&#xff08;上册&#xff09;》推荐语 06 《智能汽车网络安全权威指南&#xff08;下册&#xff09;》推荐语 后记赠书活动 前言 …

普通二叉树和右倾斜二叉树--LeetCode 111题《Minimum Depth of Binary Tree》

本文将以解释计算二叉树的最小深度的思路为例&#xff0c;致力于用简洁易懂的语言详细描述普通二叉树和右倾斜二叉树在计算最小深度时的区别。通过跟随作者了解右倾斜二叉树的概念以及其最小深度计算过程&#xff0c;读者也将对左倾斜二叉树有更深入的了解。这将为解决LeetCode…

FreeRTOS学习——同步互斥

FreeRTOS学习——同步互斥 目录 FreeRTOS学习——同步互斥一、概念1.1 同步1.2 互斥 二、示例——有缺陷的同步三、示例——优化有缺陷的同步四、示例——有缺陷的互斥五、总结 一、概念 1.1 同步 在FreeRTOS中&#xff0c;同步是指任务之间按照某种规则进行协调和按序执行的…

HarmonyOS:使用MindSpore Lite引擎进行模型推理

场景介绍 MindSpore Lite 是一款 AI 引擎&#xff0c;它提供了面向不同硬件设备 AI 模型推理的功能&#xff0c;目前已经在图像分类、目标识别、人脸识别、文字识别等应用中广泛使用。 本文介绍使用 MindSpore Lite 推理引擎进行模型推理的通用开发流程。 基本概念 在进行开…

PDI/Kettle-9.2.0.0-R(对应jdk1.8)源码编译问题记录及源码结构简介

目录 &#x1f4da;第一章 前言&#x1f4d7;背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 代码结构初识基本结构&#x1f4d7;代码模块详情 ⁉️问题记录❓问题一&#xff1a;代码分支哪些是发布版本❗答&#xff1a;后缀-R的版本 ❓问题二&#xff1a;50…

AI智能配音助手微信小程序前后端源码支持多种声音场景选择

大家好今天给大家带来一款配音小程序 &#xff0c;这款小程序支持多种不同声音和场景的选择更人性化&#xff0c; 比如说支持各地区的方言,英文,童声呀等等、 另外也支持男声女声的选择,反正就是模板那些非常的多 当然啦音量,语调,语速那些都是可以DIY跳转的哟,所以说这一款小程…

使用PyTorch II的新特性加快LLM推理速度

Pytorch团队提出了一种纯粹通过PyTorch新特性在的自下而上的优化LLM方法&#xff0c;包括: Torch.compile: PyTorch模型的编译器 GPU量化:通过降低精度操作来加速模型 推测解码:使用一个小的“草稿”模型来加速llm来预测一个大的“目标”模型的输出 张量并行:通过在多个设备…

【专题】最小生成树(prim算法、kruscal算法)

目录 一、最小生成树二、Prim算法1. 算法思想2. 例题3. 性能分析 三、Kruscal算法1. 算法思想2. 例题3. 性能分析 一、最小生成树 生成树中边的权值&#xff08;代价&#xff09;之和最小的树。 二、Prim算法 1. 算法思想 设N(V,{E})是连通网&#xff0c;TE是N上最小生成树…

【IEEE】2区SCI,接收领域广,稳定检索47年!

重点 本期推荐 区块链是一种新兴技术&#xff0c;很多行业和领域都以创新方式采用了此技术&#xff0c;如能源、金融、媒体和娱乐以及零售等。此外&#xff0c;区块链作为一门新兴的交叉学科, 涉及密码学应用&#xff08;加密&#xff0c;隐私等&#xff09;&#xff0c; 分布式…

stm32与Freertos入门(二)移植FreeRTOS到STM32中

简介 注意&#xff1a;FreeRTOS并不是实时操作系统&#xff0c;而是分时复用的&#xff0c;只不过切换频率很快&#xff0c;感觉上是同时在工作。本次使用的单片机型号为STM32F103C8T6,通过CubeMX快速移植。 一、CubeMX快速移植 1、选择芯片 打开CubeMX软件&#xff0c;进行…

Diva配置——Communication Tests

关联文章:CANoe.Diva生成测试用例 Diva目录 一、CANoe.Diva简介二、Communication Tests配置一、CANoe.Diva简介 CANoe.DiVa 是一种 CANoe 选项,用于对 ECU 中的诊断软件实施进行自动化测试。 可以通过CANdelaStudio制作的CDD或ODX文件,经过Diva配置自动生成测试用例和测试脚…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于时空注意力卷积模型的超短期风电功率预测》

这个标题描述了一种用于超短期风电功率预测的模型&#xff0c;该模型基于时空注意力卷积模型。下面我会逐步解读这个标题的关键词和背景&#xff1a; 超短期风电功率预测&#xff1a;风电功率预测是指根据历史风速和其他相关数据&#xff0c;通过建立数学模型来预测未来特定时间…

Vue学习计划-Vue2--VueCLi(四)组件传值和自定义事件

1. 组件传值 组件化编码流程&#xff1a; 拆分静态组件&#xff1a;组件要按照功能点拆分&#xff0c;命名不要与html元素冲突实现动态组件&#xff1a;考虑好数据的存放位置&#xff0c;数据是一个组件在用&#xff0c;还是一些组件在用&#xff1a; 一个组件在用&#xff0c…