【数据结构与算法】最小生成树 | 最短路径

news2024/12/24 22:05:17

🌠作者:@阿亮joy.
🎆专栏:《数据结构与算法要啸着学》
🎇座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述


目录

    • 👉最小生成树👈
      • Kruskal算法
      • Prim算法
    • 👉最短路径👈
      • Dijkstra算法
      • BellmanFord算法
      • FloydWarshall算法
    • 👉总结👈

👉最小生成树👈

连通图:在无向图中,若从顶点 v1 到顶点 v2 有路径(直接相连或间接相连),则称顶点 v1 与顶点 v2 是连通的。如果图中任意一对顶点都是连通的,则称此图为连通图。

生成树:一个连通图的最小连通子图称作该图的生成树。有 n 个顶点的连通图的生成树有 n 个顶点和 n - 1 条边。

连通图中的每一棵生成树,都是原图的一个极大无环子图,即:从其中删去任何一条边,生成树就不再连通;反之,在其中引入任何一条新边,都会形成一条回路。

若连通图由 n 个顶点组成,则其生成树必含 n 个顶点和 n - 1 条边。因此构造最小生成树的准则有三条:

  1. 只能使用图中权值最小的边来构造最小生成树
  2. 只能使用恰好 n - 1 条边来连接图中的 n 个顶点
  3. 选用的 n - 1 条边不能构成回路
  4. 构成最小生成树的的边的权值和是最小的

构造最小生成树的方法:Kruskal 算法和 Prim 算法。这两个算法都采用了逐步求解的贪心策略

贪心算法:是指在问题求解时,总是做出当前看起来最好的选择。也就是说贪心算法做出的不是整体。最优的的选择,而是某种意义上的局部最优解。贪心算法不是对所有的问题都能得到整体最优解。

Kruskal算法

在这里插入图片描述


任给一个有 n 个顶点的连通网络 N = {V,E},首先构造一个由这 n 个顶点组成、不含任何边的图 G = {V,NULL},其中每个顶点自成一个连通分量,其次不断从 E 中取出权值最小的一条边(若有多条任取其一),若该边的两个顶点来自不同的连通分量,则将此边加入到 G 中。如此重复,直到所有顶点在同一个连通分量上为止。

核心:每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树。选边的过程需要判断构不构成回路,可以通过并查集来判断。

在这里插入图片描述

Kruskal 算法的实现思路:用优先级队列(小堆)存储图所有的边(注:需要为优先级队列定制一个表示边的类),然后选出 n - 1 条边,选边的时候需要通过并查集(并查集的代码可在之前博客查询)来判断当前选的边是否和之前所选的边构成回路。如果是,那么这条边不能选;如果不是,则可以选这条边。当选出 n - 1 条边,即可返回最小生成树的权值;若循环结束,则说明该图没有最小生成树,返回权值的默认值。

namespace matrix
{
	class Graph
	{
	typedef Graph<V, W, W_MAX, Direction> Self;
	// ...
	public:
		Graph() = default;	// 强制生成默认构造函数

		// 注:src和dst是顶点,srci和dsti是顶点下标
		// 因为找出最小生成树的过程只知道顶点的下标,所以需要增加一个通过顶点下标来构造边的子函数
		void AddEdge(const V& src, const V& dst, const W& w)
		{
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);
			_AddEdge(srci, dsti, w);
		}

		void _AddEdge(size_t srci, size_t dsti, const W& w)
		{
			_matrix[srci][dsti] = w;
			// 无向图
			if (Direction == false)
				_matrix[dsti][srci] = w;
		}

		struct Edge
		{
			size_t _srci;
			size_t _dsti;
			W _w;

			Edge(size_t srci, size_t dsti, const W& w)
				: _srci(srci)
				, _dsti(dsti)
				, _w(w)
			{}

			bool operator>(const Edge& eg) const
			{
				return _w > eg._w;
			}
		};

		// 注:只有无向图才有最小生成树
		W Kruskal(Self& minTree)
		{
			size_t n = _vertexs.size();

			// 将空间开好
			minTree._vertexs = _vertexs;
			minTree._indexMap = _indexMap;
			minTree._matrix.resize(n);
			for (size_t i = 0; i < n; ++i)
			{
				minTree._matrix[i].resize(n, W_MAX);
			}

			// 优先级队列默认是小堆(greater),因为比较的是边的权值,所以需要传第三模板类型参数
			priority_queue<Edge, vector<Edge>, greater<Edge>> minQueue;
			for (size_t i = 0; i < n; ++i)
			{
				for (int j = i + 1; j < n; ++j)
				{
					if (_matrix[i][j] != W_MAX)
					{
						minQueue.push(Edge(i, j, _matrix[i][j]));
					}
				}
			}

			// 选出n-1条边
			size_t size = 0;
			W total = W();
			UnionFindSet ufs(n);
			while (!minQueue.empty())
			{
				Edge min = minQueue.top();
				minQueue.pop();

				// 不在一个集合中表示不构成回路
				if (!ufs.Inset(min._srci, min._dsti))
				{
					// 查看所选的边
					cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
					minTree._AddEdge(min._srci, min._dsti, min._w);
					ufs.Union(min._srci, min._dsti);
					++size;
					total += min._w;

					// 选出n-1条边了
					if (size == n - 1)
						return total;
				}
				else
				{
					cout << "构成环的边:";
					cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
				}
			}

			// 该图没有最小生成树
			return W();
		}
	}

	void GraphMinTreeTest1()
	{
		const char* str = "abcdefghi";
		Graph<char, int> g(str, strlen(str));
		g.AddEdge('a', 'b', 4);
		g.AddEdge('a', 'h', 8);
		//g.AddEdge('a', 'h', 9);
		g.AddEdge('b', 'c', 8);
		g.AddEdge('b', 'h', 11);
		g.AddEdge('c', 'i', 2);
		g.AddEdge('c', 'f', 4);
		g.AddEdge('c', 'd', 7);
		g.AddEdge('d', 'f', 14);
		g.AddEdge('d', 'e', 9);
		g.AddEdge('e', 'f', 10);
		g.AddEdge('f', 'g', 2);
		g.AddEdge('g', 'h', 1);
		g.AddEdge('g', 'i', 6);
		g.AddEdge('h', 'i', 7);
		Graph<char, int> kminTree;
		cout << "Kruskal:" << g.Kruskal(kminTree) << endl << endl;
		kminTree.Print();
	}
}

在这里插入图片描述

在这里插入图片描述
注:图的最小生成树是不唯一的。

Prim算法

在这里插入图片描述
这里是引用
在这里插入图片描述

namespace matrix
{
	class Graph
	{
	typedef Graph<V, W, W_MAX, Direction> Self;
	// ...
	public:
		W Prim(Self& minTree, const V& src)
		{
			size_t n = _vertexs.size();

			// 将空间开好
			minTree._vertexs = _vertexs;
			minTree._indexMap = _indexMap;
			minTree._matrix.resize(n);
			for (size_t i = 0; i < n; ++i)
			{
				minTree._matrix[i].resize(n, W_MAX);
			}

			// 使用vector来表示集合X和集合Y,可以达到
			// O(1)时间复杂度来判断点在不在集合里
			// 也可以使用set来表示,但该场景下没有vector高效
			size_t srci = GetVertexIndex(src);
			vector<bool> X(n, false);
			vector<bool> Y(n, true);
			X[srci] = true;
			Y[srci] = false;

			// 从连接集合X和集合Y的边中选出权值最小的边
			priority_queue<Edge, vector<Edge>, greater<Edge>> minQueue;
			// 先把srci连接的边添加到队列中
			for (size_t i = 0; i < n; ++i)
			{
				if (_matrix[srci][i] != W_MAX)
					minQueue.push(Edge(srci, i, _matrix[srci][i]));
			}

			// 选出n-1条边
			size_t size = 0;
			W total = W();
			while (!minQueue.empty())
			{
				Edge min = minQueue.top();
				minQueue.pop();

				// 最小边的目标点也在X集合,则构成回路
				if (X[min._dsti])
				{
					cout << "构成回路的边:";
					cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
				}
				else
				{
					minTree._AddEdge(min._srci, min._dsti, min._w);
					cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
					X[min._dsti] = true;
					Y[min._dsti] = false;
					++size;
					total += min._w;
					// 已经选出n-1条边
					if (size == n - 1)
						return total;

					for (size_t i = 0; i < n; ++i)
					{
						// i与dsti相连且i不在集合Y中则边_matrix[min._dsti][i]添加进
						// 最小生成树中不会构成回路
						// 注:判断条件不加Y[i]也行,因为在上面也会判断是否构成回路
						// 不过加上Y[i]的效率较高一些
						if (_matrix[min._dsti][i] != W_MAX && Y[i])	
							minQueue.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
					}
				}
			}

			return W();	// 该图不存在最小生成树,返回默认值
		}
	}
}

下方的代码可以得到不同起点的 Prim 算法得到的最小生成树

for (size_t i = 0; i < strlen(str); ++i)
{
	cout << "Prim:" << g.Prim(pminTree, str[i]) << endl;
}

👉最短路径👈

路径:在图 G = (V, E) 中,若从顶点 vi 出发有一组边使其可到达顶点 vj,则称顶点 vi 到顶点 vj 的顶点序列为从顶点 vi 到顶点 vj 的路径。

路径长度:对于不带权的图,一条路径的路径长度是指该路径上的边的条数;对于带权的图,一条路径的路径长度是指该路径上各个边权值的总和。

最短路径问题:从在带权有向图 G 中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。单源最短路径问题是给点一个起点,求出起点到其他点的最短路径;而多源最短路径问题就是求出图中任意两点的最多路径。

Dijkstra算法

在这里插入图片描述
这里是引用
在这里插入图片描述
在这里插入图片描述

namespace matrix
{
	class Graph
	{
	typedef Graph<V, W, W_MAX, Direction> Self;
	// ...
	public:
		// Dijkstra的时间复杂度为O(N^2),空间复杂度为O(N)
		void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
		{
			size_t srci = GetVertexIndex(src);
			size_t n = _vertexs.size();
			// 初始状态
			dist.resize(n, W_MAX);
			pPath.resize(n, -1);
			dist[srci] = W();
			pPath[srci] = srci;

			// 已经确定最短路径的顶点集合S
			vector<bool> S(n, false);

			for (size_t i = 0; i < n; ++i)
			{
				// 选出未确定最短路径的顶点,用已经确定最短路径的顶点去
				// 更新其他顶点的最短路径
				int u = 0;	// u是已经确定最短路径的顶点(注:存在错位)
				W min = W_MAX;
				for (size_t j = 0; j < n; ++j)
				{
					if (S[j] == false && dist[j] < min)
					{
						u = j;
						min = dist[j];
					}
				}
				S[u] = true;

				// 松弛更新u连接顶点v  srci->u + u->v <  srci->v  更新
				// v是还未确定最短路径的顶点
				for (size_t v = 0; v < n; ++v)
				{
					// Dijkstra算法只能确定没有负权的图的最短路径
					// 原因是没有负权,当前已经确定的最短路径肯定
					// 是该顶点的最短路径。而存在负权的话,可能多
					// 走几个顶点的路径长度会比当前确定的最短路径
					// 的长度还要短。所以Dijkstra算法只能确定没有
					// 负权的图的最短路径
					if (S[v] == false && _matrix[u][v] != W_MAX
						&& dist[u] + _matrix[u][v] < dist[v])
					{
						dist[v] = dist[u] + _matrix[u][v];
						pPath[v] = u;
					}
				}
			}
		}

		// 打印最短路径
		void PrintShortPath1(const V& src, const vector<W>& dist, const vector<int>& pPath)
		{
			size_t srci = GetVertexIndex(src);
			size_t n = _vertexs.size();

			for (size_t i = 0; i < n; ++i)
			{
				if (i != srci)
				{
					// 生成从起点srci到顶点i的最短路径
					vector<int> path;
					size_t parenti = i;
					while (parenti != srci)
					{
						path.push_back(parenti);
						parenti = pPath[parenti];
					}
					path.push_back(srci);
					reverse(path.begin(), path.end());

					cout << "从起点" << src << "到顶点" << _vertexs[i] << "的最短路径为" << dist[i] << endl;
					cout << "路径为";
					for (auto index : path)
					{
						cout << _vertexs[index];
						if (index != *(path.end() - 1))
							cout << "->";
					}
					cout << endl << "---------------------------" << endl;
				}
			}
		}
	}
}

在这里插入图片描述
Dijkstra 算法无法解决有负权的图

void GraphDijkstraTest2()
{
	// 图中带有负权路径时,贪心策略则失效了。
	// 测试结果可以看到s->t->y之间的最短路径没更新出来
	const char* str = "sytx";
	Graph<char, int, INT_MAX, true> g(str, strlen(str));
	g.AddEdge('s', 't', 10);
	g.AddEdge('s', 'y', 5);
	g.AddEdge('t', 'y', -7);
	g.AddEdge('y', 'x', 3);

	vector<int> dist;
	vector<int> parentPath;
	g.Dijkstra('s', dist, parentPath);
	g.PrintShortPath('s', dist, parentPath);
}

在这里插入图片描述

Dijkstra 算法用已经确定最短路径的顶点来更新未确定最短路径的顶点。

BellmanFord算法

在这里插入图片描述
这里是引用
在这里插入图片描述

BellmanFord 算法的优化是通过队列来优化的,将更新的更短的路径入队列,从而更新包含该路径的路径。优化后,BellmanFord 算法的最好情况是 O(N^2),最坏情况是 O(N^3)。

在这里插入图片描述

负权回路问题

在这里插入图片描述

namespace matrix
{
	class Graph
	{
	// ...
	public:
		// BellmanFord算法的时间复杂度为O(N^3),空间复杂度为O(N)
		bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
		{
			size_t n = _vertexs.size();
			size_t srci = GetVertexIndex(src);
			// vector<W> dist,记录srci-其他顶点最短路径权值数组
			dist.resize(n, W_MAX);
			// vector<int> pPath 记录srci-其他顶点最短路径父顶点数组
			pPath.resize(n, -1);
			// 先更新srci->srci为缺省值
			dist[srci] = W();

			// 总体最多更新n轮
			for (size_t k = 0; k < n; ++k)
			{
				// 顶点i到顶点j 更新一次
				bool update = false;
				//cout << "更新第:" << k << "轮" << endl;
				for (size_t i = 0; i < n; ++i)
				{
					for (size_t j = 0; j < n; ++j)
					{
						// dist[i]为起点srci到i的距离,_matrix[i][j]为i到j的距离
						if (_matrix[i][j] != W_MAX && dist[i] + _matrix[i][j] < dist[j])
						{
							update = true;
							//cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl;
							dist[j] = dist[i] + _matrix[i][j];
							pPath[j] = i;
						}
					}
				}

				// 如果这个轮次中没有更新出更短路径,那么后续轮次就不需要再走了
				if (update == false)
				{
					break;
				}
			}

			// 还能更新就是存在负权回路
			for (size_t i = 0; i < n; ++i)
			{
				for (size_t j = 0; j < n; ++j)
				{
					// srci -> i + i ->j
					if (_matrix[i][j] != W_MAX && dist[i] + _matrix[i][j] < dist[j])
					{
						return false;
					}
				}
			}
			return true;	// 不存在负权回路
		}
	}

	void GraphBellmanFordTest()
	{
		const char* str = "syztx";
		Graph<char, int, INT_MAX, true> g(str, strlen(str));
		g.AddEdge('s', 't', 6);
		g.AddEdge('s', 'y', 7);
		g.AddEdge('y', 'z', 9);
		g.AddEdge('y', 'x', -3);
		g.AddEdge('z', 's', 2);
		g.AddEdge('z', 'x', 7);
		g.AddEdge('t', 'x', 5);
		g.AddEdge('t', 'y', 8);
		g.AddEdge('t', 'z', -4);
		g.AddEdge('x', 't', -2);

		// 存在负权回路的样例
		/*const char* str = "syztx";
		Graph<char, int, INT_MAX, true> g(str, strlen(str));
		g.AddEdge('s', 't', 6);
		g.AddEdge('s', 'y', 7);
		g.AddEdge('y', 'z', 9);
		g.AddEdge('y', 'x', -3);
		g.AddEdge('y', 's', 1); // 新增
		g.AddEdge('z', 's', 2);
		g.AddEdge('z', 'x', 7);
		g.AddEdge('t', 'x', 5);
		g.AddEdge('t', 'y', -8); //更改
		g.AddEdge('t', 'z', -4);
		g.AddEdge('x', 't', -2);*/

		vector<int> dist;
		vector<int> parentPath;
		if (g.BellmanFord('s', dist, parentPath))
			g.PrintShortPath('s', dist, parentPath);
		else
			cout << "存在负权回路,无法求出最短路径" << endl;
	}
}

在这里插入图片描述

在这里插入图片描述

FloydWarshall算法

这里是引用
在这里插入图片描述
在这里插入图片描述
即 Floyd 算法本质是三维动态规划,D[i][j][k] 表示从点 i 到点 j 只经过 0 到 k 个点最短路径,然后建立起转移方程,然后通过空间优化,优化掉最后一维度,变成一个最短路径的迭代算法,最后即得到所以点的最短路。

在这里插入图片描述

namespace matrix
{
	class Graph
	{
	// ...
	public:
		// FloydWarshall算法的时间复杂度为O(N^3),空间复杂度为O(N^2)
		void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath)
		{
			size_t n = _vertexs.size();
			vvDist.resize(n);
			vvpPath.resize(n);

			// 初始化权值和路径矩阵
			for (size_t i = 0; i < n; ++i)
			{
				vvDist[i].resize(n, W_MAX);
				vvpPath[i].resize(n, -1);
			}

			// 直接相连的边更新一下
			for (size_t i = 0; i < n; ++i)
			{
				for (size_t j = 0; j < n; ++j)
				{
					if (_matrix[i][j] != W_MAX)
					{
						vvDist[i][j] = _matrix[i][j];
						vvpPath[i][j] = i;
					}
					// 自己到自己的距离为默认值
					if (i == j)
						vvDist[i][j] = W();
				}
			}

			// abcdef:a {中间节点} f 或 b {中间节点} c
			// 最短路径的更新:i->{其他顶点}->j
			for (size_t k = 0; k < n; ++k)
			{
				for (size_t i = 0; i < n; ++i)
				{
					for (size_t j = 0; j < n; ++j)
					{
						// k作为的中间点尝试去更新i->j的路径
						// vvDist[i][j]是从i到j的最短路径的长度
						// vvpPath[i][j]中存的是从i到j路径上与j直接相连的顶点下标
						if (vvDist[i][k] != W_MAX && vvDist[k][j] != W_MAX
							&& vvDist[i][k] + vvDist[k][j] < vvDist[i][j])
						{
							vvDist[i][j] = vvDist[i][k] + vvDist[k][j];
							// 找出跟j相连的上一个邻接顶点
							// 如果k和j直接相连.上一个点就是k,vvpPath[k][j]存就是k
							// 如果k和j没有直接相连,k->...->x->j,vvpPath[k][j]存就是x
							// vvpPath[k][j]中存的是从k到j路径上与j直接相连的顶点下标
							vvpPath[i][j] = vvpPath[k][j];
						}
					}
				}

				// 打印权值和路径矩阵观察数据
				/*
				for (size_t i = 0; i < n; ++i)
				{
					for (size_t j = 0; j < n; ++j)
					{
						if (vvDist[i][j] == W_MAX)
						{
							//cout << "*" << " ";
							printf("%3c", '*');
						}
						else
						{
							//cout << vvDist[i][j] << " ";
							printf("%3d", vvDist[i][j]);
						}
					}
					cout << endl;
				}
				cout << endl;

				for (size_t i = 0; i < n; ++i)
				{
					for (size_t j = 0; j < n; ++j)
					{
						//cout << vvParentPath[i][j] << " ";
						printf("%3d", vvpPath[i][j]);
					}
					cout << endl;
				}
				cout << "=================================" << endl;
				*/
			}
		}
	}

	void FloydWarShallTest()
	{
		const char* str = "12345";
		Graph<char, int, INT_MAX, true> g(str, strlen(str));
		g.AddEdge('1', '2', 3);
		g.AddEdge('1', '3', 8);
		g.AddEdge('1', '5', -4);
		g.AddEdge('2', '4', 1);
		g.AddEdge('2', '5', 7);
		g.AddEdge('3', '2', 4);
		g.AddEdge('4', '1', 2);
		g.AddEdge('4', '3', -5);
		g.AddEdge('5', '4', 6);
		vector<vector<int>> vvDist;
		vector<vector<int>> vvParentPath;
		g.FloydWarshall(vvDist, vvParentPath);

		// 打印任意两点之间的最短路径
		for (size_t i = 0; i < strlen(str); ++i)
		{
			// 一维数组vvDist[i]是从顶点i到其他点的最短路径的距离
			// 一维数组vvParentPath[i]是从顶点i到其他点的最短路径
			g.PrintShortPath(str[i], vvDist[i], vvParentPath[i]);
			cout << endl;
		}
	}
}

在这里插入图片描述

在这里插入图片描述
总结

Dijkstra 算法只能求出没有负权的图的最短路径,时间复杂度为 O(N^3)。BellmanFord 算法能够求出有负权的图的最短路径,时间复杂度为 O(N^3)。但存在负权回路问题,任何算法都无法解决负权回路问题。Dijkstra 算法和 BellmanFord 算法都需要给点起点,求得的是从起点到其他点的最短路径;而 FloydWarshall 算法能够求出任意两点之间的最短路径,时间复杂度为 O(N^3)。图论中的重点内容是图重要的基本概念、邻接矩阵和邻接表的优缺点、广度优先遍历和深度优先遍历、最小生成树和最短路径等。

👉总结👈

本篇博客主要讲解了最小生成树的 Kruskal 算法和 Prim 算法以及最短路径的 Dijkstra 算法、BellmanFord 算法和 FloydWarshall 算法等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

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

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

相关文章

【图】邻接表存储图

目录 一、概念 图是什么 各种图的定义 二、图的存储结构 邻接矩阵 邻接表 代码实现邻接表存储图&#xff08;不含权重&#xff09; 一、概念 图是什么 图&#xff08;Graph)是由顶点的有穷非空集合和顶点之间边的集合组成&#xff0c;通常表示为:G(V,E)&#xff0c;其中…

Hystrix断路器执行原理

状态机 Hystrix断路器有三种状态,分别是关闭(Closed)、打开(Open)与半开(Half-Open),三种状态转化关系如下: Closed 断路器关闭:调用下游的请求正常通过Open 断路器打开:阻断对下游服务的调用,直接走 Fallback 逻辑Half-Open 断路器处于半开状态:SleepWindowInMi…

第 14 章python学习知识记录(一)

文章目录前言14.1 numpy的使用14.1.1 数字运算14.1.2 N维数组14.1.3 矩阵运算与广播14.1.4 元素访问14.2 Matplotlib的使用14.2.1 绘制简单图形14.2.2 绘制复杂图形14.2.3 显示图片14.3 os函数14.3.1 获取文件路径14.3.2 路径的基本操作14.4 tqdm的使用14.4.1 tqdm的导入和使用…

docker搭建hadoop和hive集群

一、安装docker并生成相关的镜像&#xff08;1&#xff09;安装docker安装docker教程https://www.runoob.com/docker/centos-docker-install.html只要在终端输入&#xff1a;sudo docker run hello-world后出现如下图的内容就证明安装docker成功了&#xff08;2&#xff09;拉取…

让HTTPS、SSH 共享端口的——工具SSLH

目录 安装 SSLH 配置 Apache 或 Nginx Web 服务器 配置 SSLH 测试 安装 SSLH sudo apt-get install sslh 配置 Apache 或 Nginx Web 服务器 编辑 Web 服务器&#xff08;nginx 或 apache&#xff09;配置文件并找到以下行&#xff1a; listen 443 ssl; 将其修改为&…

Uni-App 如何实现消息推送功能?

原文链接&#xff1a;Uni-App 如何实现消息推送功能&#xff1f; 前言 这里用的是uni-app自带的UniPush1.0&#xff08;个推服务&#xff09;&#xff0c;所以只针对UniPush1.0介绍实现步骤。 建议查阅的文章&#xff1a; UniPush 1.0 使用指南Unipush 常见问题 当然现在已…

Flink day01

Flink简介 Flink起源于Stratosphere项目&#xff0c;Stratosphere是在2010~2014年由3所地处柏林的大学和欧洲的一些其他的大学共同进行的研究项目&#xff0c;2014年4月Stratosphere的代码被复制并捐赠给了Apache软件基金会&#xff0c;参加这个孵化项目的初始成员是Stratosphe…

Flutter项目实战

1.Androidstudio 获取dart支持才会出现 下图&#xff0c;才可以单独运行 2.需要 flutter pub get 3.Android Studio 中使用 FlutterJsonBeanFactory 插件 注意点&#xff1a;需要保证该 Android Studio 窗口下是一个完整的Flutter项目&#xff08;窗口下有且仅有一个Flutter项目…

录音软件哪个好用?推荐3款亲测好用的录音软件

很多小伙伴不知道电脑如何录制声音&#xff1f;其实电脑录制声音很简单&#xff0c;借助一款好用录音软件&#xff0c;就可以轻松录制。那你知道录音软件哪个好用吗&#xff1f;小编平常的工作经常需要使用到录音软件。今天就给大家推荐3款亲测好用的录音软件&#xff0c;感兴趣…

k8s核心资源Service

1、介绍为一组pod&#xff08;一次部署&#xff09;统一暴露一个ip和端口&#xff0c;供外界访问。2、集群内对外统一暴露kubectl expose deploy <部署名> --port<对外暴露端口> --target-port<容器内部端口>kubectl expose deploy mynginx --port8000 --tar…

torch_geometric--Convolutional Layers

torch_geometric–Convolutional Layers 卷积层 MessagePassing 式中口口口表示可微的排列不变函数&#xff0c;例如: sum, mean, min, max 或者 mul 和 γΘ\gamma_{\Theta}γΘ​和ϕΘ\phi_{\Theta}ϕΘ​表示可微函数&#xff0c;例如 MLPs. 请参见这里的附带教程。 参数…

JDBC-BasicDAO

引入 之前的sql语句是固定的只能通过参数传入&#xff0c;要写那么多方法肯定不好弄 这就引出了DAO 这里是整个流程示意图 最下面开始说 就是mysql库的每一张表对应一个JavaBean 右边是我们获取和关闭连接的工具类 上面XXXDAO的意思是 每一个表&#xff0c;都有一个与之对应的D…

高压放大器在镓基液态金属微型马达驱动实验研究中的应用

实验名称&#xff1a;高压放大器在镓基液态金属微型马达驱动实验研究中的应用 研究方向&#xff1a;新型材料 测试目的&#xff1a; 微/纳马达虽然是一种以实际应用为基础的动力装置&#xff0c;但其在科学研究方面的价值也尤为重要。在微/纳米尺度下&#xff0c;它可以接受能量…

【测试如何学代码?】学习代码的最佳实践经验分享

为什么要写这篇&#xff1f; 经常在群里看到大家问&#xff1a;该选择哪门语言&#xff1f;哪门语言有钱途&#xff1f; 其实&#xff0c;不管哪门语言&#xff0c;只要深入学好了都不会差&#xff0c;当然&#xff0c;我们选择语言最好还是要和自己的技术方向及职业发展相匹配…

基于php的高校社团信息管理系统

摘 要社团是由高校用户依据兴趣爱好自愿组成&#xff0c;按照章程自主开展活动的用户组织。高校社团是实施素质教育的重要途径和有效方式&#xff0c;在加强校园文化建设、提高用户综合素质、引导用户适应社会、促进用户交流等方面发挥着重要作用&#xff0c;是新形势下有效凝聚…

Android studio:Could not find method compile() for arguments 问题解决及两种解决方法探讨延伸

Could not find method compile() for arguments 问题全称 Could not find method compile() for arguments [org.tensorflow:tensorflow-lite:] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler. 如图 解决方法1(简单) …

[数据结构基础]排序算法第四弹 -- 归并排序和计数排序

目录 一. 归并排序 1.1 归并排序的实现思想 1.2 归并排序的递归实现 1.2.1 归并排序递归实现的思想 1.2.2 归并排序递归实现的代码 1.3 归并排序的非递归实现 1.3.1 归并排序非递归实现的思想 1.3.2 归并排序非递归实现的代码 1.4 归并排序的时间复杂度分析 二. 计数排…

c++之模板【进阶版】

前言 对于泛型编程&#xff0c;学好模板这节内容是非常有必要的。在前面学习的STL中&#xff0c;由于模板的可重性和扩展性&#xff0c;几乎所有的代码都采用了模板类和模板函数的方式&#xff0c;这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。 模板初阶 …

Hugging face教程-使用速查表-快速入门

Hugging face笔记 course url&#xff1a;https://huggingface.co/course/chapter5/8?fwpt 函数详细情况&#xff1a;https://huggingface.co/docs/transformers/main_classes/pipelines#transformers.TokenClassificationPipeline 基础掌握transformers和datasets&#xf…

软件测试 利器 | AppCrawler 自动遍历测试工具实践(一)

本文为霍格沃兹测试学院学院学员课程学习笔记&#xff0c;系统学习交流文末加群。 AppCrawler 是由霍格沃兹测试学院校长思寒开源的一个项目,通过名字我们大概也能猜出个方向&#xff0c;Crawler 是爬虫的意思&#xff0c;App 的爬虫&#xff0c;遍历 App &#xff1a; 官方 G…