图的两种最小生成树算法之C++封装

最小生成树定义:

    给定一无向带权图,顶点数是n,要使图连通只需n-1条边,若这n-1条边的权值和最小,则称有这n个顶点和n-1条边构成了图的最小生成树(minimum-cost spanning tree)MST。


两种最小生成树算法:

一、prim算法思想:设图G顶点集合为U,首先任意选择图G中的一点作为起始点a,将该点加入集合V,再从集合U-V(差集)中找到另一点b使得点b到集合V中任意一点的权值最小,此时将b点也加入集合V;以此类推,现在的集合V={a,b},再从集合U-V(差集)中找到另一点c使得点c到集合V中任意一点的权值最小,此时将c点加入集合V,直至所有顶点全部被加入V,此时就构建出了一颗MST。因为有N个顶点,所以该MST就有N-1条边,每一次向集合V中加入一个点,就意味着找到一条MST的边。


假设有一无向图:


1、以v1作为起始点,初始时,V={v1}

2、从 U-V={v2,v3,v4,v5,v6}中选出到V={v1}中任意点最小权值点为v3,则V={v1,v3},则:


3、从 U-V={v2,v4,v5,v6}中选出到V={v1,v3}中任意点最小权值点为v6,则V={v1,v3,v6},则:


从 U-V={v2,v4,v5}中选出到V={v1,v3,v6}中任意点最小权值点为v4,则V={v1,v3,v6,v4},


依此类推,在不能形成环路的条件下,依次选出:v2,v5,则最后的最小生成树为:



kruskal算法:

算法思想:则令最小生成树的初始状态为只有n个顶点而无边的非连通图,图中每个顶点各自成一个连通分量。从图中的所有边中选出一条权值最小的边,若该边依附的两个顶点落在T不同的连通分量中,则将该边作为最小生成树的一条边保存起来,并标记该边已经选择过,否则就摒弃该条边而选择下一条代价最小的边,依次类推,直到图中所有顶点都在同一连通分量上为止。




图中先将每个顶点看作独立的子图,然后查找最小权值边,这条边是有限制条件的,边得两个顶点必须不在同一个子图中,如上图,第一次找到最小权值边为(v1,v3),且满足限制条件,继续查找到边(v4,v6),(v2,v5),(v3,v6),当查找到最后一条边时,仅仅只有(v2,v3)满足限制条件,其他的如(v3,v4),(v1,v4)都在一个子图里面,不满足条件,至此已经找到最小生成树的所有边。


两种算法的C++实现:


.h文件


#pragma once
#include <vector>
using namespace std;



/*   无向图:
            A
         /  |  \
        B---F---E
		 \ / \ /
	      C---D

   索引: A B C D E F
         0 1 2 3 4 5

   权值:A-B 6、A-E 5、A-F 1
         B-C 3、B-F 2
		 C-F 8、C-D 7
		 D-F 4、D-E 2
		 E-F 9
*/


class CEdge//边的类
{
public:
	CEdge(int NodeIndexA = 0,int NodeIndexB = 0,int WightValue = 0);
	int m_nNodeIndexA; //边的起始点
	int m_nNodeIndexB;//边的终点
	int m_nWeightValue;//边的权值
	bool m_bSelected;//表明此边是否被选择过
};

class CNode//点的类
{
public:
	CNode(char cData = 0);
public:
	char m_cData;
	bool m_bIsVisited;
};

class CZzcGrapha
{
public:
	CZzcGrapha(int nCapacity);
	~CZzcGrapha(void);
	bool AddNodeToGrapha(CNode* pNode); //向图中增加节点
	void ResetNodeVisitFlag();           //将所有节点的访问标识置为false
	bool SetValueToMatrixForDirectedGraph(int row,int col,int value = 1);//向有向图矩阵中设置值
	bool SetValueToMatrixForUnDirectedGraph(int row,int col,int value = 1);//向无向图矩阵设置值
	bool GetValueFromMatrix(int row,int col,int& value);//从邻接矩阵中获取值
	void PrintMatrix();//打印出图的邻接矩阵
	void DepthFirstTraverse(int nodeindex);//深度优先遍历
	void WidthFirstTraverse(int nodeindex);//广度优先遍历
	void WidthTraverseIteration(vector<int> prevec);

	void PrimTree(int nodeindex);//prim算法 最小生成树
	void Kruskal();//Kruskal 最小生成树
private:
	int GetMinValueEdge(vector<CEdge> edgeVec);
	bool IsInSet(vector<int> nodeVec,int nodeIndex);
	void mergeNodeSet(vector<int> &nodeSetA,vector<int> nodeSetB);
private:
	int m_nCapacity;        //图的容量(可以容纳的节点数)
	int m_nCurNodeCount;    //图中当前的节点个数
	CNode* m_pNodeArray;    //用来存放定点数据
	int* m_pMatrix;         //用来存放邻接矩阵数据
	CEdge* m_pEdge;         //用来存储找到的最小生成树的边
};



.Cpp文件:

#include "StdAfx.h"
#include "ZzcGrapha.h"
#include "Windows.h"
#include <iostream>
using namespace std;


CEdge::CEdge(int NodeIndexA,int NodeIndexB,int WightValue)
{
	m_nNodeIndexA = NodeIndexA;
	m_nNodeIndexB = NodeIndexB;
	m_nWeightValue = WightValue;
	m_bSelected = false;
}

CNode::CNode(char cData)
{
	m_cData = cData;
	m_bIsVisited = false;
}

CZzcGrapha::CZzcGrapha(int nCapacity)
{
	m_nCapacity      = nCapacity;
	m_nCurNodeCount  = 0;
	m_pNodeArray     = new CNode[m_nCapacity];
	m_pMatrix        = new int[m_nCapacity * m_nCapacity];
	ZeroMemory(m_pMatrix,m_nCapacity * m_nCapacity * sizeof(int));

	m_pEdge = new CEdge[m_nCapacity - 1];
}

CZzcGrapha::~CZzcGrapha(void)
{
	if (m_pNodeArray)
	{
		delete[]m_pNodeArray;
		m_pNodeArray = NULL;
	}

	if (m_pMatrix)
	{
		delete[]m_pMatrix;
		m_pMatrix = NULL;
	}

	if (m_pEdge)
	{
		delete[]m_pEdge;
		m_pEdge = NULL;
	}
}

bool CZzcGrapha::AddNodeToGrapha(CNode* pNode)
{
	if(pNode == NULL) return false;

	m_pNodeArray[m_nCurNodeCount].m_cData = pNode->m_cData;
	m_nCurNodeCount++;
	return true;
}

void CZzcGrapha::ResetNodeVisitFlag()
{
	for(int i = 0;i < m_nCapacity;i++)
	{
		m_pNodeArray[i].m_bIsVisited = false;
	}
}

bool CZzcGrapha::SetValueToMatrixForDirectedGraph(int row,int col,int value)
{
	if(row < 0||row >= m_nCapacity) return false;

	if(col < 0||col >= m_nCapacity) return false;

	m_pMatrix[m_nCapacity * row + col] = value;

	return true;
}

bool CZzcGrapha::SetValueToMatrixForUnDirectedGraph(int row,int col,int value)
{
	if(row < 0||row >= m_nCapacity) return false;

	if(col < 0||col >= m_nCapacity) return false;

	m_pMatrix[m_nCapacity * row + col] = value;
	m_pMatrix[m_nCapacity * col + row] = value;

	return true;
}

bool CZzcGrapha::GetValueFromMatrix(int row,int col,int& value)
{
	if(row < 0||row >= m_nCapacity) return false;

	if(col < 0||col >= m_nCapacity) return false;

	value = m_pMatrix[m_nCapacity * row + col];

	return true;
}

void CZzcGrapha::PrintMatrix()
{
	for (int i = 0;i < m_nCapacity;i++)
	{
		for (int k = 0;k < m_nCapacity;k++)
		{
			cout<<m_pMatrix[m_nCapacity * i + k]<<" "; 
		}

		cout<<endl;
	}
}

void CZzcGrapha::DepthFirstTraverse(int nodeindex)
{
	int value = 0;

	cout<<m_pNodeArray[nodeindex].m_cData<<" ";
	m_pNodeArray[nodeindex].m_bIsVisited = true;

	for (int i = 0;i < m_nCapacity;i++)
	{
		GetValueFromMatrix(nodeindex,i,value);

		if (value == 1)
		{
			if(m_pNodeArray[i].m_bIsVisited == true) continue;

			DepthFirstTraverse(i);
		} 
		else
		{
			continue;
		}
	}
}

void CZzcGrapha::WidthFirstTraverse(int nodeindex)
{
	cout<<m_pNodeArray[nodeindex].m_cData<<" ";
	m_pNodeArray[nodeindex].m_bIsVisited = true;

	vector<int> curVec;
	curVec.push_back(nodeindex);
	WidthTraverseIteration(curVec);
}

void CZzcGrapha::WidthTraverseIteration(vector<int> prevec)
{
	int value = 0;

	vector<int> curVec;

	for(int i = 0;i < (int)prevec.size();i++)
	{
		for (int j = 0;j < m_nCapacity;j++)
		{
			GetValueFromMatrix(prevec[i],j,value);

			if (value != 0)
			{
				if(m_pNodeArray[j].m_bIsVisited) continue;

				cout<<m_pNodeArray[j].m_cData<<" ";
				m_pNodeArray[j].m_bIsVisited = true;

				curVec.push_back(j);
			} 
			else
			{
				continue;
			}
		}
	}

	if(curVec.size() == 0) return;

	WidthTraverseIteration(curVec);
}

//参数nodeindex表示第一个加入到点集合中的点
void CZzcGrapha::PrimTree(int nodeindex)//prim算法 最小生成树
{
	int value = 0;//存放所取得的边的权值
	int edgeCount = 0;//标识所找到的边的数目
	vector<int> nodeVec;//存放所找到的点的索引的集合
	vector<CEdge> edgeVec;//存放所找到的边的集合

	nodeVec.push_back(nodeindex);//将第一个点的索引加入到点集合
	m_pNodeArray[nodeindex].m_bIsVisited = true;//第一个点已经被访问过了

	cout<<m_pNodeArray[nodeindex].m_cData<<endl;

	while (edgeCount < m_nCapacity - 1)
	{
		int temp = nodeVec.back();

		for (int i = 0;i < m_nCapacity;i++)//寻找与temp点相连接的点
		{
			GetValueFromMatrix(temp,i,value);

			if (value != 0)//权值不为0,则两点相连接
			{
				if (m_pNodeArray[i].m_bIsVisited)//此点已经被访问过了
				{
					continue;
				} 
				else
				{
					CEdge edge(temp,i,value);//构造temp与i两点之间的边
					edgeVec.push_back(edge);//将此边加入到边的集合
				}
			}
		}

		//for循环过后会找到与temp点连接的所有的边,下面找到权值最小的边,返回此边在集合中的索引

		int mixEdgeIndex = GetMinValueEdge(edgeVec);

		edgeVec[mixEdgeIndex].m_bSelected = true;

		cout<<edgeVec[mixEdgeIndex].m_nNodeIndexA<<"---"<<edgeVec[mixEdgeIndex].m_nNodeIndexB<<"   ";
		cout<<edgeVec[mixEdgeIndex].m_nWeightValue<<endl;
		cout<<m_pNodeArray[edgeVec[mixEdgeIndex].m_nNodeIndexB].m_cData<<endl;

		m_pEdge[edgeCount] = edgeVec[mixEdgeIndex];//保存找到的最小边

		edgeCount++;

		int nextNodeIndex = edgeVec[mixEdgeIndex].m_nNodeIndexB;//下次要加入到点集合中的索引

		nodeVec.push_back(nextNodeIndex);

		m_pNodeArray[nextNodeIndex].m_bIsVisited = true;
	}
}

int CZzcGrapha::GetMinValueEdge(vector<CEdge> edgeVec)//找到最小权值边
{
	int value = 0;
	int edgeIndex = 0;
	
	int i = 0;

	for (;i < (int)edgeVec.size();i++)
	{
		if (!edgeVec[i].m_bSelected)
		{
			value = edgeVec[i].m_nWeightValue;
			edgeIndex = i;
			break;
		}
	}

	if (value == 0)
	{
		return -1;
	}

	for (;i < (int)edgeVec.size();i++)
	{
		if(edgeVec[i].m_bSelected) continue;

		if (value > edgeVec[i].m_nWeightValue)
		{
			value = edgeVec[i].m_nWeightValue;
			edgeIndex = i;
		}
	}

	return edgeIndex;
}

void CZzcGrapha::Kruskal()
{
	int value = 0,edgeCount = 0;

	vector<vector<int>> nodeSets;//存放点集合的数组,相当于是数组的数组

	vector<CEdge> edgeVec;//存放边的数组

	//第一步:找出所有边,并放入到边的数组中
	for (int i = 0;i < m_nCapacity - 1;i++)
	{
		for (int k = i + 1;k < m_nCapacity;k++)
		{
			GetValueFromMatrix(i,k,value);
			cout<<value<<" ";
			
			if (value != 0)//i和k两个点之间存在边
			{
				

				CEdge edge(i,k,value);
				edgeVec.push_back(edge);
			}
		}
		cout<<endl;
	}

	//第二步,从所有边中取出最小生成树的边
	while (edgeCount < m_nCapacity - 1)
	{
		//从边的集合中找出最小边
		int minEdgeIndex = GetMinValueEdge(edgeVec);
		edgeVec[minEdgeIndex].m_bSelected = true;

		//取出最小边的两个点
		int nodeAIndex = edgeVec[minEdgeIndex].m_nNodeIndexA;
		int nodeBIndex = edgeVec[minEdgeIndex].m_nNodeIndexB;

		bool bNodeAIsInSet = false;
		bool bNodeBIsInSet = false;

		int nNodeAInSetLab = -1;
		int nNodeBInSetLab = -1;

		//分别找出最小边两个点所在的集合
		for (int i = 0;i < (int)nodeSets.size();i++)
		{
			bNodeAIsInSet = IsInSet(nodeSets[i],nodeAIndex);//判断nodeAIndex点在哪个点集合中

			if (bNodeAIsInSet)
			{
				nNodeAInSetLab = i;//将集合索引保存起来
			}
		}

		for (int i = 0;i < (int)nodeSets.size();i++)
		{
			bNodeBIsInSet = IsInSet(nodeSets[i],nodeBIndex);//判断nodeAIndex点在哪个点集合中

			if (bNodeBIsInSet)
			{
				nNodeBInSetLab = i;//将集合索引保存起来
			}
		}

		//两点都不在已经存在的集合中,新建一个集合放入集合的数组中
		if(nNodeAInSetLab == -1&&nNodeBInSetLab == -1)
		{
			vector<int> vec;
			vec.push_back(nodeAIndex);
			vec.push_back(nodeBIndex);
			nodeSets.push_back(vec);
		}
		//nodeAIndex不在已经存在的集合中,nodeBIndex在已经存在的集合中,
		//将nodeAIndex放入到nodeBIndex所在的集合中
		else if(nNodeAInSetLab == -1&&nNodeBInSetLab != -1)
		{
			nodeSets[nNodeBInSetLab].push_back(nodeAIndex);
		}
		//nodeAIndex在已经存在的集合中,nodeBIndex不在已经存在的集合中,
		//将nodeBIndex放入到nodeAIndex所在的集合中
		else if(nNodeAInSetLab != -1&&nNodeBInSetLab == -1)
		{
			nodeSets[nNodeAInSetLab].push_back(nodeBIndex);
		}
		//两点在不同的集合中,合并两个集合
		else if(nNodeAInSetLab != -1&&nNodeBInSetLab != -1&&nNodeAInSetLab != nNodeBInSetLab)
		{
			//参数2合并到参数1的集合中
			mergeNodeSet(nodeSets[nNodeAInSetLab],nodeSets[nNodeBInSetLab]);
			//将参数2集合从nodeSets集合中去掉
			for (int i = nNodeBInSetLab;i < (int)nodeSets.size()-1;i++)
			{
				nodeSets[i] = nodeSets[i+1];
			}
		}
		//当期的两个点在同一个集合中,这就会形成回路,多以当期边要摒弃掉
		else if(nNodeAInSetLab != -1&&nNodeBInSetLab != -1&&nNodeAInSetLab == nNodeBInSetLab)
		{
			continue;
		}
		//到这里说明找出的边符合要求,将此边保存起来
		m_pEdge[edgeCount] = edgeVec[minEdgeIndex];
		edgeCount++;

		cout<<edgeVec[minEdgeIndex].m_nNodeIndexA<<"---"<<edgeVec[minEdgeIndex].m_nNodeIndexB<<"   ";
		cout<<edgeVec[minEdgeIndex].m_nWeightValue<<endl;
	}
}

bool CZzcGrapha::IsInSet(vector<int> nodeVec,int nodeIndex)
{
	for (int i = 0;i < (int)nodeVec.size();i++)
	{
		if (nodeVec[i] == nodeIndex)
		{
			return true;
		}
	}

	return false;
}

void CZzcGrapha::mergeNodeSet(vector<int> &nodeSetA,vector<int> nodeSetB)
{
	for (int i = 0;i < (int)nodeSetB.size();i++)
	{
		nodeSetA.push_back(nodeSetB[i]);
	}
}

测试:

// 图.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "ZzcGrapha.h"
#include <iostream>
using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	CZzcGrapha* pGrapha = new CZzcGrapha(6);

	CNode* pNodeA = new CNode('A');
	CNode* pNodeB = new CNode('B');
	CNode* pNodeC = new CNode('C');
	CNode* pNodeD = new CNode('D');
	CNode* pNodeE = new CNode('E');
	CNode* pNodeF = new CNode('F');

	pGrapha->AddNodeToGrapha(pNodeA);
	pGrapha->AddNodeToGrapha(pNodeB);
	pGrapha->AddNodeToGrapha(pNodeC);
	pGrapha->AddNodeToGrapha(pNodeD);
	pGrapha->AddNodeToGrapha(pNodeE);
	pGrapha->AddNodeToGrapha(pNodeF);

	pGrapha->SetValueToMatrixForUnDirectedGraph(0,1,6);
	pGrapha->SetValueToMatrixForUnDirectedGraph(0,4,5);
	pGrapha->SetValueToMatrixForUnDirectedGraph(0,5,1);
	pGrapha->SetValueToMatrixForUnDirectedGraph(1,2,3);
	pGrapha->SetValueToMatrixForUnDirectedGraph(1,5,2);
	pGrapha->SetValueToMatrixForUnDirectedGraph(2,5,8);
	pGrapha->SetValueToMatrixForUnDirectedGraph(2,3,7);
	pGrapha->SetValueToMatrixForUnDirectedGraph(3,5,4);
	pGrapha->SetValueToMatrixForUnDirectedGraph(3,4,2);
	pGrapha->SetValueToMatrixForUnDirectedGraph(4,5,9);

	pGrapha->PrintMatrix();
	cout<<endl;

	//pGrapha->DepthFirstTraverse(0);
	//cout<<endl;
	//pGrapha->ResetNodeVisitFlag();
	//pGrapha->WidthFirstTraverse(0);

	//pGrapha->PrimTree(0);
	pGrapha->Kruskal();


	return 0;
}


发布了415 篇原创文章 · 获赞 123 · 访问量 64万+

猜你喜欢

转载自blog.csdn.net/u012372584/article/details/76599332