最小生成树
假设在n个城市中要建立通信网络,那么连接n个点需要n-1条边。任意两个城市间建立通信网络都是需要费用的,这个可以认为是边的权值,用来表示响应的代价。对于n个顶点的连通图可以构建不同的生成树,而这个代价最小的生成树称为最小生成树。
最小生成树的MST特性,假设N = (V,{E})是一个连通网,U是顶点集V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u属于U,v属于V-U,则必存在一颗包含(u,v)的最小生成树,证明略。根据MST,求最小生成树有两种方法,普利姆(prim)和克鲁斯卡尔(kruskal)。
Prim算法
假设N = (V,{E}),TE是最小生成树中边的集合。算法从U = {u0}, u属于V,TE = {}开始,重复执行以下操作:在所有u属于U,v属于V - U的边(u,v)属于E中找一条权值最小的边(u0,v0)并入集合TE,同时v0并入U,直到 U = V为止。此时TE中必有n-1条边,则T = (V,{TE})为N的最小生成树。
如图,计算过程如下:
closedge/i | 1 | 2 | 3 | 4 | 5 | U | V - U | k |
adjvex lowcost | v1 6 | v1 1 | v1 5 | {v1} | {v2, v3,v4, v5, v6} | 2 | ||
adjvex lowcost | v3 5 |
0 | v1 5 | v3 6 | v3 4 | {v1, v3} | {v2, v4, v5, v6} | 5 |
adjvex lowcost | v3 5 |
0 | v6 2 | v3 6 |
0 | {v1, v3 v6} | {v2, v4 v5} | 3 |
adjvex lowcost | v3 5 |
0 |
0 | v3 6 |
0 | {v1, v3, v6, v4} | {v2, v5} | 1 |
adjvex lowcost |
0 |
0 |
0 | v2 3 |
0 | {v1, v3, v6 v4, v2} | {v5} | 4 |
adjvex lowcost |
0 |
0 |
0 |
0 |
0 | {v1,v3, v6 v4, v2, v5} | { } |
//由于每个顶点都需要与其他的顶点比较找寻lowcost,所以本算法采用邻接矩阵存储,
//并利用辅助数组closedge来存放每个顶点的最小cost
void Prim(MGraph *G, int v){
k = v;
struct Closedge closedge[G -> vexnum];
for(int i = 0; i < G -> vexnum; i++){
if(i != k)
closedge[i]= G -> arcs[k][i];
}
closedge[k].lowcost = 0;
for(int i = 1; i < G -> vexnum; i++){
k = min_closedge(closedge, G -> vexnum); //寻找不为0的最小权值的顶点
cout << k << " " << closedge[k].adjvex;
for(int j = 0; j < G -> vexnum; j++){
if(G -> arcs[k][j] < closedge[j])
closedge[j] = G -> arcs[k][j];
}
}
}
int min_closedge(struct Closedge closedge[], int n){
int min = INT_MAX, ans = 0;
for(int i = 0; i < n; i++)
if(closedge[i]!= 0 && min > closedge[i]){
min = closedge[ans = i];
}
return ans;
}
Prim算法时间复杂度为O(n^2),与顶点有关,与边无关,适用于稠密图。
Kruskal算法
假设连通网 N = (V,{ E }),令最小生成树的初始状态为只有n个顶点而无边的非连通图 T = (V,{ }),图中的每个顶点自称一个连通分量。在E中选取边中权值最小的,如果将此边加入T不产生回路,那么就将其加入T,否则舍去此边,寻找下一个最小权值的边,直到T中所有顶点都在同一连通分量上。由此可以看出Kruskal是基于边来做的,又每次都是挑选权值最小的边,所以在这里可以用一个指针数组存储边信息,并对其按权值由小到大排序,这样按数组由小到大遍历一次即可。为了判断新加入的边是否会构成回路,使用一个辅助数组 parent 来判断,parent[ i ] = k,表示顶点 i 和顶点 k 在边集合A中,而当k = 0时,表示集合A暂时到头。
#define MAX_NODE 20
struct Node{
int begin, end, weight;
};
struct Node edge[MAX_NODE];
//此处的读入输入数据,并对edge排序省略
void kruskal(){
int i = 0, n = 0, m = 0;
int parent[MAX_NODE];
for(; i < MAX_NODE; i++)
parent[i] = i;
for(i = 0; i < MAX_NODE; i++){
n = find(parent, edge[i].begin);
m = find(parent, edge[i].end);
if(n != m){
parent[n] = m;
cout << edge[i].begin << " " << edge[i].end << " " << edge.weight << endl;
}
}
}
int find(int parent[], int v){
while(parent[v] != v)
v = parent[v];
return v;
}
由于find时间复杂度O(loge),一共循环e次,所以kruskal时间复杂度为O(eloge),只和边个数有关,适合稀疏图。