【高阶数据结构】图--邻接矩阵、邻接表、BFS、DFS、Kruskal、Prime

图--邻接矩阵、邻接表、BFS、DFS、Kruskal、Prime

  • 一、图的概述
    • 1、概述(纯理论部分)
    • 2、邻接矩阵(实现一个添加边的图)
      • (1)思路介绍
      • (2)代码部分
      • (3)测试部分
    • 3、邻接表(只实现出度表)
      • (1)思路介绍
      • (2)代码部分
      • (3)测试部分
  • 二、图的遍历
    • 1、图的广度优先遍历
      • (1)简介
      • (2)代码
      • (3)测试用例及测试结果
      • (4)面试问答题
        • i、题目描述
        • ii、思路
        • iii、代码
        • iv、测试结果
    • 2、图的深度优先遍历
      • (1)简介
      • (2)代码
      • (3)测试用例及测试结果
    • 3、致命问题:假如说是图本身就不联通,那么DFS和BFS怎么办?
  • 三、图的最小生成树
    • 1、概念
    • 2、Kruskal算法
      • (1)简介
      • (2)代码实现
      • (3)测试用例及测试结果
    • 3、prime算法
      • (1)简介
      • (2)代码
      • (3)测试用例及测试结果


一、图的概述

1、概述(纯理论部分)

图是由顶点集合及顶点间的关系组成的一种数据结构:G = (V, E),其中:
顶点集合V = {x|x属于某个数据对象集}是有穷非空集合
E = {(x,y)|x,y属于V}或者E = {<x, y>|x,y属于V && Path(x, y)}是顶点间关系的有穷集合,也叫做边的集合。
(x, y)表示x到y的一条双向通路,即(x, y)是无方向的;Path(x, y)表示从x到y的一条单向通路,即Path(x, y)是有方向的。
顶点和边:图中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边,图中的第k条边记作ek,ek = (vi,vj)或<vi,vj>。
有向图和无向图:在有向图中,顶点对<x, y>是有序的,顶点对<x,y>称为顶点x到顶点y的一条边(弧),<x, y>和<y, x>是两条不同的边,比如下图G3和G4为有向图。在无向图中,顶点对(x, y)是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)和(y,x)是同一条边,比如下图G1和G2为无向图。注意:无向边(x, y)等于有向边<x, y>和<y, x>。

在这里插入图片描述
完全图:在有n个顶点的无向图中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边,则称此图为无向完全图,比如上图G1;在n个顶点的有向图中,若有n * (n-1)条边,即任意两个顶点之间有且仅有方向相反的边,则称此图为有向完全图,比如上图G4。
邻接顶点:在无向图中G中,若(u, v)是E(G)中的一条边,则称u和v互为邻接顶点,并称边(u,v)依附于顶点u和v;在有向图G中,若<u, v>是E(G)中的一条边,则称顶点u邻接到v,顶点v邻接自顶点u,并称边<u, v>与顶点u和顶点v相关联。
顶点的度:顶点v的度是指与它相关联的边的条数,记作deg(v)。在有向图中,顶点的度等于该顶点的入度与出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度是以v为起始点的有向边的条数,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)。注
意:对于无向图,顶点的度等于该顶点的入度和出度,即dev(v) = indev(v) = outdev(v)。
路径:在图G = (V, E)中,若从顶点vi出发有一组边使其可到达顶点vj,则称顶点vi到顶点vj的顶点序列为从顶点vi到顶点vj的路径。
路径长度:对于不带权的图,一条路径的路径长度是指该路径上的边的条数;对于带权的图,一条路径的路径长度是指该路径上各个边权值的总和。

2、邻接矩阵(实现一个添加边的图)

(1)思路介绍

我们既然要实现有边有顶点的图,那么必然我们需要先创建一个顶点的集合,顶点和坐标的映射关系以及邻接矩阵的。

我们首先需要实现的是构造函数用的是手动添加边,也就是将我们的顶点集合和邻接矩阵进行扩容至相对应的大小,并且用顶点和坐标的映射关系在进行扩容的过程中进行添加。其次我们就需要添加边了,也就是我们先要得到边的下标(这里直接用顶点和下标关系这个成员函数进行返回即可),无向图就加两次,有向图只用加当前的边即可。最后进行测试即可。

(2)代码部分

// Graph.h
#pragma once
#include <iostream>
#include <vector>
#include <map>

namespace matrix
{
	template<class V, class W, W MAX_W = INT_MAX, bool Direct = false>
	class Graph
	{
	private:
		std::vector<V> _vertex; // 顶点集合
		std::map<V, int> _indexMap; // 顶点映射下标关系
		std::vector<std::vector<W>> _matrix; // 邻接矩阵
	public:
		// 手动添加边
		Graph(const V* a, size_t n) // 边和大小
		{
			_vertex.reserve(n); // 先将顶点扩大到n个大小
			for (size_t i = 0; i < n; i++)
			{
				_vertex.push_back(a[i]); // 插入这条边
				_indexMap[a[i]] = i; // 映射关系
			}
			_matrix.resize(n); // 先将这第一行给设置n个大小
			for (int i = 0; i < _matrix.size(); i++)
			{
				_matrix[i].resize(n, MAX_W); // 之后的每一行都进行扩容
			}
		}
		// 得到顶点下标
		size_t GetIndex(const V& v)
		{
			auto it = _indexMap.find(v);
			if (it != _indexMap.end())
			{
				return it->second;
			}
			else
			{
				return -1;
			}
		}
		// 添加边
		void AddEdge(const V& src, const V& dst, const W& w)
		{
			size_t srci = GetIndex(src);
			size_t dsti = GetIndex(dst);
			_matrix[srci][dsti] = w;
			// 无向图(要添加两次)
			if (Direct == false)
			{
				_matrix[dsti][srci] = w;
			}
			// return ;
		}
		// 打印
		void Print()
		{
			// 先打印顶点
			for (int i = 0; i < _vertex.size(); i++)
			{
				std::cout << "[" << i << "]" << _vertex[i] << std::endl;
			}
			std::cout << std::endl;
			// 再打印矩阵
			// 横坐标
			std::cout << "  ";
			for (int i = 0; i < _matrix.size(); i++)
			{
				std::cout << i << " ";
			}
			std::cout << std::endl;
			for (int i = 0; i < _matrix.size(); i++)
			{
				std::cout << i << " ";
				for (int j = 0; j < _matrix[i].size(); j++)
				{
					if (_matrix[i][j] == MAX_W)
					{
						std::cout << "* ";
					}
					else
					{
						std::cout << _matrix[i][j] << " ";
					}
				}
				std::cout << std::endl;
			}
			std::cout << std::endl;
		}
	};
	void TestGraph1()
	{
		Graph<char, int, INT_MAX, true> g("0123", 4);
		g.AddEdge('0', '1', 1);
		g.AddEdge('0', '3', 4);
		g.AddEdge('1', '3', 2);
		g.AddEdge('1', '2', 9);
		g.AddEdge('2', '3', 8);
		g.AddEdge('2', '1', 5);
		g.AddEdge('2', '0', 3);
		g.AddEdge('3', '2', 6);
		g.Print();
	}
}

(3)测试部分

在这里插入图片描述

3、邻接表(只实现出度表)

(1)思路介绍

(2)代码部分

namespace link_table
{
	template<class W>
	struct Edge
	{
		int _dsti; // 出度的目标点
		W _w;     // 权值
		Edge<W>* _next; // 链接下一个指针

		Edge(int dsti, const W& w)
			: _dsti(dsti)
			, _w(w)
			, _next(nullptr)
		{}
	};
	template<class V, class W, bool Direct = false>
	class Graph
	{
		typedef Edge<W> Edge;
	private:
		std::vector<V> _vertex;		// 顶点集合
		std::map<V, int> _indexMap; // 顶点映射下标关系
		std::vector<Edge*> _tables; // 邻接表--类似哈希表
	public:
		// 手动添加边
		Graph(const V* a, size_t n) // 边和大小
		{
			_vertex.reserve(n); // 先将顶点扩大到n个大小
			for (size_t i = 0; i < n; i++)
			{
				_vertex.push_back(a[i]); // 插入这条边
				_indexMap[a[i]] = i; // 映射关系
			}
			_tables.resize(n, nullptr);
		}
		// 得到顶点下标
		size_t GetIndex(const V& v)
		{
			auto it = _indexMap.find(v);
			if (it != _indexMap.end())
			{
				return it->second;
			}
			else
			{
				return -1;
			}
		}
		// 添加边
		void AddEdge(const V& src, const V& dst, const W& w)
		{
			size_t srci = GetIndex(src);
			size_t dsti = GetIndex(dst);

			Edge* eg = new Edge(dsti, w); // 先new一个结点
			eg->_next = _tables[srci];
			_tables[srci] = eg;

			// 对于无向图来讲
			if (Direct == false)
			{
				Edge* eg = new Edge(srci, w); // 先new一个结点
				eg->_next = _tables[dsti];
				_tables[dsti] = eg;
			}
		}
		// 打印
		void Print()
		{
			// 先打印顶点
			for (int i = 0; i < _vertex.size(); i++)
			{
				std::cout << "[" << i << "]" << _vertex[i] << std::endl;
			}
			std::cout << std::endl;
			for (int i = 0; i < _tables.size(); i++)
			{
				std::cout << _vertex[i] << "[" << i << "]->";
				Edge* cur = _tables[i];
				while (cur)
				{
					std::cout << _vertex[cur->_dsti] << "[" << cur->_dsti << "]" << "w:" << cur->_w << "->";
					cur = cur->_next;
				}
				std::cout << "nullptr" << std::endl;
			}
			std::cout << std::endl;
		}
	};
	void TestGraph1()
	{
		std::string a[] = { "张三", "李四", "王五", "赵六" };
		Graph<std::string, int, false> g1(a, 4);
		g1.AddEdge("张三", "李四", 100);
		g1.AddEdge("张三", "王五", 200);
		g1.AddEdge("王五", "赵六", 30);
		/*Graph<char, int, true> g("0123", 4);
		g.AddEdge('0', '1', 1);
		g.AddEdge('0', '3', 4);
		g.AddEdge('1', '3', 2);
		g.AddEdge('1', '2', 9);
		g.AddEdge('2', '3', 8);
		g.AddEdge('2', '1', 5);
		g.AddEdge('2', '0', 3);
		g.AddEdge('3', '2', 6);*/
		g1.Print();
	}
}

(3)测试部分

有向图:
在这里插入图片描述
无向图:
在这里插入图片描述

二、图的遍历

1、图的广度优先遍历

(1)简介

首先先明白一下下面的概念性的问题,不过多赘述,直接看简介即可
在这里插入图片描述

我们先利用两个具象的图,来进行创建两个队列,一个队列用来进行进入和弹出,另一个数组用来记录是否被访问过了,防止重复访问。
在这里插入图片描述

(2)代码

		// BFS遍历
		void GraphBFS(const V& v)
		{
			size_t srci = GetIndex(v);

			// 创建一个队列(BFS深度优先遍历)和一个vector数组(用来判断是否是已经被访问过了)
			std::queue<int> q;
			std::vector<bool> visited(_vertex.size(), false);
			q.push(srci); // 先push进一个队列中
			visited[srci] = true;
			while (!q.empty())
			{
				int front = q.front(); // 先取出对列头
				q.pop(); // 弹出头
				std::cout << front << _vertex[front] << std::endl;
				// 遍历一下这个数组当不等于MAX_W的就是链接的,那么就将它们push进队列中
				for (int i = 0; i < _vertex.size(); i++)
				{
					if (_matrix[front][i] != MAX_W) // 那一列的数值不等于MAX_W的话就push并标记
					{
						if (visited[i] == false)
						{
							q.push(i);
							visited[i] = true;
						}
					}
				}
			}
			std::cout << std::endl;
		}

(3)测试用例及测试结果

	void TestGraphDBFS()
	{
		std::string a[] = { "张三", "李四", "王五", "赵六", "周七" };
		Graph<std::string, int> g1(a, sizeof(a) / sizeof(std::string));
		g1.AddEdge("张三", "李四", 100);
		g1.AddEdge("张三", "王五", 200);
		g1.AddEdge("王五", "赵六", 30);
		g1.AddEdge("王五", "周七", 30);
		g1.Print();
		g1.GraphBFS("张三");
	}

在这里插入图片描述

(4)面试问答题

i、题目描述

在这里插入图片描述

ii、思路

一度好友那么就是我们用一个LevelSize来控制一下每层我们进去的个数即可。

iii、代码
		// BFS遍历
		void GraphBFS(const V& v)
		{
			size_t srci = GetIndex(v);

			// 创建一个队列(BFS深度优先遍历)和一个vector数组(用来判断是否是已经被访问过了)
			std::queue<int> q;
			std::vector<bool> visited(_vertex.size(), false);
			int LevelSize = 1; // 控制每次出的个数
			q.push(srci); // 先push进一个队列中
			visited[srci] = true;
			while (!q.empty())
			{
				for (int i = 0; i < LevelSize; i++)
				{
					int front = q.front(); // 先取出对列头
					q.pop(); // 弹出头
					std::cout << front << _vertex[front] << " ";
					// 遍历一下这个数组当不等于MAX_W的就是链接的,那么就将它们push进队列中
					for (int i = 0; i < _vertex.size(); i++)
					{
						if (_matrix[front][i] != MAX_W) // 那一列的数值不等于MAX_W的话就push并标记
						{
							if (visited[i] == false)
							{
								q.push(i);
								visited[i] = true;
							}
						}
					}
				}
				std::cout << std::endl;
				LevelSize = q.size();
			}
			std::cout << std::endl;
		}
iv、测试结果

在这里插入图片描述

2、图的深度优先遍历

(1)简介

一句话来概括:一条路走到黑,走不通了再回溯,直到回溯到第一个点发现第一个点都没的往外走了则结束!

在这里插入图片描述

(2)代码

		// 深度遍历子函数
		void _GraphDFS(size_t srci, std::vector<bool>& visited)
		{
			std::cout << srci << _vertex[srci] << std::endl;
			visited[srci] = true;
			for (size_t i = 0; i < _vertex.size(); i++)
			{
				if (_matrix[srci][i] != MAX_W && visited[i] == false)
				{
					_GraphDFS(i, visited);
				}
			}
		}
		// 深度遍历--用递归解决
		void GraphDFS(const V& src)
		{
			size_t srci = GetIndex(src);
			std::vector<bool> visited(_vertex.size(), false);

			_GraphDFS(srci, visited);
		}

(3)测试用例及测试结果

	void TestGraph1()
	{
		Graph<char, int, INT_MAX, true> g("0123", 4);
		g.AddEdge('0', '1', 1);
		g.AddEdge('0', '3', 4);
		g.AddEdge('1', '3', 2);
		g.AddEdge('1', '2', 9);
		g.AddEdge('2', '3', 8);
		g.AddEdge('2', '1', 5);
		g.AddEdge('2', '0', 3);
		g.AddEdge('3', '2', 6);
		g.Print();
	}
	void TestGraphDBFS()
	{
		std::string a[] = { "张三", "李四", "王五", "赵六", "周七" };
		Graph<std::string, int> g1(a, sizeof(a) / sizeof(std::string));
		g1.AddEdge("张三", "李四", 100);
		g1.AddEdge("张三", "王五", 200);
		g1.AddEdge("王五", "赵六", 30);
		g1.AddEdge("王五", "周七", 30);
		g1.Print();
		g1.GraphBFS("张三");
		g1.GraphDFS("张三");
	}

在这里插入图片描述

3、致命问题:假如说是图本身就不联通,那么DFS和BFS怎么办?

如下图所示,假如说是下面这种情况,上面已有的代码中FHI肯定是没有办法被遍历到的!
在这里插入图片描述
还记得我们有一个vector<bool>数组吗?我们只需要在DFS和BFS结束后了遍历一遍这个数组,false的值再输出不就好了吗?
DFS新增(BFS新增的也是一样的):
在这里插入图片描述

测试用例:
在这里插入图片描述

三、图的最小生成树

1、概念

连通图:在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任意一对顶点都是连通的,则称此图为连通图。
强连通图:在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到vi的路径,则称此图是强连通图。
生成树:一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条边。
最小生成树:构成生成树这些边加起来的权值是最小的。

连通图中的每一棵生成树,都是原图的一个极大无环子图,即:从其中删去任何一条边,生成树就不在连通;反之,在其中引入任何一条新边,都会形成一条回路。若连通图由n个顶点组成,则其生成树必含n个顶点和n-1条边。

因此构造最小生成树的准则有三条:

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

核心算法思想:贪心算法。

2、Kruskal算法

(1)简介

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

大白话就是每次都选最小的边呗,那么我们用一个优先级队列(由小到大排列即可),并且我们为了控制不构成回路的话我们就用我们前面写的并查集,每次取出来一个数的时候就放到同一个并查集中,假如说下一个要取的目标和源数不在这个集合中,那么就不联通就可以添加进最小生成树中。

(2)代码实现

		// 先构成一条边
		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& e) const
			{
				return _w > e._w;
			}
		};
		// Kruskal算法
		W Kruskal(Self& minTree)
		{
			 先初始化一下minTree
			size_t n = _vertex.size();
			minTree._vertex = _vertex;
			minTree._indexMap = _indexMap;
			minTree._matrix.resize(n);
			for (size_t i = 0; i < n; i++)
			{
				minTree._matrix[i].resize(n, MAX_W);
			}

			// 设置一个优先级队列
			std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> minque;
			// 一个一个先都存放进去
			for (size_t i = 0; i < n; i++)
			{
				for (size_t j = 0; j < n; j++)
				{
					if (i < j && _matrix[i][j] != MAX_W) // i<j是因为保证只存放矩阵一半,保证没有重复
					{
						minque.push(Edge(i, j, _matrix[i][j]));
					}
				}
			}
			
			// 接下来用来实现算法逻辑,也就是找最小的边加进去同时
			// 满足不能在同一个并查集中,也就是先存放到并查集中
			// 构建一个并查集
			UnionFindSet ufs(n);
			// 选出n条边用来和后面的prime算法做比较
			int size = 0;
			W totalW = W(); // 权值 
			while (!minque.empty()) // 优先级对列不为空的时候
			{
				Edge min = minque.top(); // 先把最小的这条边给取出来
				minque.pop(); // 弹出

				if (!ufs.IsSet(min._srci, min._dsti)) // 判断是不是在一个集合中
				{
					std::cout << _vertex[min._srci] << "->" << _vertex[min._dsti] << ":" << min._w << std::endl;
					minTree._AddEdge(min._srci, min._srci, min._w);
					ufs.Union(min._srci, min._dsti);
					++size;
					totalW += min._w;
				}
				else
				{
					std::cout << "构成回路:";
					std::cout << _vertex[min._srci] << "->" << _vertex[min._dsti] << ":" << min._w << std::endl;
				}
			}
			if (size == n - 1)
			{
				return totalW;
			}
			else
			{
				return W();
			}
			//return 0;
		}

(3)测试用例及测试结果

	void TestGraphMinTree()
	{
		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;
		std::cout << "Kruskal:" << g.Kruskal(kminTree) << std::endl;
		kminTree.Print();
	}

下面是代码需要更改的地方:
在这里插入图片描述
在这里插入图片描述
测试结果:
在这里插入图片描述

3、prime算法

(1)简介

大白话就是加点法,我们在X集合中不断加入最小的边,再Y集合中不断删去已经加入的目标点,那么就不会变成环状了,我们使用一个优先级队列进行维护每次取最小的,只需要判断是不是构成环即可。
所用到的算法思想是:贪心策略。

在这里插入图片描述

(2)代码

		// Prime算法
		W Prim(Self& minTree, const W& src)
		{
			size_t srci = GetIndex(src);
			size_t n = _vertex.size();

			minTree._vertex = _vertex;
			minTree._indexMap = _indexMap;
			minTree._matrix.resize(n);
			for (size_t i = 0; i < n; ++i)
			{
				minTree._matrix[i].resize(n, MAX_W);
			}
			// 定义两个vector数组
			std::vector<bool> X(n, false);
			std::vector<bool> Y(n, true);
			X[srci] = true; // X集合中的该坐标位置为真 -- 表明从该集合添加
			Y[srci] = false; // Y集合中的该坐标位置为假 -- 表明从该集合中删除掉
			// 从X->Y集合中连接的边里面选出最小的边
			std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> minque;
			// 遍历一下数组将这个srci添加进去
			for (int i = 0; i < n; i++)
			{
				if (_matrix[srci][i] != MAX_W)
				{
					minque.push(Edge(srci, i, _matrix[srci][i]));
				}
			}
			// 开始进行prime算法
			W totalW = W();  // 权值
			size_t size = 0; // 用来记录是否到n-1了
			while (!minque.empty())
			{
				// 取出最小的那个元素
				Edge min = minque.top();
				minque.pop();
				// 判断是否为环 -- 即判断是否是在X这个集合当中
				// 最小边的目标点是否是在X集合中
				if (X[min._dsti] == true)
				{
					std::cout << "形成环:";
					std::cout << _vertex[min._srci] << "->" << _vertex[min._dsti] << ":" << min._w << std::endl;
				}
				else
				{
					minTree._AddEdge(min._srci, min._dsti, min._w);
					std::cout << _vertex[min._srci] << "->" << _vertex[min._dsti] << ":" << min._w << std::endl;
					X[min._dsti] = true; // 表示已经加到prime刚好遍历的边当中了
					Y[min._dsti] = false; // 表明这个Y元素中的边已经被取消掉了
					++size;
					totalW += min._w;
					if (size == n - 1)
					{
						break;
					}
					for (size_t i = 0; i < n; i++)
					{
						if (Y[i] && _matrix[min._dsti][i] != MAX_W)
						{
							minque.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
						}
					}
				}
			}
			if (size == n - 1) return totalW;
			else return W();
		}

(3)测试用例及测试结果

	void TestGraphMinTree()
	{
		const char* str = "abcdefghi";
		Graph<char, int> g(str, strlen(str));
		g.AddEdge('a', 'b', 4);
		g.AddEdge('a', 'h', 8);
		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;
		std::cout << "Kruskal:" << g.Kruskal(kminTree) << std::endl;
		kminTree.Print();*/
		Graph<char, int> pminTree;
		std::cout << "Prime:" << g.Prim(pminTree, 'a') << std::endl;
		pminTree.Print();
	}

在这里插入图片描述

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

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

相关文章

pytest教程-40-钩子函数-pytest_runtest_call

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_runtest_setup钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_runtest_call钩子函数的使用方法。 pytest_runtest_call 钩子函数在 pytest 调用测试函数&#xff08;即测试用…

JAVA栈相关习题3

1.将递归转化为循环 比如&#xff1a;逆序打印链表 // 递归方式void printList(Node head){if(null ! head){printList(head.next);System.out.print(head.val " ");}} // 循环方式void printList(Node head){if(nullhead){return;}Stack<Node> snew Stack<…

将大概的流程具体还是看源码

之前看源码的时候呢没有文字整理&#xff0c;想来还是写一个大概的流程吧&#xff0c;具体是无法用文字描述 spring源码真的yyds&#xff0c;数据结构 反射 父子类 接口…玩得溜到飞起 博大精深呐 后期不断喜欢ing&#xff01; springApplication.run方法 获取了一个Configu…

STC8增强型单片机开发——库函数

一、使用库函数点灯 导入库函数。 下载STC8H的库函数&#xff1a;&#x1f4ce;STC8G-STC8H-LIB-DEMO-CODE_2023.07.17_优化版.zip 来到库函数的目录下&#xff0c;拷贝以下文件&#xff1a; Config.hType_def.hGPIO.hGPIO.c 新建项目&#xff0c;将拷贝的4个文件放到项目目录…

【管理咨询宝藏96】企业数字化转型的中台战略培训方案

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏96】企业数字化转型的中台战略培训方案 【格式】PDF版本 【关键词】SRM采购、制造型企业转型、数字化转型 【核心观点】 - 数字化转型是指&…

代码审计-php篇之某CRM系统多处sql注入

&#x1f31f; ❤️ 作者&#xff1a;yueji0j1anke 首发于公号&#xff1a;剑客古月的安全屋 字数&#xff1a;3516 阅读时间: 35min 声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果…

科沃斯,「扫地茅」荣光恐难再现

作者 | 辰纹 来源 | 洞见新研社 科沃斯恐怕已经很难再回到被市场誉为“扫地茅”时的荣光了。 不久前&#xff0c;科沃斯发布2023年财报&#xff0c;报告期内营业收入155亿&#xff0c;同比仅增长1.16%&#xff0c;归母净利润6.12亿元&#xff0c;同比下降63.96%&#xff0c;直…

【北京迅为】《iTOP-3588开发板快速烧写手册》-第11章 救砖方法

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

pycharm中导入rospy(ModuleNotFoundError: No module named ‘rospy‘)

1. ubuntu安装对应版本ros ubuntu20.04可参考&#xff1a; https://wiki.ros.org/cn/noetic/Installation/Ubuntuhttps://zhuanlan.zhihu.com/p/515361781 2. 安装python3-roslib sudo apt-get install python3-roslib3.在conda环境中安装rospy pip install rospkg pip in…

重定向_缓冲区

目录 重定向 文件属性操作 浅谈重定向​编辑 深入重定向 dup2 缓冲区 缓冲区的理论理解 代码分析 重定向 文件属性操作 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int stat(const char *path, struct stat *buf); int fstat(i…

USB系列一:USB技术概念

在这里USB的历史就不赘述了&#xff0c;有兴趣可以自己去搜索。也省略掉USB接口的概述&#xff0c;这些都是一些飞技术性的常识性的知识&#xff0c;没必要浪费篇幅和文字来描述。 一、USB总线版本&#xff1a;&#xff08;从USB1.1说起&#xff09; 1、USB1.1 1998年9月23日…

教你如何高效使用Java中的ArrayList

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

【vue+el-upload】当action=“#“,代表不使用默认上传,使用自定义上传,http-request获取文件流

el-upload有多种上传行为&#xff1a; 1、立即上传&#xff1a; 当 action 属性被赋予一个有效的 URL 时&#xff0c;一旦用户选择了文件&#xff0c;el-upload 组件会立即自动将文件上传到指定的服务器地址。 2、不立即上传&#xff08;自定义触发&#xff09;&#xff1a; 如…

杰发科技AC7840——软件Sent_HAL39X

0. 序 截止2024.5.8&#xff0c;杰发的MCU没有硬件Sent功能&#xff0c;因此使用PWM模拟Sent来试试。 测试下7840的软件sent功能。 参考链接&#xff1a;SENT协议应用笔记 - TechPlus汽车工坊的文章 - 知乎 SENT协议 1. Sent功能测试 使用提供的软件Sent代码在7840上测试&a…

正点原子Linux学习笔记(五)FrameBuffer 应用编程

FrameBuffer 应用编程 19.1 什么是 FrameBuffer19.2 LCD 的基础知识19.3 LCD 应用编程介绍使用 ioctl()获取屏幕参数信息使用 mmap()将显示缓冲区映射到用户空间 19.4 LCD 应用编程练习之 LCD 基本操作19.5 LCD 应用编程练习之显示 BMP 图片在 LCD 上显示 BMP 图像在开发板上测…

超强动画制作软件blender

blender中文手册&#xff1a;Blender 4.1 Manual Blender 是一款集3D建模、渲染、动画、视频编辑、音频处理、游戏设计等多功能于一体的软件。由于其开源性质&#xff0c;它拥有庞大的用户群体和活跃的开发者社区&#xff0c;这使得Blender的功能和性能得到了不断的提升和优化…

Windows内核开发:如何使用STL

前言 大家都知道应用层c的STL非常强大&#xff0c;非常好用&#xff0c;但是在内核下就没法用了。针对这个问题&#xff0c;经过我不懈的寻找&#xff0c;终于找到了解决内核无法使用STL的方法。 使用new/delete关键字 先说一下常用关键字如何在内核中使用。其实只需要在一个全…

第四十节实现主人公的技能释放功能(二)实现技能按钮

看看我们今天要实现的效果是&#xff0c;当我们按下数字1快捷键&#xff0c;我们的技能按钮会进入倒计时&#xff0c;如下图演示&#xff1a; 一、新建场景和根节点设置 新建场景&#xff0c;选择TextureButton作为根节点&#xff0c;重名为SpellButton&#xff0c;保存场景…

啸叫抑制器采用什么处理芯片?ES56031或PH56031

会议系统或卡拉OK最头疼的就是啸叫了吧&#xff0c;来看看啸叫抑制器采用什么芯片 四通道啸叫抑制器&#xff0c;采用了2个电路板&#xff0c;每个板子处理2路信号&#xff0c;每块电路板有2个卡侬输入插座&#xff0c;2个卡侬输出插座 ES56031S&#xff0c;该啸叫抑制器为4通道…

【优选算法】——双指针——Leetcode——283.移动零

目录 ​编辑 1.题目 2. 解法&#xff08;快排的思想&#xff1a;数组划分区间-数组分两块&#xff09;&#xff1a; 1.算法思路&#xff1a; 2.算法流程&#xff1a; 3.代码实现 1.C语言 2.C 1.题目 283. 移动零 提示 给定一个数组 nums&#xff0c;编写一个函数将所有…
最新文章