有向图的强连通分量(最小生成树)
// algo7-2.cpp 实现算法7.9的程序 #include"c1.h" typedef int VRType; typedef char InfoType; #define MAX_NAME 3 // 顶点字符串的最大长度+1 #define MAX_INFO 20 // 相关信息字符串的最大长度+1 typedef char VertexType[MAX_NAME]; #include"c7-1.h" #include"bo7-1.cpp" typedef struct { // 记录从顶点集U到V-U的代价最小的边的辅助数组定义(见图7.55) VertexType adjvex; VRType lowcost; }minside[MAX_VERTEX_NUM]; int minimum(minside SZ,MGraph G) { // 求SZ.lowcost的最小正值,并返回其在SZ中的序号 int i=0,j,k,min; while(!SZ[i].lowcost) i++; min=SZ[i].lowcost; // 第一个不为0的值 k=i; for(j=i+1;j<G.vexnum;j++) if(SZ[j].lowcost>0&&min>SZ[j].lowcost) // 找到新的大于0的最小值 { min=SZ[j].lowcost; k=j; } return k; } void MiniSpanTree_PRIM(MGraph G,VertexType u) { // 用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边。算法7.9 int i,j,k; minside closedge; k=LocateVex(G,u); for(j=0;j<G.vexnum;++j) // 辅助数组初始化 { strcpy(closedge[j].adjvex,u); closedge[j].lowcost=G.arcs[k][j].adj; } closedge[k].lowcost=0; // 初始,U={u} printf("最小代价生成树的各条边为\n"); for(i=1;i<G.vexnum;++i) { // 选择其余G.vexnum-1个顶点 k=minimum(closedge,G); // 求出T的下一个结点:第k顶点 printf("(%s-%s)\n",closedge[k].adjvex,G.vexs[k]); // 输出生成树的边 closedge[k].lowcost=0; // 第k顶点并入U集 for(j=0;j<G.vexnum;++j) if(G.arcs[k][j].adj<closedge[j].lowcost) { // 新顶点并入U集后重新选择最小边 strcpy(closedge[j].adjvex,G.vexs[k]); closedge[j].lowcost=G.arcs[k][j].adj; } } } void main() { MGraph g; CreateUDN(g); // 构造无向网g Display(g); // 输出无向网g MiniSpanTree_PRIM(g,g.vexs[0]); // 用普里姆算法从第1个顶点出发输出g的最小生成树的各条边 }
代码的运行结果 :
请输入无向网G的顶点数,边数,边是否含其它信息(是:1,否:0): 6,10,0
请输入6个顶点的值(<3个字符):
V1 V2 V3 V4 V5 V6
请输入10条边的顶点1 顶点2 权值(以空格作为间隔):
V1 V2 6
V1 V3 1
V1 V4 5
V2 V3 5
V2 V5 3
V3 V4 5
V3 V5 6
V3 V6 4
V4 V6 2
V5 V6 6
6个顶点10条边或弧的无向网。顶点依次是: V1 V2 V3 V4 V5 V6 (见图756)
G.arcs.adj:
32767 6 1 5 32767 32767
6 32767 5 32767 3 32767
1 5 32767 5 6 4
5 32767 5 32767 32767 2
32767 3 6 32767 32767 6
32767 32767 4 2 6 32767
G.arcs.info:
顶点1(弧尾) 顶点2(弧头) 该边或弧的信息:
最小代价生成树的各条边为
(V1-V3)
(V3-V6)
(V6-V4)
(V3-V2)
(V2-V5)
图757 是根据以上程序运行的例子,显示了算法7.9(普里姆算法)求最小生成树的
过程。首先,主程序构造了图756 所示的无向网。然后,调用MiniSpanTree_PRIM(),
由顶点V1 开始,求该网的最小生成树。这样,最小生成树顶点集最初只有V1,其中用到
了辅助数组closedge[]。closedge[i].lowcost 是最小生成树顶点集中的顶点到i 点的最小权
值。若i 点属于最小生成树,则closedge[i].lowcost=0。closedge[i].adjvex 是最小生成树
顶点集中到i 点为最小权值的那个顶点。图757(a)显示了closedge[]的初态。这时最小
生成树顶点集中只有V1,所以closedge[i].adjvex 都是V1,closedge[i].lowcost 是V1 到i
的权值。closedge[0].lowcost=0 , 说明V1 已属于最小生成树顶点集了。在
closedge[].lowcost 中找最小正数,closedge[2].lowcost=1,是最小正数。令k=2,将V3
并入最小生成树的顶点集(令closedge[2].lowcost=0),输出边(V1—V3)。因为V3 到
V2、V5 和V6 的权值小于V1 到它们的权值,故将它们的closedge[].lowcost 替换为V3
到它们的权值;将它们的closedge[].adjvex 替换为V3,如图757(b)所示。重复这个过
程,依次如图757(c)、(d)和(e)所示。最后,closedge[]包含了最小生成树中每一条边
的信息。
// algo7-8.cpp 克鲁斯卡尔算法求无向连通网的最小生成树的程序 #include"c1.h" typedef int VRType; typedef char InfoType; #define MAX_NAME 3 // 顶点字符串的最大长度+1 #define MAX_INFO 20 // 相关信息字符串的最大长度+1 typedef char VertexType[MAX_NAME]; #include"c7-1.h" #include"bo7-1.cpp" void kruskal(MGraph G) { int set[MAX_VERTEX_NUM],i,j; int k=0,a=0,b=0,min=G.arcs[a][b].adj; for(i=0;i<G.vexnum;i++) set[i]=i; // 初态,各顶点分别属于各个集合 printf("最小代价生成树的各条边为\n"); while(k<G.vexnum-1) // 最小生成树的边数小于顶点数-1 { // 寻找最小权值的边 for(i=0;i<G.vexnum;++i) for(j=i+1;j<G.vexnum;++j) // 无向网,只在上三角查找 if(G.arcs[i][j].adj<min) { min=G.arcs[i][j].adj; // 最小权值 a=i; // 边的一个顶点 b=j; // 边的另一个顶点 } min=G.arcs[a][b].adj=INFINITY; // 删除上三角中该边,下次不再查找 if(set[a]!=set[b]) // 边的两顶点不属于同一集合 { printf("%s-%s\n",G.vexs[a],G.vexs[b]); // 输出该边 k++; // 边数+1 for(i=0;i<G.vexnum;i++) if(set[i]==set[b]) // 将顶点b所在集合并入顶点a集合中 set[i]=set[a]; } } } void main() { MGraph g; CreateUDN(g); // 构造无向网g Display(g); // 输出无向网g kruskal(g); // 用克鲁斯卡尔算法输出g的最小生成树的各条边 }
代码的运行结果:
请输入无向网G的顶点数,边数,边是否含其它信息(是:1,否:0): 6,10,0
请输入6个顶点的值(<3个字符):
V1 V2 V3 V4 V5 V6
请输入10条边的顶点1 顶点2 权值(以空格作为间隔):
V1 V2 6
V1 V3 1
V1 V4 5
V2 V3 5
V2 V5 3
V3 V4 5
V3 V5 6
V3 V6 4
V4 V6 2
V5 V6 6
6个顶点10条边或弧的无向网。顶点依次是: V1 V2 V3 V4 V5 V6 (见图756)
G.arcs.adj:
32767 6 1 5 32767 32767
6 32767 5 32767 3 32767
1 5 32767 5 6 4
5 32767 5 32767 32767 2
32767 3 6 32767 32767 6
32767 32767 4 2 6 32767
G.arcs.info:
顶点1(弧尾) 顶点2(弧头) 该边或弧的信息:
最小代价生成树的各条边为
(V1-V3)
(V4-V6)
(V2-V5)
(V3-V6)
(V2-V3)
图758 是根据以上程序运行的例子,显示了克鲁斯卡尔算法求最小生成树的过程。
首先,主程序构造了图756 所示的无向网。然后,调用kruskal(),求该网的最小生成
树。其中用到了辅助数组set[]。set[i]表示第i 个顶点所在的集合。设初态set[i]=i,6 个
顶点分属于6 个集合,如图758(a)所示。在邻接矩阵的上三角中找权值最小的边(因为
是无向网),边(V1—V3)的权值最小,将V1 和V3 并到1 个集合中。方法是将V3 的集合
set[2]赋值为set[0]( V1 的集合),同时将该边删除(令其上在三角的值为无穷)并输出该
边,如图758(b)所示。用此方法依次将V4 和V6、V2 和V5 分别并到1 个集合中,如
图758(c)、图758(d)所示。这时,邻接矩阵上三角中权值最小的边是(V3—V6),这
两顶点分属于两个集合0 和3。将集合3 合并到集合0 中。方法是把集合3 中的V4、V6
都并到集合0 中,如图758(e)所示。这时在邻接矩阵的上三角中首先找到的权值最小边
是(V1—V4),但它们属于同一个集合(set[0]=set[3]=0),删除该边,继续查找。找到
(V2—V3)是权值最小的边,且它们分属于不同的集合。把V3 所在集合中的顶点都并到
V2 所在集合中,使所有顶点都在集合1 中,如图758(f)所示,最后构成了最小生成
树。程序运行结果和普里姆算法的一样。
需要指出的是,虽然在kruskal()中修改了无向网的邻接矩阵,由于kruskal()的形参不
是引用类型,故在主调函数中并没有改变图的结构。
每当夜深人静的时候,想想今天发生了什么,失去了什么,得到了什么,做了什么,没做什么,该做什么,不该做什么,明天要做什么!