【C++高阶数据结构】图

news2024/9/25 12:19:49

🏆个人主页:企鹅不叫的博客

​ 🌈专栏

  • C语言初阶和进阶
  • C项目
  • Leetcode刷题
  • 初阶数据结构与算法
  • C++初阶和进阶
  • 《深入理解计算机操作系统》
  • 《高质量C/C++编程》
  • Linux

⭐️ 博主码云gitee链接:代码仓库地址

⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!

💙系列文章💙

【C++高阶数据结构】并查集


文章目录

  • 💙系列文章💙
  • 💎一、概念
    • 🏆1.图概念
    • 🏆2.邻接矩阵
    • 🏆3.邻接表
    • 🏆4.代码
  • 💎二、图遍历
    • 🏆1.广度优先遍历-BFS
    • 🏆2.深度优先遍历-DFS
  • 💎二、生成最小树
    • 🏆1.Kruskal算法
    • 🏆2.Prim算法
  • 💎三、最短路径
    • 🏆1.Dijkstra算法
    • 🏆2.Bellman-Ford算法


💎一、概念

🏆1.图概念

图用G表示,顶点集合用V表示,边用E表示

有向图:<x, y>和<y, x>是不同的,带有方向,G3和G4

无向图:<x, y>和<y, x>是一样的,没有方向,G1和G2

请添加图片描述

无向完全图:有n个顶点的无向图中,若有n * (n-1)/2条边 (参考等差数列求和),任意两个顶点间有且仅有一条边,如G1

有向完全图:有n个顶点的无向图若有n * (n-1)条边 (参考等差数列求和),任意两个顶点间有且仅有二条边,如G4

邻接顶点:x和y是通过<x,y>连接的,就是邻接顶点

顶点的度:等于入度和出度的和,入度是从外面连接到此顶点,出度是从这个顶点连接到外面,如G3的0顶点入度是1出度是1,度是2

径的路径长度是指该路径上各个边权值的总和

权值:边附带的数据信息

请添加图片描述

请添加图片描述

连通图:在无向图中任意一对顶点都是连通的

强连通图:在有向图中任意一对顶点都是连通的

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

图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和边关系即可。节点保存比较简单,只需要一段连续空间即可。其边关系一般采用邻接矩阵邻接表的方式保存。

🏆2.邻接矩阵

因为节点与节点之间的关系就是连通与否,即为0或者1,因此邻接矩阵(二维数组)即是:先用一个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系。

请添加图片描述

请添加图片描述

  1. 无向图的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度。有向图的邻接矩阵则不一定是对称的,第i行(列)元素之后就是顶点i 的出(入)度。
  2. 如果边带有权值,并且两个节点之间是连通的,上图中的边的关系就用权值代替,如果两个顶点不通,则使用无穷大代替。
  3. 邻接矩阵的优势是快速找到两个顶点之间的边,适合于边比较多的图;其劣势为要找一个顶点连出去的边,需要遍历其他顶点,因此时间复杂度为O(N),并且如果顶点比较多,边比较少时,矩阵中存储了大量的0成为系数矩阵,比较浪费空间。

🏆3.邻接表

无向图邻接表存储

请添加图片描述

有向图邻接表存储

请添加图片描述

邻接表的优势为:

  1. 找一个点相连的顶点的边很快。

  2. 适合边比较少,比较稀疏的图。

劣势为:
要确认两个顶点是否相连为O(N),因为要把所有的边都找一遍。

🏆4.代码

邻接矩阵

//V表示中节点的属性,W表示边的权值,Direction表示是否为有向图,默认是无向图,默认值权值是最大 
template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
class Graph {
public:
	Graph(const V* vertexs, size_t n) {
		//将顶点全部插入集合
		_vertexs.reserve(n);
		for (size_t i = 0; i < n; i++) {
			_vertexs.push_back(vertexs[i]);
			_indexMap[vertexs[i]] = i;
		}
		//初始化邻接矩阵
		_matrix.resize(n);
		for (auto& e : _matrix) {
			e.resize(n, MAX_W);
		}
	}
	//获取顶点的下标
	size_t GetVertexIndex(const V& v) {
		//遍历map
		auto it = _indexMap.find(v);
		if (it != _indexMap.end()) {
			return it->second;
		}
		else {
			//throw invalid_argument("不存在的顶点");
			assert(false);
			return -1;
		}
	}
	void AddEdge(const V& src, const V& dst, const W& w) {
		size_t srcindex = GetVertexIndex(src);
		size_t dstindex = GetVertexIndex(dst);
		_matrix[srcindex][dstindex] = w;
		if (Direction == false) {
			//无向图,双向的
			_matrix[dstindex][srcindex] = w;
		}
	}
	void Print() {
		// 打印顶点和下标映射关系
		for (size_t i = 0; i < _vertexs.size(); ++i) {
			cout << _vertexs[i] << "-" << i << " ";
		}
		cout << endl << endl;

		cout << "  ";
		for (size_t i = 0; i < _vertexs.size(); ++i) {
			cout << i << " ";
		}
		cout << endl;

		// 打印矩阵
		for (size_t i = 0; i < _matrix.size(); ++i) {
			cout << i << " ";
			for (size_t j = 0; j < _matrix[i].size(); ++j) {
				if (_matrix[i][j] != MAX_W)
					cout << _matrix[i][j] << " ";
				else
					cout << "#" << " ";
			}
			cout << endl;
		}
		cout << endl << endl;

		// 打印所有的边
		for (size_t i = 0; i < _matrix.size(); ++i) {
			for (size_t j = 0; j < _matrix[i].size(); ++j) {
				if (i < j && _matrix[i][j] != MAX_W) {
					cout << _vertexs[i] << "-" << _vertexs[j] << ":" << _matrix[i][j] << endl;
				}
			}
		}
	}
private:
	vector<V> _vertexs;			//顶点集合
	map<V, int> _indexMap;		//顶点和下标的映射关系
	vector<vector<W>> _matrix;	//存储边集合的矩阵,邻接矩阵
};

void TestGraph() {
	string a[] = { "张三","李四","王五","赵六","周七" };
	Graph<string, int>g1(a, sizeof(a) / sizeof(a[0]));
	g1.AddEdge("张三", "李四", 100);
	g1.AddEdge("张三", "王五", 200);
	g1.AddEdge("王五", "赵六", 10);
	g1.AddEdge("李四", "周七", 50);
	g1.Print();
}

邻接表

template<class W>
struct Edge {
	int _srcIndex;
	int _dstIndex;//目标点下标
	W _w;		  //权值
	Edge<W>* _next;
	Edge(const W& w)
		:_srcIndex(-1)
		, _dstIndex(-1)
		, _w(w)
		, _next(nullptr) {
	}
};
//V表示中节点的属性,W表示边的权值,Direction表示是否为有向图,默认是无向图,默认值权值是最大 
template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
class Graph {
	typedef Edge<W> Edge;
public:
	Graph(const V* vertexs, size_t n) {
		//将顶点全部插入集合
		_vertexs.reserve(n);
		for (size_t i = 0; i < n; i++) {
			_vertexs.push_back(vertexs[i]);
			_indexMap[vertexs[i]] = i;
		}
		//初始化邻接矩阵
		_tables.resize(n);

	}
	//获取顶点的下标
	size_t GetVertexIndex(const V& v) {
		//遍历map
		auto it = _indexMap.find(v);
		if (it != _indexMap.end()) {
			return it->second;
		}
		else {
			//throw invalid_argument("不存在的顶点");
			assert(false);
			return -1;
		}
	}
	void AddEdge(const V& src, const V& dst, const W& w) {
		size_t srcindex = GetVertexIndex(src);
		size_t dstindex = GetVertexIndex(dst);

		Edge* eg = new Edge(w);
		eg->_srcIndex = srcindex;
		eg->_dstIndex = dstindex;
		eg->_next = _tables[srcindex];
		_tables[srcindex] = eg;

		if (Direction == false) {
			//无向图的起点和终点相互颠倒
			Edge* eg = new Edge(w);
			eg->_srcIndex = dstindex;
			eg->_dstIndex = srcindex;
			eg->_next = _tables[dstindex];
			_tables[dstindex] = eg;
		}
	}
	void Print() {
		// 打印顶点和下标映射关系
		for (size_t i = 0; i < _vertexs.size(); ++i) {
			cout << _vertexs[i] << "-" << i << " ";
		}
		cout << endl << endl;

		cout << "  ";
		for (size_t i = 0; i < _vertexs.size(); ++i) {
			cout << i << " ";
		}
		cout << endl;

		// 打印矩阵
		for (size_t i = 0; i < _vertexs.size(); ++i) {
			cout << "[" << i << "]" << "->" << _vertexs[i] << endl;
		}
		cout << endl;

		for (size_t i = 0; i < _tables.size(); ++i) {
			cout << _vertexs[i] << "[" << i << "]->";
			Edge* cur = _tables[i];
			while (cur) {
				cout << "[" << _vertexs[cur->_dstIndex] << ":" << cur->_dstIndex << ":" << cur->_w << "]->";
				cur = cur->_next;
			}
			cout << "nullptr" << endl;
		}
	}
private:
	vector<V> _vertexs;			//顶点集合
	map<V, int> _indexMap;		//顶点和下标的映射关系
	vector<Edge*> _tables;		//邻接表
};

void TestGraph() {
	string a[] = { "张三","李四","王五","赵六","周七" };
	Graph<string, int>g1(a, sizeof(a) / sizeof(a[0]));
	g1.AddEdge("张三", "李四", 100);
	g1.AddEdge("张三", "王五", 200);
	g1.AddEdge("王五", "赵六", 10);
	g1.AddEdge("李四", "周七", 50);
	g1.Print();
}

💎二、图遍历

🏆1.广度优先遍历-BFS

用邻接矩阵为例子,相当于二叉树中的层序遍历

	//广度优先搜索,起点
	void BFS(const V& src) {
		//找到其下标
		size_t srcindex = GetVertexIndex(src);
		//visited表示没有被访问过的顶点,用于标记
		vector<bool> visited(_vertexs.size(), false);
		//使用队列完成广度遍历
		queue<int>q;
		q.push(srcindex);
		visited[srcindex] = true;
		size_t d = 1;//记录起点的度

		//dSize保证队列中的数据走完
		size_t leveSize = 1;
		while (!q.empty()) {
			printf("%s的%d度好友:", src.c_str(), d);
			while (leveSize--) {
				size_t front = q.front();
				q.pop();
				//把front顶点的邻接顶点入队列
				for (size_t i = 0; i < _vertexs.size(); ++i) {
					if (visited[i] == false && _matrix[front][i] != INT_MAX) {
						printf("[%d:%s]", i, _vertexs[i].c_str());
						q.push(i);
						visited[i] = true;
					}
				}
			}
			cout << endl;
			leveSize = q.size();
			++d;
		}
	}

void TestGraph() {
	string a[] = { "张三","李四","王五","赵六","周七" };
	Graph<string, int>g1(a, sizeof(a) / sizeof(a[0]));
	g1.AddEdge("张三", "李四", 100);
	g1.AddEdge("张三", "王五", 200);
	g1.AddEdge("王五", "赵六", 10);
	g1.AddEdge("李四", "周七", 50);
	g1.Print();
	g1.BFS("张三");
	g1.DFS("张三");
}

🏆2.深度优先遍历-DFS

用邻接矩阵为例子,相当于而擦函数中的前中后序遍历

	//起点和标记数组
	void _DFS(size_t srcIndex, vector<bool>& visited) {
		printf("[%d:%s]", srcIndex, _vertexs[srcIndex].c_str());
		visited[srcIndex] = true;//表示该点被标记
		for (size_t i = 0; i < _vertexs.size(); ++i) {
			if (visited[i] == false && _matrix[srcIndex][i] != INT_MAX) {
				_DFS(i, visited);
			}
		}

	}
	//深度优先搜索,起点
	void DFS(const V& src) {
		//找到其下标
		size_t srcindex = GetVertexIndex(src);
		//visited表示没有被访问过的顶点
		vector<bool> visited(_vertexs.size(), false);
		_DFS(srcindex, visited);
	}
void TestGraph() {
	string a[] = { "张三","李四","王五","赵六","周七" };
	Graph<string, int>g1(a, sizeof(a) / sizeof(a[0]));
	g1.AddEdge("张三", "李四", 100);
	g1.AddEdge("张三", "王五", 200);
	g1.AddEdge("王五", "赵六", 10);
	g1.AddEdge("李四", "周七", 50);
	g1.Print();
	g1.BFS("张三");
	g1.DFS("张三");
}

💎二、生成最小树

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

  1. 只能使用图中的权值最小的边来构造最小生成树,这几条边加起来的权值是最小的。
  2. 只能使用恰好n-1条边来连接图中的n个顶点。
  3. 选用的n-1条边不能构成回路。构造最小生成树的方法:Kruskal算法和Prim算法。这两个算法都采用了逐步求解的贪心策略。贪心算法:是指在问题求解时,总是做出当前看起来最好的选择。也就是说贪心算法做出的不是整体最优的的选择,而是某种意义上的局部最优解。贪心算法不是对所有的问题都能得到整体最优解。

🏆1.Kruskal算法

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

请添加图片描述

  1. 先判断加入边的两个顶点在不在一个集合,如果在一个集合,加入就会构成环。
  2. 加入的边,就把它的两个顶点合并。
  3. 并查集传送门
void _AddEdge(size_t srci, size_t dsti, const W& w) {
		_matrix[srci][dsti] = w;
		// 无向图
		if (Direction == false) {
			_matrix[dsti][srci] = w;
		}
	}

	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);
	}
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 kruskalMST(Graph<V, W, MAX_W, Direction>& g) {
		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, MAX_W);
		}
		//升序的优先级队列,每次获取权值最小的边
		priority_queue<Edge, vector<Edge>, 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) {
					minque.push(Edge(i, j, _matrix[i][j]));
				}
			}
		}
		//每次从优先级队列中获取权值最小的边,然后判断它们的起点和终点在不在一个集合
		//若不在则选中这条边,并将起点和终点放入集合
		//若在则说明如果选中则会形成环,此时忽略掉这条边
		//重复上述操作,直到优先级队列为空
		// 选出n-1条边
		int size = 0;
		W totalW = W();
		UnionFindSet ufs(n);//利用并查集
		while (!minque.empty()) {
			Edge min = minque.top();
			minque.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;
				totalW += min._w;
			}
			else {
				cout << "构成环:";
				cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
			}
		}
		//找到了最小生成树
		if (size == n - 1) {
			return totalW;
		}
        	//如果没有找到就返回权值
		else {
			return W();
		}
	}

🏆2.Prim算法

Prim算法随便选一个点,然后选该点权值最小的那条边,再选这条边相连的节点的所有边中权值最小的边,不断重复。

请添加图片描述
这种方式不会构成环,因为每次选边,都是从两个集合里面分别选一个节点构成的边:已经相连顶点是一个集合,没有相连顶点是一个集合。因此只需要用set记录选过的节点即可。

	//最小生成树Prim
	W Prim(Graph<V, W, MAX_W, Direction>& minTree, const W& src) {
     	//获取这个起点的下标
		size_t srci = GetVertexIndex(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, MAX_W);
		}
		//同两个数组,分别表示已经被最小生成树选中的结点和没有被最小生成树选中的结点
		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>> minq;
		// 先把srci连接的边添加到队列中
		for (size_t i = 0; i < n; ++i) {
			if (_matrix[srci][i] != MAX_W) {
             		//分别将起始点,指向的最终点和权值构成的边放入队列中
				minq.push(Edge(srci, i, _matrix[srci][i]));
			}
		}

		cout << "Prim开始选边" << endl;
		size_t size = 0;
		W totalW = MAX_W;
		while (!minq.empty()) {
			Edge min = minq.top();
			minq.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中对应的点将其标记成true代表已经加入了x集合
				X[min._dsti] = true;
             		//Y代表的是还没有被连接的点,所以我们将我们这个已经被连接的点的位置标记成false
				Y[min._dsti] = false;
				++size;
				totalW += min._w;
				if (size == n - 1)
					break;

				for (size_t i = 0; i < n; ++i) {
                 		    //将当前边的终点作为我们挑选下一条边的起点,并且这条起点的终点不能在我们的X集合中
                    			 //然后将这些点重新放入我们的队列中
					if (_matrix[min._dsti][i] != MAX_W && Y[i]) {
						minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
					}
				}
			}
		}
		//选到了n-1条边就返回总的权重
		if (size == n - 1) {
			return totalW;
		}
		else {
			return MAX_W;
		}
	}

💎三、最短路径

🏆1.Dijkstra算法

最短路径问题:从在带权图的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。

单源最短路径问题:算法要求图中所有边的权重非负,一般在求解最短路径的时候都是已知一个起点 和一个终点,所以使用Dijkstra算法求解过后也就得到了所需起点到终点的最短路径。运用贪心思想,每次从 「未求出最短路径的点」中取出距离距离起点最小路径的点,以这个点为跳转点刷新「未求出最短路径的点」的距离。然后锁定该跳转点,因为该跳转点到起始位置的距离已经是最小值了。

Dijkstra算法详解 通俗易懂

请添加图片描述

// 传入起点
	// 传入存储起点到其他各个点的最短路径的权值和容器(例:点s到syzx的路径)
	// 传入每个节点的父路径,也就是从我们的源节点到我们每一个节点的最短路径的前一个节点的下标,存储路径前一个顶点下标
	void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath) {
		//源点的下标
		size_t srci = GetVertexIndex(src);
		//节点的数量
		size_t n = _vertexs.size();
		//将所有的路径初始化为无穷大
		dist.resize(n, MAX_W);
		//将路径全部都初始化成-1,也就是没有前一个结点(我们结点的下标从0开始)
		pPath.resize(n, -1);
		//自己结点到自己的距离就是0
		dist[srci] = 0;
		//自己到自己的最短路径的前一个节点就是自己,所以前一个节点的下标也是自己
		pPath[srci] = srci;

		// 标记已经确定最短路径的顶点集合,初始全部都是false,也就是全部都没有被确定下来
		vector<bool> S(n, false);

		for (size_t j = 0; j < n; ++j) {
			// 选最短路径顶点且不在S更新其他路径
			//最小的点为u,初始化为0
			int u = 0;
			//记录到最小的点的权值
			W min = MAX_W;
			for (size_t i = 0; i < n; ++i) {
				//这个点没有被选过,并且到这个点的权值比我们当前的最小值更小,我们就进行更新
				if (S[i] == false && dist[i] < min) {
					//用u记录这个最近的点的编号
					u = i;
					min = dist[i];
				}
			}
			//标记当前点已经被选中了
			S[u] = true;
			// 松弛更新u连接顶点v  srci->u + u->v < srci->v 则更新
			//确定u链接出去的所有点
			for (size_t v = 0; v < n; ++v) {
				//如果v这个点没有被标记过,也就是我们这个点还没有被确定最短距离,并且从我们当前点u走到v的路径是存在的
				//并且从u走到v的总权值的和比之前源点到v的权值更小,我们就更新我们从源点到我们的v的最小权值
				if (S[v] == false && _matrix[u][v] != MAX_W
					&& dist[u] + _matrix[u][v] < dist[v]) {
					//更新从源点到v的最小权值
					dist[v] = dist[u] + _matrix[u][v];
					//标记我们从源点到v的最小路径要走到v这一步的前一个节点需要走u
					pPath[v] = u;
				}
			}
		}
	}

测试

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

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

    }

时间复杂度O(N^2),空间复杂度O(N)

🏆2.Bellman-Ford算法

bellman—ford算法可以解决负权图的单源最短路径问题,是一种暴力算法。时间复杂度 O(N*E) (N是点数,E是边数),使用邻接矩阵实现,那么遍历所有边的数量的时间复杂度就是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, MAX_W);

		// vector<int> pPath 记录srci-其他顶点最短路径父顶点数组
		pPath.resize(n, -1);

		// 先更新srci->srci为缺省值
		dist[srci] = MAX_W;

		//cout << "更新边:i->j" << endl;


		// 总体最多更新n轮
		//从s->t最多经过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) {
					// srci -> i + i ->j
					//如果i->j边存在的话,并且srci -> i + i ->j比原来的距离更小,就更新该路径
					if (_matrix[i][j] != MAX_W && 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] != MAX_W && dist[i] + _matrix[i][j] < dist[j]) {
					return false;
				}
			}
		}

		return true;
	}

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

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

相关文章

代码随想录拓展day7 649. Dota2 参议院;1221. 分割平衡字符串;5.最长回文子串;132. 分割回文串 II;673.最长递增子序列的个数

代码随想录拓展day7 649. Dota2 参议院&#xff1b;1221. 分割平衡字符串&#xff1b;5.最长回文子串&#xff1b;132. 分割回文串 II&#xff1b;673.最长递增子序列的个数 贪心和动态规划的题目。因为贪心其实没什么规律&#xff0c;所以就简要记录了。 649. Dota2 参议院 …

数据可视化系列-04数据大屏基础知识

文章目录5.销售数据看板5.1 了解数据大屏基础知识1.数据大屏简介&#xff1a;2.数据大屏使用场景3.数据大屏分类5.2 数据大屏的设计&#xff1a;1.大屏前端设计流程2.数据大屏设计尺寸解析3.可视化视觉设计5.3 大屏开发工具DataV&#xff1a;1.DataV数据可视化简介2.优势3.Data…

植物大战僵尸:遍历向日葵的生产速度

找到向日葵的吐出阳光的速度&#xff0c;向日葵生产阳光是一个周期性的,所以其内部是有一个计时器控制的,我们只要找到这个计时器并改写它的定时时间即可完成无限吐阳光的作用 向日葵的遍历技巧: 首先种下一个向日葵 -> 然后CE马上搜索->未知初始化回到游戏->然后在…

关于单相智能多用户远程预付费控系统的优化设计研究

摘要&#xff1a;由于现有系统仅对电表数据进行读取操作&#xff0c;存在成本较高和耗时较长的问题&#xff0c;为此对单相智能多用户远程预付费控系统优化设计进行研究。选择电能表子系统作为优化对象&#xff0c;选取78KO527A微控制器作为电能表子系统的控制核心&#xff0c;…

Java 并发编程 (三)Phaser

#Phaser 功能介绍 CyclicBarrier 解决了 CountDownLatch的缺点&#xff0c;但是其本身也仍然具备一定的缺陷&#xff0c;比如不可以动态添加parties 调用一次await 仅占用1个parties public class MyPhaser {private Phaser phaser new Phaser(3);public void testA(){System…

微信过期文件怎么找回?2个方法,轻松解决

现在很多小伙伴都喜欢使用微信发送文字、图片、语音、照片、文件等内容给对方。可是在其中&#xff0c;文件内容比较特殊。如果没有及时进行查看&#xff0c;过了一段时间就会变成过期文件&#xff0c;无法再进行查看了。微信过期文件怎么找回&#xff1f;今天小编分享2个简单好…

微型消防站一体化指挥作战解决方案

一、政策要求 近年政府对社区微型消防站提出了新的建设要求&#xff1a; 1、 大力发展多种形式消防队伍&#xff0c;逐步加强现役消防力量建设&#xff0c;发展政府专职消防队&#xff0c;企业事业单位专职消防队和志愿消防队。 2、 明确城市消防安全布局和公共消防设施的消…

vue 可视化表单设计器 自定义组件

有一款基于vue的可视化表单编辑器&#xff0c;直接双击或者拖拽组件&#xff0c;便可以创建一个表单&#xff0c;非常方便。今天为大家介绍这款编辑器&#xff1a; 官方地址&#xff1a;Variant Form - 可视化低代码表单 github地址&#xff1a;https://github.com/vform666/…

Redshift渲染器为什么这么受欢迎?

Redshift是用于高质量制作工作的渲染器 。它是很快的渲染引擎&#xff0c;并且可以很好地与许多第三方插件配合使用。在本文中&#xff0c;让我们深入了解Redshift的更多细节。什么是 Redshift 及其工作原理&#xff1f;Redshift 是一种有偏见的 GPU 渲染引擎&#xff0c;它支持…

【回答问题】ChatGPT上线了!给我推荐20个比较流行的OCR模型

给我推荐20个比较流行的OCR模型 好的&#xff0c;以下是我为你推荐的 20 个比较流行的 OCR 模型。 Tesseract CuneiForm Ocrad GOCR OCRopus OCR-ICR Ocropy Ocrad.js Ocracoke Ocralysis Ocrinum Ocrmypdf Ocrsaurus Ocrdroid Ocr-textscan Ocrfeeder Ocrmypicture Ocrus Oc…

RHCE-chrony服务部署

目录 要求&#xff1a; 思路&#xff1a; 配置过程&#xff1a; a: 1.开启两台服务器&#xff0c;开启chrony服务。 2.部署chrony服务 查看chrony服务状态 b: 区别&#xff1a; 配置过程: 修改完配置后&#xff0c;重启chrony服务 查看chrony状态&#xff1a; 小结一…

element-ui 多选框和级联选择的部分bug以及解决方法

前言 最近在开发一款使用了 element-ui 的低代码设计器&#xff0c;在开发的过程当中碰到了一些关于 element-ui 组件本身不合理的地方&#xff0c;并且在百度的基础上自己去阅读了一下 element-ui 的源码&#xff0c;也找出了这些问题的一个解决方案&#xff0c;下面就来看一…

steam搬砖是什么?怎么做呀?

steam平台是什么&#xff1f;它是国外一个集全球大部分网游于一体的游戏平台&#xff0c;玩过绝地求生端游&#xff08;吃鸡&#xff09;&#xff0c;csgo的朋友&#xff0c;对它都不陌生&#xff0c;就像国内的Wegame一样&#xff0c;现在玩英雄联盟的&#xff0c;都是通过Weg…

排序算法之冒泡算法

目录 排序算法介绍 冒泡排序 算法流程 算法实现 python C 排序算法介绍 《Hello算法》是GitHub上一个开源书籍&#xff0c;对新手友好&#xff0c;有大量的动态图&#xff0c;很适合算法初学者自主学习入门。而我则是正式学习算法&#xff0c;以这本书为参考&#xff0c…

返回数组所有元素中或每行(列)中,最小值的位置(位置号从0开始):argmin()函数

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 返回数组所有元素中或每行(列)中 最小值的位置&#xff08;位置号从0开始&#xff09; argmin()函数 选择题 下列说法错误的是? import numpy as np a np.array([[10,20,30,40],[15,20,25…

Electron 企业级应用开发实战(二)

这一讲会重点介绍如何集成 Node.js、使用 preload 脚本、进程间双向通信、上下文隔离等&#xff0c;为大家揭开 Electron 更强大的能力。 集成 Node.js 企业级桌面应用的资源都是本地化的&#xff0c;离线也能使用&#xff0c;所以需要把 html、js、css 这些资源都打包进去&a…

独立光伏-电池-柴油发电机组的能源管理系统的主干网研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

ESPI3接收机

18320918653 ESPI3 ESPI3|R&S ESPI3|二手EMI接收机|EMI预认证测试接收机|罗德与施瓦茨|EMC接收机|9KHz至3GHz品 牌&#xff1a;德国罗德与施瓦茨 | R&S | Rohde&Schwarz处于预认证级别的 R&S ESPI测试接收机有两种型号, 集成了罗德与施瓦茨公司认证级EMI测试…

springboot:除了OpenOffice还可以用它轻松实现文档在线预览功能【附带源码】

0. 引言 我们在项目中常常需要实现文档在线预览的功能&#xff0c;而文档种类繁多&#xff0c;除了pdf&#xff0c;还有word、text、excel、甚至还有mp3,mp4等多媒体文件。常用的手段是通过OpenOffice来将文档转换为pdf实现预览&#xff0c;本期我们就来看如何通过kkFileView实…

rabbitmq基础10——消息追踪、Shovel插件的web端使用和命令使用

文章目录一、消息追踪1.1 Firehose功能1.1.1 开启与关闭1.1.2 测试1.1.3 总结1.2 rabbitmq_tracing 插件1.2.1 定义trace规则1.2.2 测试1.2.2.1 与Firehose之间的优先级二、Shovel插件2.1 实现原理2.1.1 从队列到交换器2.1.2 从队列到队列2.1.3 交换器到交换器2.2 Shovel 插件使…