1)生成树 一个连通图的生成树是它的极小连通子图,在n个顶点的情形下,有n-1条边。生成树是对连通图而言的,是连同图的极小连通子图,包含图中的所有顶点,有且仅有n-1条边。非连通图的生成树则组成一个生成森林;若图中有n个顶点,m个连通分量,则生成森林中有n-m条边。
2)和树的遍历相似,若从图中某顶点出发访遍图中每个顶点,且每个顶点仅访问一次,此过程称为图的遍历, (Traversing Graph)。图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。图的遍历顺序有两种:深度优先搜索(DFS)和广度优先搜索(BFS)。对每种搜索顺序,访问各顶点的顺序也不是唯一的。
3)在一个无向连通图G中,其所有顶点和遍历该图经过的所有边所构成的子图G′ 称做图G的生成树。一个图可以有多个生成树,从不同的顶点出发,采用不同的遍历顺序,遍历时所经过的边也就不同。
在图论中,常常将树定义为一个无回路连通图。对于一个带权的无向连通图,其每个生成树所有边上的权值之和可能不同,我们把所有边上权值之和最小的生成树称为图的最小生成树。求图的最小生成树有很多实际应用。例如,通讯线路铺设造价最优问题就是一个最小生成树问题。
常见的求最小生成树的方法有两种:克鲁斯卡尔(Kruskal)算法和普里姆(Prim)算法。
2、 普里姆(Prim)算法
1) 算法的基本思想:
普里姆算法的基本思想:普里姆算法是另一种构造最小生成树的算法,它是按逐个将顶点连通的方式来构造最小生成树的。
从连通网络 N = { V, E }中的某一顶点 u0 出发,选择与它关联的具有最小权值的边(u0, v),将其顶点加入到生成树的顶点集合U中。以后每一步从一个顶点在U中,而另一个顶点不在U中的各条边中选择权值最小的边(u, v),把该边加入到生成树的边集TE中,把它的顶点加入到集合U中。如此重复执行,直到网络中的所有顶点都加入到生成树顶点集合U中为止。
假设G=(V,E)是一个具有n个顶点的带权无向连通图,T(U,TE)是G的最小生成树,其中U是T的顶点集,TE是T的边集,则构造G的最小生成树T的步骤如下:
(1)初始状态,TE为空,U={v0},v0∈V;
(2)在所有u∈U,v∈V-U的边(u,v) ∈E中找一条代价最小的边(u′,v′)并入TE,同时将v′并入U;
重复执行步骤(2)n-1次,直到U=V为止。
在普里姆算法中,为了便于在集合U和(V-U)之间选取权值最小的边,需要设置两个辅助数组closest和lowcost,分别用于存放顶点的序号和边的权值。
对于每一个顶点v∈V-U,closest[v]为U中距离v最近的一个邻接点,即边 (v,closest[v]) 是在所有与顶点v相邻、且其另一顶点j∈U的边中具有最小权值的边,其最小权值为lowcost[v],即lowcost[v]=cost[v][closest[v]],采用邻接表作为存储结构:
设置一个辅助数组closedge[]:
lowcost域 存放生成树顶点集合内顶点到生成树外各顶点的各边上的当前最小权值;
adjvex域 记录生成树顶点集合外各顶点距离集合内哪个顶点最近(即权值最小)。
应用Prim算法构造最小生成树的过程:
如下所示为构造生成树的过程中,辅助数组中各分量值的变化情况,初始归U={v1},加入到U集合中的节点,我们将lowcost改成0以示:
/**
* KruscalMinTree.cpp
* @author arhaiyun
* Date : 2013-10-01
*
**/
#include "stdafx.h"
#include <iostream>
#include "Graph.h"
int main(void)
{
adjmatrix g;
InitMatrix(g);
CreateMatrix(g);
int n;
cout << "Input vertex number :";
cin >> n;
cout<<"Adjacent matrix :"<<endl;
PrintMatrix(g, n);
vector<EdgeType*> minTBranches;
Prime(g, minTBranches, n);
PrintTreeBranches(minTBranches);
system("pause");
return 0;
}
=================Graph.h==============================================
#pragma once
#include <fstream>
#include <iomanip>
#include <vector>
#include <set>
#define MaxVerNum 20
#define MaxValue 10000
typedef int adjmatrix[MaxVerNum][MaxVerNum]; //邻接矩阵类型定义
typedef struct Node{
int left;
int right;
int value;
}EdgeType; //指针数组 treeBranches[] 基类型定义
void InitMatrix(adjmatrix G)
{
for(int i = 0; i < MaxVerNum; i++)
{
for(int j = 0; j < MaxVerNum; j++)
{
G[i][j] = MaxValue;
}
}
}
void CreateMatrix(adjmatrix G)
{
fstream in("in.txt", ios::in);
int i, j, value;
cout<<"Input vertex ID and its value:"<<endl;
in >> i >> j >> value;
while(i != -1)
{
G[i][j] = value;
G[j][i] = value;
in >> i >> j >> value;
}
}
void PrintMatrix(adjmatrix G, int n)
{
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
if(G[i][j] == MaxValue)
{
cout << setiosflags(ios::left) << setw(4) << "Inf";
}
else
{
cout << setiosflags(ios::left) << setw(4) << G[i][j];
}
}
cout << endl;
}
}
void Prime(adjmatrix G, vector<EdgeType*> &minTBranches, int n)
{
bool *s = new bool[n];
EdgeType* edge = new EdgeType;
edge->left = edge->right = -1;
edge->value = MaxValue;
//找到第一条最短的边
for(int i = 0; i < n; i++)
{
s[i] = false;
for(int j = 0; j < n; j++)
{
if(i != j && G[i][j] < edge->value)
{
edge->left = i;
edge->right = j;
edge->value = G[i][j];
}
}
}
minTBranches.push_back(edge);
set<int> vertex;
vertex.insert(edge->left);
vertex.insert(edge->right);
s[edge->left] = true;
s[edge->right] = true;
for(int k = 2; k < n; k++)
{
edge = new EdgeType;
edge->left = edge->right = -1;
edge->value = MaxValue;
//每次寻找最短的边加入到生成树
set<int>::iterator iter = vertex.begin();
for(; iter != vertex.end(); iter++)
{
for(int j = 0; j < n; j++)
{
if(s[j] == false && G[*iter][j] < edge->value)
{
edge->value = G[*iter][j];
edge->left = *iter;
edge->right = j;
}
}
}
minTBranches.push_back(edge);
vertex.insert(edge->right);
s[edge->right] = true;
}
}
void PrintTreeBranches(vector<EdgeType*> minTBranches)
{
if(minTBranches.empty())
return;
cout<<"Minimum generate tree:"<<endl;
for(int i = 0; i < minTBranches.size(); i++)
{
cout<<"["<<minTBranches[i]->left<<","<<minTBranches[i]->right<<"]"<<minTBranches[i]->value<<endl;
}
}
1 4 3
4 5 6
5 3 2
3 0 5
1 2 5
2 3 5
2 4 6
2 5 4
-1 2 3
0 2 1