赞
踩
构造最小生成树的方法多数利用了最小生成树的MST性质:
设N=(V,{E})是连通网,TE是N上最小生成树中边的集合
附设一个辅助数组closedge,用以记录从V-U中的各个顶点到U的具有最小代价的边。
对于每一个顶点vi∈V-U,相应的分量为closedge[i-1],它应该包括两个域:
closedge[i-1].lowcost = min{ cost ( u , vi ) | u ∈ U }
(网采用邻接矩阵表示法)
U中每增加一个顶点,只要考虑该新增顶点到vi这条边上的权值会不会更小即可。
closedge数组的组成部分: | - |
---|---|
lowcost | Adjvex |
储存该边上的权 | 存储该边依附在U中的顶点 |
closedge数组的应用举例:
(答案不唯一)
每次加入一个点,就看该点与其他点的关系,如果有比当前closedge数组中的权值更小的,就更新lowcost和adjvex,同时去掉原来的那条线。
算法实现:(图的组成结构参照数据结构之图论算法(一)图的存储结构及其构建算法中的邻接表表示)
//记录从顶点集U到V-U的代价最小的边的辅助closedge数组定义: struct closedgetype{ VertexType adjvex; //存储最小权值边所连接的另一个点的地址 VRType lowcost; //存储边的最小权值 }closedge[MAX_VERTEX_NUM]; int GetWeight(Graph G, int start, int end){//start为起点地址,end为终点地址,该函数用于求两点间的距离; if(start == end) return 0; //如果起点和终点为同一个,就返回0 adjVert *p = G.v[start].firstarc; //邻接表的探索的管用方法,先用p去指向邻接顶点相连的第一条边 while(p){ if(end == p->adjvert) return p->edgeInfo; //如果end==p所指的边连接的下一顶点,就说明这两点存在边,权值大小就是这条边的值 p = p->next; //如果不相等,就看下一条与start相连的边 } //如果p==NULL了还没有return,说明start与end之间没有直接相连的边,则返回无穷大; return INFINITY; } int MinCostVert(closedgetype closedge[], int sum){ int i, minvert = 0; int min = INFINITY; //初始化 for(i = 0; i < sum; i++){ if(closedge[i].lowcost < min){ min = closedge[i].lowcost; minvert = closedge[i].adjvex; } } return minvert; } void MiniSpanTree_PRIM(Graph G, int u){ //用Prim算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边; int k = u-1; //找到点u对应的顶点地址; int sumcost = 0; //用于求总代价 int j, i; //初始化:U = {V1},只有一个点 for(j = 0 ; j < G.n; j++){ closedge[j].adjvex = k; //表示每一个点如果都与u点相连的话,最小代价为多少 closedge[j].lowcost = GetWeight(G, k, j); //从u(起点地址)到j(终点地址) } for(i = 1; i < G.n; i++){ //从当前closedge中找到最小的邻接结点的地址k; k = MinCostVert(closedge, G.n); sumcost += closedge[k].lowcost; //把k点并入集合U中: closedge[k].lowcost = 0; //重新选择最小边: for(j = 0;j < G.n; j++){ if(GetWeight(G, i, j) < closedge[j].lowcost){ closedge[j].adjvex = i; closedge[j].lowcost = GetWeight(G,i,j); } } } printf("%d\n", sumcost); //输出最小代价 }
如果图的构成是邻接矩阵的话,则代码可参考如下:
int MinCostVert(closedgetype closedge[], int sum){ int i, minvert = 0; int min = INFINITY; //初始化 for(i = 0; i < sum; i++){ if(closedge[i].lowcost < min){ min = closedge[i].lowcost; minvert = closedge[i].adjvex; } } return minvert; } void MiniSpanTree_PRIM(Graph G, int u){ //用Prim算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边; int k = u-1; //找到点u对应的顶点地址; int sumcost = 0; //用于求总代价 int j, i; //初始化:U = {V1},只有一个点 for(j = 0 ; j < G.n; j++){ if(j != k){ closedge[j].adjvex = k; //表示每一个点如果都与u点相连的话,最小代价为多少 closedge[j].lowcost = G.arcs[k][j].adj; } } closedge[k].lowcost = 0; for(i = 1; i < G.n; i++){ //从当前closedge中找到最小的邻接结点的地址k; k = MinCostVert(closedge, G.n); sumcost += closedge[k].lowcost; //把k点并入集合U中: closedge[k].lowcost = 0; //重新选择最小边: for(j = 0;j < G.n; j++){ if(G.arcs[k][j].adj < closedge[j].lowcost){ closedge[j].adjvex = i; closedge[j].lowcost = G.arcs[k][j].adj; } } } printf("%d\n", sumcost); //输出最小代价 }
大体不变,主要是把GetWeight函数换成了二维数组可以表示的值。
设连通网N =(V,{E})
用伪代码可表示为:
把所有边排序,记第i小的边为e[i](1<=i<m)//m为总边数;
初始化MST为空;
初始化连通分量,让每个点自成一个独立的连通分量;
for(int i = 0; i < m; i++){
if(e[i].u 和 e[i].v 不在同一个连通分量里){
把边e[i]并入MST;
合并e[i].u 和 e[i].v 所在的连通分量;
}
}
在上述的伪代码中,关键在于连通分量的查询与合并:
如同Prim算法要借助closedge数组一样,Kruskal算法需要使用并查集。
可以把每个连通分量看成一个集合,该集合包含了连通分量中的所有点,这些点两两相通,而具体的连通方式无关紧要,类似于集合中的元素没有先后顺序之分,只有“属于”和“不属于”之分。
图的所有连通分量可以用若干个不相交的集合表示,而集合又通过树来表示。
例如:包含点{1,2,3,4,5,6}的图有3个连通分量{{1,2}, {3,4,5}, {6}},那么就需要用3棵树来表示。规定每棵树的根节点是这棵树所对应的集合的代表元。
代表元存储在 p[ v ] 数组(parent)中,即把结点 vi 的父节点存储在 p[ i ] 中,因此可以写出“查找x所在树的根节点”的递归程序:(其中若 vi 没有父节点了,则p[i] = vi 本身)
(以邻接表为例)
int Findroot(int x){
return p[x] == x ? x : Findroot(p[x]); //如果x没有父节点了,即p[x] == x,就返回x;
//否则继续递归寻找根节点
}
为了提高效率,就将遍历过的结点都改成树根的子节点;因此上述代码改进为:
int Findroot(int x){
return p[x] == x ? x : p[x] = Findroot(p[x]);
}
这样,Kruscal算法的完整代码如下:
int CountArcs(Graph G){ int sum = 0; for(int i = 0; i <G.n; i++){ adjVert *p = G.v[i].firstarc; while(p){ sum++; p = p->next; } } return sum; } void setvalue_rank(Graph G,int *r, int *w, int *u, int *v){ int e = 0; for(int i = 0; i < G.n; i++){ adjVert *arcp = G.v[i].firstarc; while(arcp){ w[e] = arcp->edgeInfo; u[e] = i; v[e] = arcp->adjvert; arcp = arcp->next; e++; } } for(int i = 0; i <= e-1; i++){ for(int j = i+1; j<= e; j++){ if(w[i] > w[j]){ r[i] = j; r[j] = i; } } } } int Findroot(int x, int *p){ return p[x] == x ? x : p[x] = Findroot(p[x], p); } //第i条边的两个端点分别储存在u[i],v[i]中,其权值储存在w[i]中; int Kruscal(Graph G){ int m = CountArcs(G), ans = 0; int p[MAX_VERTEX_NUM], *w, *r, *u, *v; int i, j, e; w = (int*)malloc(m * sizeof(int));r = (int*)malloc(m * sizeof(int)); u = (int*)malloc(m * sizeof(int));v = (int*)malloc(m * sizeof(int)); //初始化: for(i = 0; i < m; i++) r[i] = i; for(i = 0; i < G.n;i++) p[i] = i; //赋值并排序 setvalue_rank(G,r,w,u,v); for(i = 0; i < m; i++){ e = r[i]; int x = Findroot(u[e], p); int y = Findroot(v[e],p); if(x != y){ans+=w[e]; p[x] = y;} } return ans; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。