图的拓扑排序(AOV网络)

news2024/10/6 10:31:00

文章目录

    • 拓扑排序
      • 概念
      • 实现
        • 邻接表(队列)
        • 邻接矩阵(栈)
      • 总结
    • 源代码
      • 邻接表
      • 邻接矩阵

拓扑排序

概念

拓扑排序是对有向无环图的顶点的一种排序.

  • AOV网络 : 在有向图中, 用顶点表示活动或者任务, 弧表示活动或者任务间的优先关系, 则此有向图称为用顶点表示活动的网络(Activity On Vertex简称AOV网络).
  • 拓扑序列(Topolagical Order) : 在有向无环图中, 若存在顶点vi到顶点vj的路径, 那么在序列中顶点vi就排在顶点vj的前面, 称此序列为拓扑排序.
  • 拓扑排序(Topological Sort) : 将有向无环图的顶点按照它们之间的优先关系排成一个拓扑序列的操作称为拓扑排序.

拓扑排序可以解决先决条件问题, 即以某种线性顺序来组织多项任务, 使任务顺序完成.

拓扑序列应该满足 : 如果有向无环图G存在顶点vi到vj的一条路径, 那么在序列中顶点vi必在顶点vj之前.

image-20230115181711823

实现

拓扑排序的步骤如下 :

  1. 在有向图中任选一个入度为0的顶点(即没有前驱的顶点)并输出它.
  2. 删除该顶点以及该顶点的所有出边, 将其邻接顶点的入度减一, 重复上述步骤, 最后结果可能有两种情况 : (1) 当输出了全部顶点时, 拓扑排序成功, 得到该图的拓扑排序. (2) 当图中还有顶点没有输出时, 拓扑排序失败, 说明该图中含有环, 剩余顶点的入度均不为零.
  • 如何计算和存储顶点的入度?

  • 定义一个整形数组inDegreeSize, 数组元素表示的各顶点的入度, 随图中边数的减少而减少. 从逻辑上删除某顶点以及该顶点的所有出边的操作, 可通过对该顶点的后继顶点的入度减一来实现. 此外, 为了便于查找入度为零的顶点, 可以另设一个存储空间来暂存入度为零的顶点 (可以用栈或者队列, 当度为零的顶点出栈或者出队时, 将对应的后继顶点的入度减一, 再将新的入度为零的顶点入栈或者入队) .

拓扑排序算法描述如下 :

  1. 计算每一个顶点的入度, 存入inDegreeSize数组, 遍历inDegreeSize数组并将所有入度为零的顶点入队列或者栈.
  2. 若队列或者栈非空, 从队头或者栈顶取出一个入度为零的顶点并输出它, 将以该顶点为弧尾的所有邻接顶点(弧头)的入度减一, 若此时某个邻接顶点的入度为零, 便将其入队或者入栈.
  3. 重复步骤2, 直到队列或者栈为空. 此时, 若所有顶点均被输出, 拓扑排序成功有意义返回true; 否则, 还有顶点未被输出表示图中有环, 拓扑排序失败没有意义返回false.

注意:在下面的邻接表与邻接矩阵中有写法一与写法二, 比较推荐编写写法一因为代价更低. 之所以有写法一与写法二只是想说明拓扑排序不唯一.

邻接表(队列)

namespace AdjacentList
{
	template<typename W>
	struct Edge
	{	
		int _dsti; // 终点顶点的下标
		W _weight; // 边的权值

		struct Edge<W>* _next; // 下一个结点的指针

		Edge(int dsti, const W& weight)
			:_dsti(dsti)
			,_weight(weight)
			,_next(nullptr)
		{}
	};

	template<typename V, typename W,bool Directed = false>
	class Graph
	{
		using Edge = Edge<W>;
	private:
		std::vector<V> _vertexSet; // 顶点的集合
		std::map<V, int> _vertexIndex; // 顶点映射下标
		std::vector<Edge*> _table; // 出度顶点表
    }
}

写法一:

image-20230115204636512

	void TestGraph()
	{
        std::string aStr[] = {
			"V1","V2","V3","V4","V5","V6","V7","V8","V9"
		};
        
		Graph<std::string, int, true> dg(aStr,sizeof(aStr)/sizeof(aStr[0]));
        
		dg.AddEdge("V1", "V3", 1);
		dg.AddEdge("V2", "V3", 1);
		dg.AddEdge("V2", "V4", 1);
		dg.AddEdge("V2", "V7", 1);
		dg.AddEdge("V3", "V4", 1);
		dg.AddEdge("V3", "V5", 1);
		dg.AddEdge("V4", "V5", 1);
		dg.AddEdge("V4", "V6", 1);
		dg.AddEdge("V4", "V7", 1);
		dg.AddEdge("V5", "V6", 1);
		dg.AddEdge("V5", "V9", 1);
		dg.AddEdge("V6", "V9", 1);
		dg.AddEdge("V7", "V8", 1);
		dg.AddEdge("V8", "V9", 1);

		dg.TopologicalSort();
	}

image-20230115205020402

写法二:

image-20230115195326133

image-20230115195847862

邻接矩阵(栈)

namespace AdjacentMatrix
{
	template<class V,class W=int,W W_MAX=INT_MAX,bool Directed=false>
	class Graph
	{
	private:
		std::vector<V> _vertexs; // 顶点集合,下标找顶点

		std::vector<vector<W>> _matrix; // 邻接矩阵,顶点与顶点之间的权值

		YX::RedBlackTree<V, int> _findIndexTree; // 顶点与下标的映射,顶点找下标
    }
}

写法一:

image-20230115205459566

	void TestGraph()
	{
		std::string aStr[] = {
			"V1","V2","V3","V4","V5","V6","V7","V8","V9"
		};

		Graph<std::string, int, INT_MAX,true> dg(aStr,sizeof(aStr)/sizeof(aStr[0]));
		
		dg.AddEdge("V1", "V3", 1);
		dg.AddEdge("V2", "V3", 1);
		dg.AddEdge("V2", "V4", 1);
		dg.AddEdge("V2", "V7", 1);
		dg.AddEdge("V3", "V4", 1);
		dg.AddEdge("V3", "V5", 1);
		dg.AddEdge("V4", "V5", 1);
		dg.AddEdge("V4", "V6", 1);
		dg.AddEdge("V4", "V7", 1);
		dg.AddEdge("V5", "V6", 1);
		dg.AddEdge("V5", "V9", 1);
		dg.AddEdge("V6", "V9", 1);
		dg.AddEdge("V7", "V8", 1);
		dg.AddEdge("V8", "V9", 1);

		dg.TopologicalSort();
	}

image-20230115205741722

写法二:

image-20230115201833234

image-20230115202351457

总结

与图的遍历相同:

  • 图的每条边弧处理一次
  • 图的每个顶点访问一次

采用邻接表表示时, 时间复杂度为O(n+e).

采用邻接矩阵表示时, 时间复杂度O(n2).

空间复杂度都为O(n).

源代码

邻接表

namespace AdjacentList
{
	template<typename W>
	struct Edge
	{
		int _dsti;
		W _weight;

		struct Edge<W>* _next;

		Edge(int dsti, const W& weight)
			:_dsti(dsti)
			, _weight(weight)
			, _next(nullptr)
		{}
	};

	template<typename V, typename W, bool Directed = false>
	class Graph
	{
		using Edge = Edge<W>;
	private:
		std::vector<V> _vertexSet; // 顶点的集合
		std::map<V, int> _vertexIndex; // 顶点映射下标
		std::vector<Edge*> _table; // 出度边表
	public:

		typedef Graph<V, W, Directed> Self;

		Graph() = default;

		Graph(const V* a, int n)
		{
			for (int i = 0; i < n; i++)
			{
				AddVertex(a[i]);
			}
		}

		int GetVertexIndex(const V& v)
		{
			typename std::map<V, int>::iterator pos = _vertexIndex.find(v);
			if (pos != _vertexIndex.end())
			{
				return pos->second;
			}
			else
			{
				return -1;
			}
		}

		bool AddVertex(const V& v)
		{
			if (GetVertexIndex(v) != -1)
				return false;

			_vertexSet.push_back(v);

			_vertexIndex.insert(std::make_pair(v, _vertexSet.size() - 1));

			_table.push_back(nullptr);

			return true;
		}

		bool _AddEdge(int srci, int dsti, const W& weight)
		{
			Edge* edge = new Edge(dsti, weight);

			// 头插
			edge->_next = _table[srci];
			_table[srci] = edge;

			// 无向图
			if (!Directed)
			{
				edge = new Edge(srci, weight);

				edge->_next = _table[dsti];
				_table[dsti] = edge;
			}

			return true;
		}

		bool AddEdge(const V& src, const V& dst, const W& weight)
		{
			int srci = GetVertexIndex(src);
			int dsti = GetVertexIndex(dst);

			// 顶点不在图中,添加边失败
			if (srci == -1 || dsti == -1)
				return false;

			//Edge* edge = new Edge(dsti, weight);

			 头插
			//edge->_next = _table[srci];
			//_table[srci] = edge;

			 无向图
			//if (!Directed)
			//{
			//	edge = new Edge(srci, weight);

			//	edge->_next = _table[dsti];
			//	_table[dsti] = edge;
			//}

			return _AddEdge(srci, dsti, weight);
		}

		//bool TopologicalSort()
		//{
		//	if (!Directed)
		//		return false;

		//	// 记录顶点的入度大小
		//	std::vector<int> inDegreeSize(_vertexSet.size(), 0);
		//	// 记录顶点是否被访问过(即是否入过队或者栈)
		//	std::vector<bool> visited(_vertexSet.size(), false);

		//	// 顶点个数
		//	int n = static_cast<int>(_vertexSet.size());

		//	// 获取图中所有顶点对应的入度大小
		//	for (int i = 0; i < n; i++)
		//	{
		//		Edge* curr = _table[i];

		//		while (curr != nullptr)
		//		{	
		//			inDegreeSize[curr->_dsti]++;
		//			curr = curr->_next;
		//		}
		//	}
		//	
		//	// 记录入度为零的顶点
		//	std::queue<int> q;

		//	// 将入度为零的顶点下标入队或者入栈
		//	for (int i = 0; i < n; i++)
		//	{
		//		if (inDegreeSize[i] == 0 && !visited[i])
		//		{
		//			q.push(i);
		//			visited[i] = true;
		//		}
		//	}

		//	while (!q.empty())
		//	{
		//		// 先遍历某入度为零的顶点
		//		int front = q.front();
		//		q.pop();
		//		std::cout << _vertexSet[front] << "--->";// << std::endl;

		//		// 再将该顶点对应的后继顶点的入度减一
		//		Edge* curr = _table[front];

		//		while (curr != nullptr)
		//		{
		//			inDegreeSize[curr->_dsti]--;

		//			curr = curr->_next;
		//		}

		//		// 最后将没有入过队或者栈的入度为零的顶点入队
		//		for (int i = 0; i < n; i++)
		//		{
		//			if (inDegreeSize[i] == 0 && !visited[i])
		//			{
		//				q.push(i);
		//				visited[i] = true;
		//			}
		//		}
		//	}

		//	// 判断是否还有顶点没有访问到
		//	for (int i = 0; i < n; i++)
		//	{
		//		if (inDegreeSize[i] != 0 && !visited[i])
		//		{
		//			printf("\nTopological sorting doesn't make sense\n");
		//			return false;
		//		}
		//	}

		//	printf("\nTopological sorting makes sense\n");
		//	return true;
		//}
		
		bool TopologicalSort()
		{
			if (!Directed)
				return false;

			// 记录顶点的入度大小
			std::vector<int> inDegreeSize(_vertexSet.size(), 0);

			// 顶点个数
			int n = static_cast<int>(_vertexSet.size());

			// 获取图中所有顶点对应的入度大小
			for (int i = 0; i < n; i++)
			{
				Edge* curr = _table[i];

				while (curr != nullptr)
				{
					inDegreeSize[curr->_dsti]++;
					curr = curr->_next;
				}
			}

			// 记录入度为零的顶点
			std::queue<int> q;

			// 将入度为零的顶点下标入队或者入栈
			for (int i = 0; i < n; i++)
			{
				if (inDegreeSize[i] == 0)
				{
					q.push(i);
				}
			}

			while (!q.empty())
			{
				// 先遍历某入度为零的顶点
				int front = q.front();
				q.pop();
				std::cout << _vertexSet[front] << "--->";// << std::endl;

				// 再将该顶点对应的后继顶点的入度减一
				Edge* curr = _table[front];

				while (curr != nullptr)
				{
					// 如果有顶点的入度被减为零时,则该顶点入队或者入栈
					if (--inDegreeSize[curr->_dsti] == 0)
					{
						q.push(curr->_dsti);
					}
					curr = curr->_next;
				}
			}

			// 判断是否还有顶点没有访问到
			for (int i = 0; i < n; i++)
			{
				if (inDegreeSize[i] != 0)
				{
					printf("\nTopological sorting doesn't make sense\n");
					return false;
				}
			}

			printf("\nTopological sorting makes sense\n");
			return true;
		}
	};

	void TestGraph()
	{
		Graph<std::string, int, true> dg;

		dg.AddVertex("V1");
		dg.AddVertex("V2");
		dg.AddVertex("V3");
		dg.AddVertex("V4");
		dg.AddVertex("V5");
		dg.AddVertex("V6");
		dg.AddVertex("V7");
		dg.AddVertex("V8");
		dg.AddVertex("V9");

		dg.AddEdge("V1", "V3", 1);
		dg.AddEdge("V2", "V3", 1);
		dg.AddEdge("V2", "V4", 1);
		dg.AddEdge("V2", "V7", 1);
		dg.AddEdge("V3", "V4", 1);
		dg.AddEdge("V3", "V5", 1);
		dg.AddEdge("V4", "V5", 1);
		dg.AddEdge("V4", "V6", 1);
		dg.AddEdge("V4", "V7", 1);
		dg.AddEdge("V5", "V6", 1);
		dg.AddEdge("V5", "V9", 1);
		dg.AddEdge("V6", "V9", 1);
		dg.AddEdge("V7", "V8", 1);
		dg.AddEdge("V8", "V9", 1);

		dg.TopologicalSort();
	}
}

邻接矩阵

namespace AdjacentMatrix
{
	template<typename V, typename W, W W_MAX, bool Directed = false>
	class Graph
	{
	private:
		std::vector<V> _vertexSet;
		std::map<V, int> _vertexIndex;
		std::vector<std::vector<W>> _matrix;
	public:

		typedef Graph<V, W, W_MAX, Directed> Self;

		Graph() = default;

		Graph(const V* a, int n)
		{
			for (int i = 0; i < n; i++)
			{
				AddVertex(a[i]);
			}
		}

		int GetVertexIndex(const V& v)
		{
			typename std::map<V, int>::iterator pos = _vertexIndex.find(v);
			if (pos != _vertexIndex.end())
			{
				return pos->second;
			}
			else
			{
				return -1;
			}
		}

		bool AddVertex(const V& v)
		{
			// 顶点存在不需要继续增加
			if (GetVertexIndex(v) != -1)
				return false;

			_vertexSet.push_back(v);
			_vertexIndex.insert(std::make_pair(v, _vertexSet.size() - 1));

			// 先在原有的行上一列
			for (int i = 0; i < _matrix.size(); i++)
			{
				_matrix[i].push_back(W_MAX);
			}

			// 增加一行
			_matrix.push_back(std::vector<W>(_vertexSet.size(), W_MAX));

			return true;
		}

		bool AddEdge(const V& src, const V& dst, const W& weight)
		{
			int srci = GetVertexIndex(src);
			int dsti = GetVertexIndex(dst);

			// 顶点不在图中,添加边失败
			if (srci == -1 || dsti == -1)
				return false;

			//_matrix[srci][dsti] = weight;

			 如果为无向图,则需要再添加一条dst->src的边
			//if (!Directed)
			//{
			//	_matrix[dsti][srci] = weight;
			//}

			//return true;

			return _AddEdge(srci, dsti, weight);
		}

		bool _AddEdge(int srci, int dsti, const W& weight)
		{
			// 顶点不在图中,添加边失败
			if (srci == -1 || dsti == -1)
				return false;

			_matrix[srci][dsti] = weight;

			// 如果为无向图,则需要再添加一条dst->src的边
			if (!Directed)
			{
				_matrix[dsti][srci] = weight;
			}

			return true;
		}

		//bool TopologicalSort()
		//{
		//	if (!Directed)
		//		return false;

		//	// 记录顶点的入度大小
		//	std::vector<int> inDegreeSize(_vertexSet.size(), 0);
		//	// 记录顶点是否被访问过(即是否入过队或者栈)
		//	std::vector<bool> visited(_vertexSet.size(), false);

		//	// 顶点个数
		//	int n = static_cast<int>(_vertexSet.size());
		//	
		//	// 统计图中所有顶点的入度数
		//	for (int i = 0; i < n; i++)
		//	{
		//		for (int j = 0; j < n; j++)
		//		{
		//			if (_matrix[i][j] != W_MAX)
		//			{
		//				inDegreeSize[j]++;
		//			}
		//		}
		//	}

		//	// 将入度为零的顶点压入栈中
		//	std::stack<int> st;
		//	for (int i = 0; i < n; i++)
		//	{
		//		if (inDegreeSize[i] == 0 && !visited[i])
		//		{
		//			st.push(i);
		//			visited[i] = true;
		//		}
		//	}
		//	// 栈不为空时,将存储在栈中入度为零的顶点输出
		//	while (!st.empty())
		//	{
		//		int top = st.top();
		//		st.pop();
		//		// 输出栈顶元素
		//		std::cout << _vertexSet[top] << "--->";

		//		// 将以该顶点为弧尾的边对应顶点的入度减一
		//		for (int i = 0; i < n; i++)
		//		{
		//			if (top != i && _matrix[top][i] != W_MAX)
		//			{
		//				inDegreeSize[i]--;
		//			}
		//		}

		//		// 最后将没有入栈的入度为零的顶点入栈
		//		for (int i = 0; i < n; i++)
		//		{
		//			if (inDegreeSize[i] == 0 && !visited[i])
		//			{
		//				st.push(i);
		//				visited[i] = true;
		//			}
		//		}
		//	}

		//	// 判断是否还有顶点没有访问到
		//	for (int i = 0; i < n; i++)
		//	{
		//		if (inDegreeSize[i] != 0 && !visited[i])
		//		{
		//			printf("\nTopological sorting doesn't make sense\n");
		//			return false;
		//		}
		//	}

		//	printf("\nTopological sorting makes sense\n");
		//	return true;
		//}

		bool TopologicalSort()
		{
			if (!Directed)
				return false;

			// 记录顶点的入度大小
			std::vector<int> inDegreeSize(_vertexSet.size(), 0);
			// 顶点个数
			int n = static_cast<int>(_vertexSet.size());

			// 统计图中所有顶点的入度数
			for (int i = 0; i < n; i++)
			{
				for (int j = 0; j < n; j++)
				{
					if (_matrix[i][j] != W_MAX)
					{
						inDegreeSize[j]++;
					}
				}
			}

			// 将入度为零的顶点压入栈中
			std::stack<int> st;
			for (int i = 0; i < n; i++)
			{
				if (inDegreeSize[i] == 0)
				{
					st.push(i);
				}
			}
			// 栈不为空时,将存储在栈中入度为零的顶点输出
			while (!st.empty())
			{
				int top = st.top();
				st.pop();
				// 输出栈顶元素
				std::cout << _vertexSet[top] << "--->";

				// 将以该顶点为弧尾的边对应顶点的入度减一
				for (int i = 0; i < n; i++)
				{
					if (top != i && _matrix[top][i] != W_MAX)
					{
						// 如果有顶点的入度被减为零时,则该顶点入队或者入栈
						if (--inDegreeSize[i] == 0)
						{
							st.push(i);
						}
					}
				}
			}

			// 判断是否还有顶点没有访问到
			for (int i = 0; i < n; i++)
			{
				if (inDegreeSize[i] != 0)
				{
					printf("\nTopological sorting doesn't make sense\n");
					return false;
				}
			}

			printf("\nTopological sorting makes sense\n");
			return true;
		}
	};

	void TestGraph()
	{
		std::string aStr[] = {
			"V1","V2","V3","V4","V5","V6","V7","V8","V9"
		};

		Graph<std::string, int, INT_MAX,true> dg(aStr,sizeof(aStr)/sizeof(aStr[0]));
		
		dg.AddEdge("V1", "V3", 1);
		dg.AddEdge("V2", "V3", 1);
		dg.AddEdge("V2", "V4", 1);
		dg.AddEdge("V2", "V7", 1);
		dg.AddEdge("V3", "V4", 1);
		dg.AddEdge("V3", "V5", 1);
		dg.AddEdge("V4", "V5", 1);
		dg.AddEdge("V4", "V6", 1);
		dg.AddEdge("V4", "V7", 1);
		dg.AddEdge("V5", "V6", 1);
		dg.AddEdge("V5", "V9", 1);
		dg.AddEdge("V6", "V9", 1);
		dg.AddEdge("V7", "V8", 1);
		dg.AddEdge("V8", "V9", 1);

		dg.TopologicalSort();
	}
}

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

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

相关文章

小程序介绍和注册安装

小程序介绍和注册安装微信小程序介绍小程序特点其它平台小程序注册微信小程序开发帐号获取appidappid简介微信开发者工具安装创建一个小程序项目核心步骤微信开发者工具构成微信小程序介绍 简短定义&#xff1a;微信小程序是运行在微信APP中的一个程序。 常见小程序 行程码拼…

UDS诊断系列介绍11-3E服务

本文框架1. 系列介绍1.1 3E服务概述2. 3E服务请求与应答2.1 3E服务请求2.2 3E服务正响应2.3 3E服务否定响应3. Autosar系列文章快速链接1. 系列介绍 UDS&#xff08;Unified Diagnostic Services&#xff09;协议&#xff0c;即统一的诊断服务&#xff0c;是面向整车所有ECU的…

# 【笔记】大话设计模式21-23

【笔记】大话设计模式21-23 文章目录【笔记】大话设计模式21-23单例模式21.1 Example21.2 定义21.3 Show me the code一般单例代码(**懒汉模式**)静态初始化&#xff08;**饿汉模式**&#xff09;21.4 总结22 桥接模式22.1 Example22.2 定义22.3 Show me the code22.4 总结23 命…

Code for VeLO 1: Training Versatile Learned Optimizers by Scaling Up

Code for VeLO 1: Training Versatile Learned Optimizers by Scaling Up 这篇文章将介绍一下怎么用VeLO进行训练。 这篇文章基于https://colab.research.google.com/drive/1-ms12IypE-EdDSNjhFMdRdBbMnH94zpH#scrollToRQBACAPQZyB-&#xff0c;将介绍使用learned optimizer in…

入门力扣自学笔记230 C++ (题目编号:2293)

2293. 极大极小游戏 题目&#xff1a; 给你一个下标从 0 开始的整数数组 nums &#xff0c;其长度是 2 的幂。 对 nums 执行下述算法&#xff1a; 设 n 等于 nums 的长度&#xff0c;如果 n 1 &#xff0c;终止 算法过程。否则&#xff0c;创建 一个新的整数数组 newNums …

【Python百日进阶-数据分析】Day226 - plotly的仪表盘go.Indicator()

文章目录一、语法二、参数三、返回值四、实例4.1 Bullet Charts子弹图4.1.1 基本子弹图4.1.2 添加步骤和阈值4.1.3 自定义子弹4.1.4 多子弹4.2 径向仪表图4.2.1 基本仪表4.2.2 添加步骤、阈值和增量4.2.3 自定义仪表图4.3 组合仪表图4.3.1 组合仪表图4.3.2 单角量规图4.3.3 子弹…

Android 深入系统完全讲解(19)

技术的学习关键点 是什么&#xff1f;思路。 而我这里分享一个学习的经典路线&#xff0c;先厘清总框架&#xff0c;找到思路&#xff0c;然后再逐步击破。 这里关于音视频的就是&#xff1a; 总体分为几部分&#xff1a; 1 绘制 2 编解码格式 3 Android 平台的 FFmpeg 开源移…

Compressed Sensing——从零开始压缩感知

Problem 考虑一个线性方程组求解问题&#xff1a; Axb(1)A x b \tag{1}Axb(1) 其中&#xff0c;A∈RmnA \in\mathbb R^{m\times n}A∈Rmn&#xff0c;x∈Rn1x \in\mathbb R^{n\times 1}x∈Rn1&#xff0c;b∈Rm1b \in\mathbb R^{m\times 1}b∈Rm1且m≪nm \ll nm≪n 这是一个…

【C++11】—— lambda表达式

目录 一、lambda表达式的简介 二、lambda表达式的基本语法 三、lambda表达式的使用方法 四、lambda表达式的底层原理 一、lambda表达式的简介 lambda表达式就类似于仿函数&#xff0c;相比仿函数要更加的简洁&#xff0c;我们看一下下面的代码&#xff1a; //商品类 struct…

【项目实战】使用MybatisPlus乐观锁插件功能

一、背景 当要更新一条记录时&#xff0c;希望这条记录没有被别人更新&#xff0c;可以考虑使用MybatisPlus乐观锁插件功能来实现以上需求。 二、乐观锁介绍 2.1 乐观锁是什么&#xff1f; 乐观锁是一种乐观思想&#xff0c;即认为读多写少&#xff0c;遇到并发的可能性低&…

使用ASM框架创建ClassVisitor时遇到IllegalArgumentException的一种可能解决办法

背景 ASM是java语言中最为广泛使用的插装框架&#xff0c;其优点在于可以动态地在运行时改变java系统的行为&#xff0c;加入我们自己的逻辑。在软件测试领域应用广泛。但是其使用难度很高&#xff0c;一方面使用asm框架需要对java底层知识有较高的了解&#xff0c;另一方面网…

网页共享电脑屏幕与播放(带声音)

这次项目我们是写的一个课堂辅助软件的网页版&#xff0c;其中有一个功能感觉能作为我们项目的一个亮点&#xff0c;就是直播功能&#xff0c;在之前并没有写过这个东西。虽然现在这个功能还不知道怎么写&#xff0c;但是它的流程终归是利用视频流将本地的视频给共享出去&#…

Verilog:【8】基于FPGA实现SD NAND FLASH的SPI协议读写

碎碎念&#xff1a; 终于熬过了期末周&#xff0c;可以开始快乐的开发之旅了。 这一期作为一千粉后的首篇博客&#xff0c;由于之后项目会涉及到相关的部分&#xff0c;因此介绍的是使用FPGA实现SD NAND FLASH的读写操作&#xff0c;以雷龙科技提供的SD NAND FLASH样品为例&…

实证分析权重系数计算大全

在实际研究中&#xff0c;权重计算是一种常见的分析方法&#xff0c;需要结合数据的特征情况进行选择&#xff0c;比如数据之间的波动性是一种信息量&#xff0c;那么可考虑使用CRITIC权重法或信息量权重法&#xff1b;也或者专家打分数据&#xff0c;那么可使用AHP层次法或优序…

直观感受PromQL及其数据类型

由于PromQL内容较多&#xff0c;将内容分为三篇文章讲述&#xff1a; 一、直观感受PromQL及其数据类型 二、PromQL之选择器和运算符 三、PromQL之函数 想必都知道要使用Msql&#xff0c;必须会用SQL&#xff0c;同样要使用Prometheus 就要掌握PromQL&#xff08;Prometheus Que…

【链表】leetcode142.环形链表II(C/C++/Java/Js)

leetcode142.环形链表II1 题目2 思路2.1 判断链表是否有环--快慢指针法2.2 如果有环&#xff0c;如何找到这个环的入口2.3 补充3 代码3.1 C版本3.2 C版本3.3 Java版本3.4 JavaScript版本4 总结1 题目 题源链接 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个…

软测复习05:基于质量特征的测试

作者&#xff1a;非妃是公主 专栏&#xff1a;《软件测试》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录性能测试压力测试容量测试健壮性测试安全性测试可靠性测试恢复性测试协议一致性测试兼容性测试安装…

【数据结构】保姆级单链表教程(概念、分类与实现)

目录 &#x1f34a;前言&#x1f34a;&#xff1a; &#x1f348;一、链表概述&#x1f348;&#xff1a; 1.链表的概念及结构&#xff1a; 2.链表存在的意义&#xff1a; &#x1f353;二、链表的分类&#x1f353;&#xff1a; &#x1f95d;三、单链表的实现&#x1f…

​盘点几款国内外安全稳定的域名解析平台​

众所周知&#xff0c;有了域名后想建站使用&#xff0c;必须要先解析域名。域名使用注册商一般会提供域名解析服务&#xff0c;这虽然为用户提供了方便&#xff0c;但功能大多有限&#xff0c;使用第三方域名解析平台就成了非常必要的选择。今天&#xff0c;小编就为大家盘点几…

计算机视觉OpenCv学习系列:第四部分、键盘+鼠标响应操作

第四部分、键盘鼠标响应操作第一节、键盘响应操作1.键盘响应事件2.键盘响应3.代码练习与测试第二节、鼠标操作与响应1.鼠标事件与回调2.鼠标操作3.代码练习与测试学习参考第一节、键盘响应操作 键盘响应中有一个函数叫做waitKey&#xff0c;所有的获取键盘键值都是通过waitKey…