一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
一.普利姆算法
从图中任意取出一个顶点,把他当作一棵树,然后从这棵树相接的边中选取一条最短(权值最小)的边,并将这条边及其所连接的顶点也并入这棵树中,此时得到一颗有两个顶点的树。然后在这棵树中相连的顶点中选取最短的边,并将图中的所有顶点并入树中为止,此时得到的树就是最小生成树。
流程如图:
/*普利姆算法*/ void Prim(Graph g, int v0, int &sum) {//任意开始节点v0,得到边的总长度 int lowcost[MAX_NUM], vset[MAX_NUM], v;//邻接节点的最短路径,被访问的标记,最后访问的节点标记 int i, j, k, min; v = v0; for (int i = 0; i < g.vetexs; ++i) { lowcost[i] = g.arcs[v0][i]; vset[i] = 0; } vset[v0] = 1;//将v0并入树中 sum = 0;//sum清零用来累计树的权值 for (i = 0; i < g.vetexs; ++i) { min = INF;//INF是一个比图中所有边权值都要大的树 for (j = 0; j < g.vetexs; ++j) //选出当前生成树到其一顶点最短边中的一条 if (vset[j] == 0 && lowcost[j] < min) { min = lowcost[j]; k = j; } vset[j] = 1; v = k; sum += min;//记录最小生成树的总权值,也可以换成其他操作 /*下面这个循环一刚并入的顶点v为媒介更新候选边*/ for (j = 0; j < g.vetexs; ++j) if (vset[j] == 0 && g.arcs[v][j] < lowcost[j]) lowcost[j] = g.arcs[v][j]; } }
二.克鲁斯卡算法
基本思想:(1)构造一个只含n个顶点,边集为空的子图。若将图中各个顶点看成一棵树的根节点,则它是一个含有n棵树的森林。(2)从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图。也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之(3)依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。
大白话:(1)将图中的所有边都去掉。(2)将边按权值从小到大的顺序添加到图中,保证添加的过程中不会形成环(3)重复上一步直到连接所有顶点,此时就生成了最小生成树。这是一种贪心策略。
typedef struct { int a, b;//a和b是一条边的两个顶点 int w;//边的权值 }Road; Road road[MAX_NUM]; int v[MAX_NUM];//定义并查集数组 int getRoot(int a) { //在并查集中查找a的根节点的函数 while (v[a] != -1)a = v[a];//结点a与v[a]是否连接(或者间接即多跳连接) //着(在边子集中)同一个结点 ,注意!!此处是循环while而非判断if return a; } void Kruskal(Graph g, int &sum, Road *road) { int i; int N, E, a, b; N = g.vetexs;//节点数 E = g.brim;//边数 sum = 0; for (i = 0; i < g.vetexs; ++i)//初始化并查集 v[i] = -1; sort(road, E);//排序操作,使数组按权值有小到大排序 for (i = 0; i < E; ++i) { a = getRoot(road[i].a); b = getRoot(road[i].b); if (a != b)//判断结点road[i].a与road[i].b是否连 //接(或者间接即多跳连接)着(在边子集中)同一个结点, //注意:假设结点edges[i].a与结点edges[i].b都跟量 //外一个结点X相连(或者间接相连),如若不加判断,则三个结点会形成回路 { v[a] = b; sum += road[i].w;//求生成树的权值,这句并不是本算法的固定 //写法,可以换成其他的,,例如输出各边 } } }