最小生成树定义:
给定一无向带权图,顶点数是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; //用来存储找到的最小生成树的边
};
#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;
}