Conceptos básicos de los algoritmos de la teoría de grafos: algoritmo de árbol de expansión mínimo (algoritmo Kruskal y algoritmo Prim)

Insertar descripción de la imagen aquí

1. Estructura de datos de la matriz de adyacencia del gráfico

  • Implemente la estructura de datos del gráfico usando STL vectory unordered_mapcomo contenedor de adaptación:
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;		// 图的邻接矩阵
	};

}
  • unográfico conectado no dirigidoEl árbol de expansión se refiere aContiene todos los vértices del gráfico.deSubgrafo conectado acíclico
    • árbol de expansión mínimoSe refiere a todos los árboles de expansión del gráfico.suma de pesos de bordeárbol de expansión mínimo

2. algoritmo kruskal

  • kruskalalgoritmoEncuentra el árbol de expansión mínimo de un gráfico.La idea básica deSeleccione el borde con el peso más pequeño en el gráfico en secuenciaPara construir un árbol de expansión, antes de agregar un borde en el gráfico al árbol de expansión, es necesario juzgar si se formará un bucle en el árbol de expansión (este es un punto difícil del algoritmo), si se formará un bucle formado, omita este borde y continúe seleccionando el siguiente. Borde mínimo. Si el gráfico conectado tiene Nun total de vértices, seleccione N-1un borde y se construirá el árbol de expansión mínimo.

Ideas de implementación de algoritmos

  • El método para elegir el lado más pequeño.: Agregue todos los bordes del gráfico a la estructura de datos del montón pequeño ( priority_queuela estructura subyacente de STL). Al seleccionar bordes, puede seleccionar el borde más pequeño de la parte superior del montón. La Ncomplejidad temporal de seleccionar un borde esO(NlogN)
  • Antes de agregar un borde al árbol de expansión,Cómo determinar si un ciclo aparecerá en un árbol de expansión:Utilice la búsqueda de unión para lograr un juicio de anillo, en el proceso de selección de bordes seráVértices conectados en un árbol de expansiónen el conjunto de fusiónfusionarse en el mismo componente conectado.
    • Durante la construcción del árbol de expansión mínima, si el seleccionadoLos puntos inicial y final del borde con el peso más pequeño.quedarse en camaEn el mismo componente conectado del conjunto de búsqueda de unión, entonces el borde no se puede agregar al árbol de expansión mínimo (principio: dos vértices están enEn el proceso de construcción de un árbol de expansión mínimo.Si se realizan dos conexiones, definitivamente aparecerá un ciclo en el árbol de expansión)
    • Al utilizar la búsqueda de unión para juzgar el bucle, la complejidad temporal de cada juicio de bucle es cercana aO(1)
      Insertar descripción de la imagen aquí

Implementación de la interfaz del algoritmo Kruskal

  • Defina una clase interna que describa un borde (registre el punto inicial y final del borde y el peso del borde)
		//定义表示边的结构用于算法设计
		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;
			}
		};
  • kruskalInterfaz de algoritmo:
		//克鲁斯卡尔算法求最小生成树,返回值是最小生成树的边权值之和
		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();
			}
		}
  • Prueba de interfaz:
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();
}

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

3.Algoritmo primario

  • PrimIdea del algoritmo de árbol de expansión mínima: utilice uncolección de vérticespara registrarvértices que se han unido al árbol de expansión mínima, seleccione un vértice inicial para construir el árbol de expansión mínimo (y agregue el vértice al conjunto de vértices) y combínelo contodos los puntos en el conjunto de vérticesbordes conectadosAgregar a la estructura de datos del montón, desde lo alto del montónSeleccione el borde con el peso más pequeño a su vez para unirse al árbol de expansión mínimo, luego actualizarconjunto mínimo de vértices del árbol de expansiónymontón, etcétera.
    • Si los puntos inicial y final de un borde ya están enconjunto mínimo de vértices del árbol de expansión, omita el borde y tome el borde con el peso más pequeño, de esta manera se puede garantizar que no aparecerá ningún ciclo en el árbol de expansión mínima.
      Insertar descripción de la imagen aquí

Implementación de la interfaz del algoritmo 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();
			}
		}
  • Prueba de interfaz:
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;
}

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/weixin_73470348/article/details/132510508
Recomendado
Clasificación