目录
生成树:
所有顶点均由边连接在一起,但不存在回路。
举例:
这个图可以有很多棵不同的生成树,如:
特点:
生成树的顶点个数与图的顶点个数相同
生成树是图的极小连通子图,去掉一条边则非连通
一个有n个顶点的连通图的生成树有n-1条边
在生成树中再加一条边必然形成回路
生成树中任意两个顶点间的路径是唯一的
含有n个顶点,n-1条边的图不一定是生成树,如:
原因:
1.不连通
2.存在回路
无向图的生成树:
方法:
用深度优先遍历算法(DFS)或者是广度优先遍历算法(BFS)去访问每一个顶点,然后将这些顶点和遍历过程中它所经过的边来共同构成一个生成树。
深度优先生成树:
举例:
它的遍历过程为:v1->v2->v4->v8->v5->v8->v4->v2->v1->v3->v6->v7->v6->v3->v1。
它遍历过程中所经过的边为图中蓝色部分,所以这棵生成树就是由图中所有的顶点和蓝色部分的边所共同构成的。
广度优先生成树:
举例:
它的遍历过程为:v1->v2->v3->v4->v5->v6->v7->v8。
它遍历过程中所经过的边为图中蓝色部分,所以这棵生成树就是由图中所有的顶点和蓝色部分的边所共同构成的。
最小生成树:
定义:
给定一个无向网络,在该网的所有生成树中,使得各边权值之和最小的那棵生成树称为该网的最小生成树,也叫最小代价生成树。
举例:
它的生成树有:
权值之和最小的即为该图的最小生成树。
构造最小生成树:
构造最小生成树的算法很多,其中多数算法都利用了MST的性质。
MST性质:
定义:
举例:
集合U中只有v1一个顶点,而集合V-U中有 v2,v3,v4,v5,v6 共5个顶点,然后从U到V-U中共有三条边,权值分别是6,1,5,把其中权值最小的那条边(图中那条蓝色的线)包含到我们所要构造的最小生成树当中。
解释:
举例:
图中从集合U中到集合V-U的所有边中,红色的这 条边即为权值最小的边。
两大经典算法:
普里姆(Prim)算法:
算法思想:
这个最终得到的最小生成树就由图中所有的顶点和蓝色的边所构成。
步骤:
从v1开始构造,选中v1,然后利用MST性质找到最小权值的边,将这条边相关联的顶点选到最小生成树中去,即加入U集合。
重复以上操作:在v1和v2,v1和v4,v3和v2,v3和v4,v3和v5,v3和v6中找到权值最小那条边,将该边与它相连的顶点加入到所要构造的最小生成树中去(即集合U)。
重复以上的操作:在v1和v2,v1和v4,v3和v2,v3和v4,v3和v5,v6和v5,v6和v4中找到权值最小那条边,将该边与它相连的顶点加入到所要构造的最小生成树中去(即集合U)。
重复以上的操作:在v1和v2,v3和v2,v3和v5,v6和v5中找到权值最小那条边,将该边与它相连的顶点加入到所要构造的最小生成树中去(即集合U)。
重复以上的操作:在v2和v5,v3和v5,v6和v5中找到权值最小那条边,将该边与它相连的顶点加入到所要构造的最小生成树中去(即集合U)。
这时候,所有的顶点都包含在里面了,就结束了,由所有的顶点和图中蓝色的边所共同构成的即为我们的最小生成树。
算法:
void MiniSpanTree_Prim(AMGraph G,VerTexType u)
{
//无向网G以邻接矩阵形式存储,从顶点u出发构造G的最小生成树T,输出T的各条边
int k=LocateVex(G,u);//k为顶点u的下标
for(int j=0;j<G.vexnum;j++)//对V-U的每一个顶点vj,初始化为closedge[j]
{
if(j!=k)
{
closedge[j]=(u,G.arcs[k][j]);//(adjvex,lowcost)
}
}
closedge[k].lowcost=0//初始,U={u}
for(int i=1;i<G.vexnum;i++)
{
//选择其余n-1个顶点,生成n-1条边(n=G.vexnum)
k=Min(closedge);
//求出T的下一个结点:第k个顶点,closrdge[k]中存有最小边
u0=closedge[k].adjvex;//u0为最小边的一个顶点,u0属于U
v0=G.vexs[k];//v0为最小边的另一个顶点,v0属于V-U
cout<<u0<<v0;//输出当前的最小边(u0,v0)
closedge[k].lowcost=0;//第k个顶点并入U集
for(int j=0;j<G.vexnum;j++)
{
if(G.arcs[k][j]<closedge[j].lowcost)//新顶点并入U后重新选择最小边
{
closedge[j]=(G.vexs[k],G.arcs[k][j]);
}
}
}
}
克鲁斯卡尔(Kruskal)算法:
算法思想:
它可以形成几种不同的最小生成树,原因是因为图中权值为5的边有好多条。
我们以其中一个最小生成树为例:
生成步骤:
首先先将5个顶点都包含到最小生成树当中
将所有的边按照它的权值排好序,找权值最小的边,加入到最小生成树当中
继续找权值最小的边,加入到最小生成树当中
继续找权值最小的边,加入到最小生成树当中
继续找权值最小的边,加入到最小生成树当中
我们发现此刻权值最小的边的权值为5,但是有好多条,我们选择哪一条都行,前提是不能构成回路,如果出现了构成回路的情况,那就舍弃那条边,不加它,重新再找。上面的例子中我们是选了v2和v3这条边,构成了以下的最小生成树:
算法:
void MiniSpanTree_Kruskal(AMGraph G)
{
//无向网G以邻接矩阵形式存储,构造G的最小生成树T,输出T的各条边
Sort(Edge);//将数组Edge中的元素按权值从小到大排序
for(int i=0;i<G.vexnum;i++)//辅助数组,表示各顶点自成一个连通分量
{
Vexset[i]=i;
}
for(int i=0;i<G.arcnum;i++)//依次查看数组Edge中的边
{
int v1=LocateVex(G.Edge[i].Head);//v1为边的始点Head的下标
int v2=LocateVex(G.Edge[i].Tail);//v2为边的终点Tail的下标
int vs1=Vexset[v1];//获取边Edge[i]的始点所在的连通分量vs1
int vs2=Vexset[v2];//获取边Edge[i]的始点所在的连通分量vs2
if(vs1!=vs2)//边的两个顶点分属于不同的连通分量
{
cout<<Edge[i].Head<<Edge[i].Tail;//输出此边
for(int j=0;j<G.vexnum;j++)//合并vs1和vs2两个分量,即两个集合统一编号
{
if(Vexset[j]==vs2)//集合编号为vs2的都改为vs1
{
Vexset[j]=vs1;
}
}
}
}
}
两种算法的比较: