Basics of graph theory algorithms: minimum spanning tree algorithm (kruskal algorithm and Prim algorithm)

Insert image description here

1. Graph adjacency matrix data structure

  • Implement graph data structure using STL vectorand unordered_mapas adaption container:
namespace Graph_Structure
{
    
    
	//Vertex是代表顶点的数据类型,Weight是边的权值的数据类型,MAX_W是权值的上限值(表示不相两)
	//Direction表示图是否为有向图
	template<class Vertex, class Weight = int, Weight MAX_W = INT_MAX, bool Direction = false>
	class Graph
	{
    
    
		typedef Graph<Vertex, Weight, MAX_W, Direction> Self;
	public:
		//使用编译器的默认构造函数
		Graph() = default;

		//给定一个存放顶点的数组用来初始化图
		Graph(const Vertex* a, size_t n)
		{
    
    
			_vertexs.reserve(n);
			_indexMap.rehash(n);
			_matrix.resize(n, std::vector<Weight>(n, MAX_W));
			for (size_t i = 0; i < n; ++i)
			{
    
    
				_vertexs.push_back(a[i]);
				//建立顶点和数组下标的映射(目的是为了邻接矩阵的边存储)
				_indexMap[a[i]] = i;
			}
		}

		//获取顶点在邻接矩阵中对应的下标
		size_t GetVertexIndex(const Vertex& vertex)
		{
    
    
			if (_indexMap.find(vertex) == _indexMap.end())
			{
    
    
				throw "invalued_para";
				return -1;
			}
			return _indexMap[vertex];
		}


		void _AddEdge(size_t srci, size_t dsti, const Weight& w)
		{
    
    
			//判断是有向图还是无向图
			if (Direction == false)
			{
    
    
				_matrix[dsti][srci] = w;
			}
			_matrix[srci][dsti] = w;
		}
		//给定起点和终点,在邻接矩阵中添加一条边
		void AddEdge(const Vertex& src, const Vertex& dst, const Weight& w)
		{
    
    
			if (_indexMap.find(src) == _indexMap.end() || _indexMap.find(dst) == _indexMap.end())
			{
    
    
				throw "invalued_para";
			}

			size_t srci_index = GetVertexIndex(src);
			size_t dst_index = GetVertexIndex(dst);
			_AddEdge(srci_index, dst_index, w);
		}

		//打印图的接口
		void Print()
		{
    
    
			for (auto e : _vertexs)
			{
    
    
				std::cout << e << '[' << _indexMap[e] << ']' << std::endl;
			}

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


			int i = 0;
			for (auto arry : _matrix)
			{
    
    
				std::cout << i++ << ' ';
				for (auto e : arry)
				{
    
    
					if (e == MAX_W)
					{
    
    
						printf("%4c ", '*');
					}
					else
					{
    
    
						printf("%4d ", e);
					}
				}
				std::cout << std::endl;
			}
		}

		//图顶点的广度优先遍历
		void BFS(const Vertex& src)
		{
    
    
			size_t begin = GetVertexIndex(src);
			std::queue<int> QNode;
			std::vector<bool> Label(_vertexs.size(), false);
			QNode.push(begin);
			Label[begin] = true;
			size_t Level = 0;
			while (!QNode.empty())
			{
    
    
				size_t LevelSize = QNode.size();
				for (size_t i = 0; i < LevelSize; ++i)
				{
    
    
					size_t front = QNode.front();
					QNode.pop();
					std::cout << _vertexs[front] << '[' << front << ']' << std::endl;
					for (int j = 0; j < _vertexs.size(); ++j)
					{
    
    
						if (Label[j] == false && _matrix[front][j] != MAX_W)
						{
    
    
							QNode.push(j);
							Label[j] = true;
						}
					}
				}
			}
		}
		
		//图顶点的深度优先遍历
		void DFS(const Vertex& src)
		{
    
    
			std::vector<bool> visited(_vertexs.size(), false);
			_DFS(GetVertexIndex(src), visited);
		}
	private:
		void _DFS(size_t srci, std::vector<bool>& visited)
		{
    
    
			visited[srci] = true;
			std::cout << _vertexs[srci] << '[' << srci << ']' << std::endl;
			for (int i = 0; i < _vertexs.size(); ++i)
			{
    
    
				if (_matrix[srci][i] != MAX_W && visited[i] == false)
				{
    
    
					_DFS(i, visited);
				}
			}
		}

	private:
		std::vector<Vertex> _vertexs;					// 顶点集合
		std::unordered_map<Vertex, size_t> _indexMap;	// 顶点与下标的映射
		std::vector<std::vector<Weight>> _matrix;		// 图的邻接矩阵
	};

}
  • oneundirected connected graphThe spanning tree refers to theContains all vertices of the graphofAcyclic connected subgraph
    • minimum spanning treeRefers to all spanning trees of the graphsum of edge weightsminimal spanning tree

2. Kruskal algorithm

  • kruskalalgorithmFind the minimum spanning tree of a graphThe basic idea isSelect the edge with the smallest weight in the graph in sequenceTo build a spanning tree, before adding an edge in the graph to the spanning tree, it is necessary to determine whether a cycle will form in the spanning tree (this is a difficulty of this algorithm). If a cycle will form, skip this edge and continue to select the next right. Minimum edge. If the connected graph has Na total of vertices, select N-1an edge and the minimum spanning tree is constructed.

Algorithm realization idea

  • How to choose the smallest edge: Add all the edges of the graph to the small heap data structure ( priority_queuethe underlying structure of STL). When selecting edges, you can select the smallest edge from the top of the heap. NThe time complexity of selecting an edge isO(NlogN)
  • Before an edge is added to the spanning tree,How to determine whether a cycle will appear in a spanning tree:Use union lookup to achieve ring judgment, during the process of edge selection,Connected vertices in a spanning treeIn the combined search setmerge into the same connected component.
    • During the construction of the minimum spanning tree, if the selectedThe starting and ending points of the edge with the smallest weightlie inFind the same connected component of the union set, then the edge cannot be added to the minimum spanning tree (principle: two vertices are inIn the process of building a minimum spanning treeIf two connections are made, a cycle will definitely appear in the spanning tree)
    • Using union search to judge the loop, the time complexity of each loop judgment is close toO(1)
      Insert image description here

Kruskal algorithm interface implementation

  • Define an internal class that describes an edge (records the starting point and end point of the edge and the edge weight)
		//定义表示边的结构用于算法设计
		class Edge
		{
    
    
		public:
			size_t _srci;//起始点编号
			size_t _dsti;//终点编号
			Weight _weight;//边的权值
			//边的构造函数
			Edge(size_t srci, size_t dsti, Weight weight)
				:_srci(srci),
				_dsti(dsti),
				_weight(weight)
			{
    
    }
			//重载比较运算符
			bool operator>(const Edge& edge) const
			{
    
    
				return _weight > edge._weight;
			}
		};
  • kruskalAlgorithm interface:
		//克鲁斯卡尔算法求最小生成树,返回值是最小生成树的边权值之和
		Weight Kruskal(Self& MinTree)
		{
    
    
			//minTree成员的拷贝
			MinTree._vertexs = _vertexs;
			MinTree._indexMap = _indexMap;
			//初始化最小生成树的邻接矩阵
			MinTree._matrix.resize(_vertexs.size(), std::vector<Weight>(_vertexs.size(), MAX_W));


			//建立优先级队列(小堆)
			std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> Qedge;
			//将边存入堆中
			for (int i = 0; i < _matrix.size(); ++i)
			{
    
    
				for (int j = 0; j < _matrix.size(); ++j)
				{
    
    
					Qedge.push(Edge(i, j, _matrix[i][j]));
				}
			}

			//建立并查集防止在构建最小生成树时形成环
			std::vector<int> FindSet(_vertexs.size(), -1);
			auto FindRoot = [&FindSet](size_t root) {
    
    
				while (FindSet[root] >= 0)
				{
    
    
					root = FindSet[root];
				}
				return root;
			};


			Weight totalweight = Weight();//记录最小生成树的总权值
			size_t Edgenums = 0;		  //记录生成树中边数
			//开始选堆构建最小生成树
			while (!Qedge.empty() && Edgenums < _vertexs.size() - 1)
			{
    
    
				//选出权值最小边
				Edge MinEdge = Qedge.top();
				Qedge.pop();
				//并查集排环
				size_t root1 = FindRoot(MinEdge._srci);
				size_t root2 = FindRoot(MinEdge._dsti);
				if (root1 != root2)
				{
    
    
					//在最小生成树中添加边
					MinTree._AddEdge(MinEdge._srci, MinEdge._dsti, MinEdge._weight);
					//打印方便调试
					std::cout << '[' << _vertexs[MinEdge._srci] << ']' << "->" << '[' << _vertexs[MinEdge._dsti] << ']' << std::endl;

					//并查集中等价元素合并
					if (FindSet[root2] > FindSet[root1])
						std::swap(root1, root2);
					FindSet[root2] = root1;

					++Edgenums;
					totalweight += MinEdge._weight;
				}
			}

			//检验最小生成树是否正常生成
			if (Edgenums == _vertexs.size() - 1)
			{
    
    
				return totalweight;
			}
			else
			{
    
    
				return Weight();
			}
		}
  • Interface test:
void TestGraphMinTree()
{
    
    
	const char str[] = "abcdefghi";
	Graph_Structure::Graph<char, int> g(str, strlen(str));
	g.AddEdge('a', 'b', 4);
	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_Structure::Graph<char, int> kminTree;
	std::cout << "Kruskal:" << g.Kruskal(kminTree) << std::endl;
	kminTree.Print();
}

Insert image description here
Insert image description here

3. Prim algorithm

  • PrimMinimum spanning tree algorithm idea: use avertex setto recordVertices that have been added to the minimum spanning tree, select a starting vertex for constructing the minimum spanning tree (and add the vertex to the vertex set), which will be combined withall points in the vertex setconnected edgesAdd to heap data structure, from the top of the heapSelect the edge with the smallest weight in turn to join the minimum spanning tree., then updateminimum spanning tree vertex setandheap, and so on.
    • If the starting point and end point of an edge are already inminimum spanning tree vertex set, then skip this edge and take the next edge with the smallest weight. In this way, it can be guaranteed that there will be no cycle in the minimum spanning tree
      Insert image description here

Prim algorithm interface implementation

		//给定一个起点,用普利姆算法求最小生成树,返回值是最小生成树的边权值之和
		Weight Prim(Self& MinTree, Vertex start)
		{
    
    
			//minTree成员的拷贝
			MinTree._vertexs = _vertexs;
			MinTree._indexMap = _indexMap;
			//初始化最小生成树的邻接矩阵
			MinTree._matrix.resize(_vertexs.size(), std::vector<Weight>(_vertexs.size(), MAX_W));

			//建立优先级队列(小堆)
			std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> Qedge;
			size_t starti = GetVertexIndex(start);

			//顶点集合用于记录顶点是否在生成树中
			std::vector<bool> InMinTree(_vertexs.size(), false);
			InMinTree[starti] = true;

			//将与当前点相连的边加入优先级队列
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
    
    
				if (_matrix[starti][i] != MAX_W)
				{
    
    
					Qedge.push(Edge(starti, i, _matrix[starti][i]));
				}
			}

			Weight totalweight = Weight();//记录最小生成树的总权值
			size_t Edgenums = 0;		  //记录生成树中边数
			while (!Qedge.empty() && Edgenums < _vertexs.size() - 1)
			{
    
    
				//选出权值最小边
				Edge MinEdge = Qedge.top();
				Qedge.pop();
				//判断边的终点是否在最小生成树中,避免生成环
				if (InMinTree[MinEdge._dsti] == false)
				{
    
    
					InMinTree[MinEdge._dsti] = true;
					//在最小生成树中添加边
					MinTree._AddEdge(MinEdge._srci, MinEdge._dsti, MinEdge._weight);
					//打印方便调试
					//std::cout << '[' << _vertexs[MinEdge._srci] << ']' << "->" << '[' << _vertexs[MinEdge._dsti] << ']' << std::endl;
					
					//将与当前点相连的边加入优先级队列
					for (size_t i = 0; i < _vertexs.size(); ++i)
					{
    
    
						if (_matrix[MinEdge._dsti][i] != MAX_W && InMinTree[i] == false)
						{
    
    
							Qedge.push(Edge(MinEdge._dsti, i, _matrix[MinEdge._dsti][i]));
						}
					}
					++Edgenums;
					totalweight += MinEdge._weight;
				}
			}
			//检验最小生成树是否正常生成
			if (Edgenums == _vertexs.size() - 1)
			{
    
    
				return totalweight;
			}
			else
			{
    
    
				return Weight();
			}
		}
  • Interface test:
void TestGraphMinTree()
{
    
    
	const char str[] = "abcdefghi";
	Graph_Structure::Graph<char, int> g(str, strlen(str));
	g.AddEdge('a', 'b', 4);
	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_Structure::Graph<char, int> pminTree;
	std::cout << "Prim:" << g.Prim(pminTree, 'a') << std::endl;
	pminTree.Print();
	std::cout << std::endl;
}

Insert image description here
Insert image description here
Insert image description here

Guess you like

Origin blog.csdn.net/weixin_73470348/article/details/132510508