图论算法基础:最小生成树算法(kruskal算法和Prim算法)

在这里插入图片描述

一.图邻接矩阵数据结构

  • 以STLvectorunordered_map为适配容器实现图数据结构:
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;		// 图的邻接矩阵
	};

}
  • 一个无向连通图的生成树指的是包含该图所有顶点无环连通子图
    • 最小生成树指的是图的所有生成树中边权值之和最小的生成树

二.kruskal算法

  • kruskal算法求图的最小生成树的基本思想是依次选出图中权值最小的边来构建生成树,图中某条边加入生成树之前,要判断生成树中是否会形成环(这是该算法的一个难点),如果会形成环,则跳过这条边继续选下一条权值最小边.若连通图共有N个顶点,则选出N-1条边则最小生成树构建完成

算法实现思想

  • 选最小边的方法:将图的所有边加入到小堆数据结构(STLpriority_queue的底层结构)中,选边时可以从堆顶选到最小边,选N条边的时间复杂度为O(NlogN)
  • 某条边加入生成树之前,判断生成树中是否会出现环的方法:采用并查集实现判环,选边的过程中将生成树中互相连通的顶点在并查集中合并到同一个连通分量中.
    • 最小生成树构建的过程中,如果选出的权值最小边的起点和终点位于并查集的同一个连通分量中,则该边不能加入最小生成树(原理:两个顶点在构建最小生成树的过程中如果两次连通则生成树中必定会出现环)
    • 采用并查集判环,每次判环的时间复杂度接近O(1)
      在这里插入图片描述

kruskal算法接口实现

  • 定义一个描述边的内部类(记录边的起点和终点以及边的权值)
		//定义表示边的结构用于算法设计
		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;
			}
		};
  • kruskal算法接口:
		//克鲁斯卡尔算法求最小生成树,返回值是最小生成树的边权值之和
		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();
			}
		}
  • 接口测试:
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();
}

在这里插入图片描述
在这里插入图片描述

三.Prim算法

  • Prim最小生成树算法思想:用一个顶点集合来记录已经加入最小生成树的顶点,选定一个构建最小生成树的起始顶点(并将该顶点加入顶点集合),将与顶点集合中的所有点相连的边加入堆数据结构中,从堆顶依次选取权值最小边加入最小生成树,然后更新最小生成树顶点集合,如此往复。
    • 若某条边的起点和终点已经在最小生成树顶点集合中,则跳过该边,取下一条权值最小边,通过这种方式可以保证最小生成树中不会出现环
      在这里插入图片描述

Prim算法接口实现

		//给定一个起点,用普利姆算法求最小生成树,返回值是最小生成树的边权值之和
		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();
			}
		}
  • 接口测试:
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;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_73470348/article/details/132510508