一,最小生成树算法基本概念
最小生成树是数据结构中图的一种重要应用,它的要求是从一个有n个节点的带权完全图中选择n-1条边并使这个图仍然连通(也即得到了一棵生成树),同时还要考虑使树的权之和最小。最小生成树可以用,Kruskal(克鲁斯卡尔)算法或Prim(普里姆)算法求出。
1,Prim算法
1),算法简单描述:
1).输入:一个加权连通图,其中顶点集合为V,边集合为E;
2).初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
3).重复下列操作,直到Vnew = V:
a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;
4).输出:使用集合Vnew和Enew来描述所得到的最小生成树
2),下面对算法的图例描述:
绿色顶点及边表示:已经被选中为最小生成树中的顶点
淡蓝色顶点及边表示:当前距离已选中的顶点中的最小权值边及其相连的顶点
蓝色表示顶点及边表示:当前已经选中顶点可达的顶点或者边
图例 | 说明 | 不可选 | 可选 | 已选(Vnew) |
---|---|---|---|---|
| 此为原始的加权连通图。每条边一侧的数字代表其权值。 | - | - | - |
| 1,选取任意顶点为起始点,比如D。, 2,顶点A、B、E和F通过单条边与D相连。A是距离D最近的顶点,因此将A及对应边AD以高亮表示。 | C, G | A, B, E, F | D |
| 1,下一个顶点为距离D或A最近的顶点。B距D为9,距A为7,E为15,F为6。因此,F距D或A最近,因此将顶点F与相应边DF以高亮表示。 | C, G | B, E, F | A, D |
算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。 | C | B, E, G | A, D, F | |
| 在当前情况下,可以在C、E与G间进行选择。C距B为8,E距B为7,G距F为11。E最近,因此将顶点E与相应边BE高亮表示。 | 无 | C, E, G | A, D, F, B |
| 这里,可供选择的顶点只有C和G。C距E为5,G距E为9,故选取C,并与边EC一同高亮表示。 | 无 | C, G | A, D, F, B, E |
| 顶点G是唯一剩下的顶点,它距F为11,距E为9,E最近,故高亮表示G及相应边EG。 | 无 | G | A, D, F, B, E, C |
| 现在,所有顶点均已被选取,图中绿色部分即为连通图的最小生成树。在此例中,最小生成树的权值之和为39。 | 无 | 无 | A, D, F, B, E, C, G |
2,Kruskal算法
Kruskal算法与Prim算法的不同之处在于,Kruskal在找最小生成树结点之前,需要对所有权重边做从小到大排序。将排序好的权重边依次加入到最小生成树中,如果加入时产生回路就跳过这条边,加入下一条边。当所有结点都加入到最小生成树中之后,就找出了最小生成树。
1).算法简单描述
1).记Graph中有v个顶点,e个边
2).新建图Graphnew,Graphnew中拥有原图中相同的e个顶点,但没有边
3).将原图Graph中所有e个边按权值从小到大排序
4).循环:从权值最小的边开始遍历每条边 直至图Graph中所有的节点都在同一个连通分量中
if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中
添加这条边到图Graphnew中
2),下面对算法的图例描述:
(每次所选的边都要与原所选边进行是否为圈的判断)
首先第一步,我们有一张图Graph,有若干点和边
将所有的边的长度排序,用排序的结果作为我们选择边的依据。这里再次体现了贪心算法的思想。资源排序,对局部最优的资源进行选择,排序完成后,我们率先选择了边AD。这样我们的图就变成了右图
在剩下的变中寻找。我们找到了CE。这里边的权重也是5
依次类推我们找到了6,7,7,即DF,AB,BE。
下面继续选择, BC或者EF尽管现在长度为8的边是最小的未选择的边。但是现在他们已经连通了(对于BC可以通过CE,EB来连接,类似的EF可以通过EB,BA,AD,DF来接连)。所以不需要选择他们。类似的BD也已经连通了(这里上图的连通线用红色表示了)。
二,C++模板实现
1,Graph.h代码实现如下:
#include "stdafx.h"
#include "iostream"
#include "queue"
using namespace std;
template<class DistType/*边的权值的类型*/>
class Edge//边的定义
{
public:
Edge(int dest, DistType weight)
{
m_nposTable=dest;
m_distWeight=weight;
m_pnext=NULL;
}
~Edge()
{
}
public:
int m_nposTable;//该边的目的顶点在顶点集中的位置
DistType m_distWeight;//边的权重值
Edge<DistType> *m_pnext;//下一条边(注意不是下一个顶点,因为m_nposTable已经知道了这个顶点的位置)
};
//声明
template<class NameType/*顶点集名字类型*/, class DistType/*距离的数据类型*/> class Graph;
template<class NameType/*顶点集名字类型*/, class DistType/*距离的数据类型*/>
class Vertex//顶点的定义
{
public:
Vertex()
{
padjEdge=NULL;
m_vertexName=0;
}
~Vertex()
{
Edge<DistType> *pmove = padjEdge;
while (pmove)
{
padjEdge = pmove->m_pnext;
delete pmove;
pmove = padjEdge;
}
}
private:
friend class Graph<NameType,DistType>;//允许Graph类任意访问
NameType m_vertexName;//顶点中的数据内容
Edge<DistType> *padjEdge;//顶点的邻边
};
template<class NameType/*顶点集名字类型*/, class DistType/*距离的数据类型*/>
class Graph
{
public:
Graph(int size = m_nDefaultSize/*图顶点集的规模*/)
{
m_pVertexTable = new Vertex<NameType, DistType>[size]; //为顶点集分配内存
if (m_pVertexTable == NULL)
{
exit(1);
}
m_numVertexs=0;
m_nmaxSize=size;
m_nnumEdges=0;
}
~Graph()
{
Edge<DistType> *pmove;
for (int i=0; i < this->m_numVertexs; i++)
{
pmove = this->m_pVertexTable[i].padjEdge;
if (pmove){
this->m_pVertexTable[i].padjEdge = pmove->m_pnext;
delete pmove;
pmove = this->m_pVertexTable[i].padjEdge;
}
}
delete[] m_pVertexTable;
}
int GetNumEdges()
{//获得边的数目
return m_nnumEdges/2;
}
int GetNumVertexs()
{//获得顶点数目
return m_numVertexs;
}
bool IsGraphFull() const
{ //图满的?
return m_nmaxSize == m_numVertexs;
}
//在顶点集中位置为v1和v2的顶点之间插入边
bool InsertEdge(int v1, int v2, DistType weight=m_Infinity);
//插入顶点名字为vertex的顶点
bool InsertVertex(const NameType vertex);
//打印图
void PrintGraph();
//顶点v到其他各个顶点的最短路径(包括自身)
void Dijkstra(int v, DistType *shotestpath);
//获取顶点集中位置为v1和v2的顶点之间边的权重值
DistType GetWeight(int v1, int v2);
//获得在顶点集中的位置为v的顶点的名字
NameType GetVertexValue(int v);
//用该顶点的名字来寻找其在顶点集中的位置
int GetVertexPosTable(const NameType vertex);
//得到顶点v的邻点中权值最小的那条边
Edge<DistType> *GetMin(int v, int *visited);
//最小生成树
void Prim(Graph<NameType, DistType> &graph);
//深度搜索优先
void DFS(int v, int *visited);
void DFS();
//广度优先搜索
void BFS(int v, int *visited);
void BFS();
//获取第v个顶点的名字(或者说内容)
NameType GetVertexName(int v);
//获得顶点v的第一个相邻顶点,如果没有就返回-1
int GetFirst(int v);
//获得顶点v1的邻点v2后的邻点
int GetNext(int v1, int v2);
private:
Vertex<NameType, DistType> *m_pVertexTable; //顶点集
int m_numVertexs;//图中当前的顶点数量
int m_nmaxSize;//图允许的最大顶点数
static const int m_nDefaultSize = 10; //默认的最大顶点集数目
static const DistType m_Infinity = 65536; //边的默认权值(可以看成是无穷大)
int m_nnumEdges;//图中边的数目
};
//返回顶点vertexname在m_pVertexTable(顶点集)中的位置
//如果不在顶点集中就返回-1
template<class NameType, class DistType>
int Graph<NameType, DistType>::GetVertexPosTable(const NameType vertexname)
{
for (int i=0; i < this->m_numVertexs; i++)
{
if (vertexname == m_pVertexTable[i].m_vertexName)
{
return i;
}
}
return -1;
}
//打印图中的各个顶点及其链接的边的权重
template<class NameType, class DistType>
void Graph<NameType, DistType>::PrintGraph()
{
Edge<DistType> *pmove;
for (int i=0; i<this->m_numVertexs; i++)
{
cout << this->m_pVertexTable[i].m_vertexName << "->";
pmove = this->m_pVertexTable[i].padjEdge;
while (pmove)
{
cout << pmove->m_distWeight << "->" << this->m_pVertexTable[pmove->m_nposTable].m_vertexName << "->";
pmove = pmove->m_pnext;
}
cout << "NULL" << endl;
}
}
//获得在顶点集中的位置为v的顶点的名字
template<class NameType, class DistType>
NameType Graph<NameType, DistType>::GetVertexValue(int v)
{
if (v<0 || v>=this->m_numVertexs)
{
cerr << "查找的顶点位置参数有误,请检查!" <<endl;
exit(1);
}
return m_pVertexTable[v].m_vertexName;
}
//返回顶点v1和v2之间的边权值,
//如果没有直接相连(即不是一条边直接相连)则返回无穷大
template<class NameType, class DistType>
DistType Graph<NameType, DistType>::GetWeight(int v1, int v2)
{
if (v1>=0 && v1<this->m_numVertexs && v2>=0 && v2<this->m_numVertexs)
{
if (v1 == v2)
{
return 0;
}
Edge<DistType> *pmove = m_pVertexTable[v1].padjEdge;
while (pmove)
{
if (pmove->m_nposTable == v2)
{
return pmove->m_distWeight;
}
pmove = pmove->m_pnext;
}
}
return m_Infinity;
}
//顶点依次插入到分配好的顶点集中
template<class NameType, class DistType>
bool Graph<NameType, DistType>::InsertVertex(const NameType vertexname)
{
if (IsGraphFull())
{
cerr<<"图已经满,请勿再插入顶点!"<<endl;
return false;
}else
{
this->m_pVertexTable[this->m_numVertexs].m_vertexName = vertexname;
this->m_numVertexs++;
}
return true;
}
//在顶点集位置为v1和v2的顶点之间插入权值为weght的边(务必保持输入的准确性,否则.....)
template<class NameType, class DistType>
bool Graph<NameType, DistType>::InsertEdge(int v1, int v2, DistType weight)
{
if (v1 < 0 && v1 > this->m_numVertexs && v2 < 0 && v2 > this->m_numVertexs)
{
cerr<<"边的位置参数错误,请检查! "<<endl;
return false;
}
else
{
Edge<DistType> *pmove = m_pVertexTable[v1].padjEdge;
if (pmove == NULL)//如果顶点v1没有邻边
{ //建立顶点v1的第一个邻边(该邻边指明了目的顶点)
m_pVertexTable[v1].padjEdge = new Edge<DistType>(v2, weight);
m_nnumEdges++;//图中边的数目
return true;
}else//如果有邻边
{
while (pmove->m_pnext)
{
pmove = pmove->m_pnext;
}
pmove->m_pnext = new Edge<DistType>(v2, weight);
m_nnumEdges++;//图中边的数目
return true;
}
}
}
template<class NameType, class DistType>
void Graph<NameType, DistType>::Dijkstra(int v, DistType *shPath)
{
int num =GetNumVertexs();
int *visited = new int[num];
for (int i=0; i < num; i++)
{//初始化
visited[i] = 0;//未访问
shPath[i] = this->GetWeight(v, i);//顶点v(当前中间点)到各个相邻顶点的边权值,其他情况返回无穷大
}
visited[v] = 1;//第v个顶点初始化为被访问,并以他为中点点开始找最短路径
for (int i = 1; i < num; i++)
{
DistType min = this->m_Infinity;
int u=0;
//寻找新的中间点u,依据就是数组中权值最小的那个点的位置(且没被访问过)
for (int j=0; j < num; j++)
{
if (!visited[j])
{
if (shPath[j]<min)
{
min = shPath[j];//获得当前shPath数组中的最小边权重
u = j;//用u来记录获取最小值时的顶点位置,即新的中间点
}
}
}
visited[u] = 1;//已经确定的最短路径
//以u为中间点寻找顶点v到顶点w的最短路径
for (int w=0; w < num; w++)
{
DistType weight = this->GetWeight(u, w);//顶点u(当前中间点)到各个相邻顶点的边权值,其他情况返回无穷大
if (!visited[w] && weight != this->m_Infinity )
{
if ( shPath[u]+weight < shPath[w] )
{
shPath[w] = shPath[u] + weight;//更新顶点v到w的最短路径值
}
}
}
}
delete[] visited;
}
//获得顶点v1的邻点v2后的邻点
template<class NameType, class DistType>
int Graph<NameType, DistType>::GetNext(int v1, int v2)
{
if (-1 != v1)
{
Edge<DistType> *pmove = this->m_pVertexTable[v1].padjEdge;
while (NULL != pmove->m_pnext)
{
if (pmove->m_nposTable==v2)
{
return pmove->m_pnext->m_nposTable;
}
pmove = pmove->m_pnext;
}
}
return -1;
}
//从第v个顶点开始深度遍历
template<class NameType, class DistType>
void Graph<NameType, DistType>::DFS(int v, int *visited)
{
cout << "->" << this->GetVertexName(v);
visited[v] = 1;
int firstVertex = this->GetFirst(v);//获得顶点v的第一个相邻顶点,若没有则返回-1
while (-1 != firstVertex)
{
if (!visited[firstVertex])//如果没有访问过
{
cout << "->" << this->GetWeight(v, firstVertex);//获得顶点v及其邻点firstVertex之间的权值
DFS(firstVertex, visited);
}
firstVertex = this->GetNext(v, firstVertex);//获得顶点v的邻点firstVertex后的邻点,如果没有就返回-1
}
}
template<class NameType, class DistType>
void Graph<NameType, DistType>::DFS()
{
int *visited = new int[this->m_numVertexs];
for (int i=0; i<this->m_numVertexs; i++)
{
visited[i] = 0;
}
cout << "head";
DFS(0, visited);//从第一个顶点开始遍历
cout << "--->end";
}
template<class NameType, class DistType>
void Graph<NameType, DistType>::BFS()
{
int *visited = new int[this->m_numVertexs];
for (int i=0; i<this->m_numVertexs; i++)
{
visited[i] = 0;
}
cout << "head";
BFS(0, visited);//从第一个顶点开始遍历
cout << "--->end";
}
//从第v个顶点开始广度遍历
template<class NameType, class DistType>
void Graph<NameType, DistType>::BFS(int v, int *visited)
{
cout << "->" << this->GetVertexName(v);
visited[v]=1;
queue<int> que;//=new queue<int>[this->GetNumVertexs()];
que.push(v);//进队(队列的末端)
while (!que.empty())
{
v=que.front();//出队首元素
que.pop();//删除队首元素
int firstvertex=GetFirst(v);
while(firstvertex != -1)
{
if (!visited[firstvertex])
{
cout << "->" << this->GetWeight(v, firstvertex);//获得顶点v及其邻点firstVertex之间的权值
que.push(firstvertex);
visited[firstvertex]=1;
cout << "->" << this->GetVertexName(firstvertex);
}
firstvertex=GetNext(v,firstvertex);
}
}
}
//获得在顶点集中的位置为v的顶点的名字
template<class NameType, class DistType>
NameType Graph<NameType, DistType>::GetVertexName(int v)
{
if (v<0 || v>=this->m_numVertexs)
{
cerr << "查找的顶点位置参数有误,请检查!" <<endl;
exit(1);
}
return m_pVertexTable[v].m_vertexName;
}
//获得顶点v的第一个相邻顶点,如果没有就返回-1
template<class NameType, class DistType>
int Graph<NameType, DistType>::GetFirst(int v)
{
if (v<0 || v>=this->m_numVertexs)
{
return -1;
}
Edge<DistType> *ptemp = this->m_pVertexTable[v].padjEdge;
return m_pVertexTable[v].padjEdge ? m_pVertexTable[v].padjEdge->m_nposTable : -1;
}
template<class NameType, class DistType>
Edge<DistType>* Graph<NameType, DistType>::GetMin(int v, int *visited)
{
Edge<DistType> *pmove = this->m_pVertexTable[v].padjEdge;
Edge<DistType> *ptemp = new Edge<DistType>(0, this->m_Infinity);
Edge<DistType> *pmin = ptemp;
while (pmove)
{
if (!visited[pmove->m_nposTable] && pmin->m_distWeight>pmove->m_distWeight){
pmin = pmove;
}
pmove = pmove->m_pnext;
}
if (pmin == ptemp)
{
delete ptemp;
return NULL;
}
delete ptemp;
return pmin;
}
template<class NameType, class DistType>
void Graph<NameType, DistType>::Prim(Graph<NameType, DistType> &graphprim)
{
int *nodeVertex = new int[this->m_numVertexs]; //用来存储被访问过的顶点
int *visited = new int[this->m_numVertexs];//设置顶点被访问过与否
int count = 0;
Edge<DistType> *ptemp1;
Edge<DistType> *ptemp2 = new Edge<DistType>(0, this->m_Infinity);
Edge<DistType> *pmin;
int min=0;
//初始化最小生成树
for (int i=0; i<this->m_numVertexs; i++)
{
graphprim.InsertVertex(this->m_pVertexTable[i].m_vertexName);
nodeVertex[i] = 0;
visited[i] = 0;
}
visited[0] = 1;//从第一个顶点开始,并标记为以访问
while(++count < this->m_numVertexs)
{
pmin = ptemp2;
pmin->m_distWeight = this->m_Infinity;
//获得已访问顶点和未访问顶点之间的最小权值
for (int i=0; i<count; i++){
ptemp1 = GetMin(nodeVertex[i], visited);
if (NULL == ptemp1)
{
continue;
}
if (pmin->m_distWeight > ptemp1->m_distWeight)
{
pmin = ptemp1;
min = nodeVertex[i];
}
}
nodeVertex[count] = pmin->m_nposTable;
visited[nodeVertex[count]] = 1;
graphprim.InsertEdge(pmin->m_nposTable, min, pmin->m_distWeight);
graphprim.InsertEdge(min, pmin->m_nposTable, pmin->m_distWeight);
}
delete ptemp2;
delete[] nodeVertex;
delete[] visited;
}
2,主程序代码如下:
// ConsoleAppMyGraph.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "Graph.h"
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
Graph<char *, int> graph(7);
char *vertex[7] = {"【地大】", "【武大】", "【华科】", "【交大】", "【北大】", "【清华】", "【复旦】"};//顶点集
for (int i=0; i<7; i++)
{
graph.InsertVertex(vertex[i]);
}
cout<<"一,图的初始化(邻结表存储):======================================"<<endl;
graph.PrintGraph();
cout<<"图中顶点的数目:"<<graph.GetNumVertexs()<<endl;
cout <<endl;
int edge[][3] = {{0, 1, 43}/*地大到武大的距离*/, {0, 2, 12}, {1, 2, 38}, {2, 3 ,1325},
{3, 6, 55},{4, 5, 34}, {4, 6, 248},{0,3,400},{2,6,314},{2,4,37}}; //分配距离
int len=sizeof(edge)/sizeof(edge[0]);
for (int i=0; i < len; i++)
{
graph.InsertEdge(edge[i][0], edge[i][1], edge[i][2]);
graph.InsertEdge(edge[i][1], edge[i][0], edge[i][2]);
}
cout<<"二,添加边后的图(无向图):=================================="<<endl;
graph.PrintGraph();
cout<<"图中边的数目(实际上是所示边数的两倍,因为是双向的):"<<graph.GetNumEdges()<<endl;
cout <<endl;
cout<<"三,Dijkstra法最短路径为:=========================="<<endl;
int shortestPath[7];//存储Dijkstra算法最短路径值
graph.Dijkstra(0, shortestPath);
for (int i=0; i<7; i++)
{
cout << graph.GetVertexValue(0) << "--->" << graph.GetVertexValue(i)
<< ": " << shortestPath[i] <<endl;
}
cout<<endl;
cout<<"四,图的遍历:=========================="<<endl;
cout<<"1,DFS深度优先遍历结果:"<<endl;
graph.DFS();
cout<<endl<<endl;
cout<<"2,BFS广度优先遍历结果:"<<endl;
graph.BFS();
cout<<endl<<endl;
cout<<"五,最小生成生成树:================================="<<endl;
Graph<char *, int> graphPrim;
graph.Prim(graphPrim);
cout<<"使用DFS遍历:"<<endl;
graphPrim.DFS();
cout<<endl<<endl;
cout<<"使用BFS遍历:"<<endl;
graphPrim.BFS();
system("color 0A");
system("pause");
return 0;
}
3,测试结果:
参考资源:
【1】《算法导论》
【2】《百度文库》
【3】《维基百科》
【4】https://en.wikipedia.org/wiki/Kruskal%27s_algorithm
【5】https://en.wikipedia.org/wiki/Prim%27s_algorithm
【6】http://baike.baidu.com/linkurl=tHoWD0_Xcu_fSMMoQgLmwh_0nR1Uk0xfYRhd8zgVYSX8DMsHPSXRsQZhgvw7SL9NyHDrQkU7j7B80uusAZbl4PzSGZzJwxqHba_FJ7jAkApSmLUjq2PswoytLdvZpL7nWoUCR4jJp_MxGyGA06YQKf5WU6sHZNBbUePG6NiytO
【7】http://blog.csdn.net/todd911/article/details/9219937
【8】http://blog.chinaunix.net/uid-25324849-id-2182922.html
【9】http://www.cnblogs.com/rollenholt/archive/2012/04/09/2439055.html
注:
本文部分文字学习并copy自网络,代码参考并改写于《算法导论》.
如果侵犯了您的版权,请联系本人tangyibiao520@163.com,本人将及时编辑掉!