最小生成树
最小生成树是指对于一个带边权的图,要求删去一些边,使点之间仍相连且剩下的边权尽可能小。以下给出两种方法,都用到了贪心法:
①Prim:先建立两个集合S和T,分别表示已连接和未连接的点集。开始时把在S中加入任意一个点x,并在T中加入1到n所有除x以外的点。然后每次找出T中连接到S边权最小的一个点把它加入S,同时从T删除,直到T为空。主要程序段如下:
int lowcost[MAXN],closest[MAXN]; int prim(int v0) { int i,j,mindis,minone; int ans = 0;/*用来记录最小生成树的总长度*/ /*各点距离初始化*/ for(i = 0;i < n;i++) { lowcost[i] = cost[v0][i]; closest[i] = v0; } for(i = 0;i < n-1;i++) { mindis = UPPERDIS; for(j = 0;j < n;j++) if(lowcost[j] && mindis > lowcost[j]) { mindis = lowcost[j]; minone = j; } /*将找到的最近点加入最小生成树*/ ans += lowcost[minone]; lowcost[minone] = 0; /*修正其他点到最小生成树的距离*/ for(j = 0;j < n;j++) if(cost[minone][j] < lowcost[j]) { lowcost[j] = cost[j][minone]; closest[j] = minone; } } return ans; }
时间复杂度为O(n^2)
②Kruskal:这种方法很简单,也很高效。先把边从小到大排序,依次枚举边并判断其两点是否在同一连通块(即是否已相连),若没有就加入该边。朴素算法时间复杂度为O(n^2),可用并查集或斐波那契堆加速,以下给出并查集加速代码(网上找来的,莫名地长):
#include <stdio.h> #include <stdlib.h> #define MAX 100 /* 定义边(x,y),权为w */ typedef struct{ int x, y; int w; }edge; edge e[MAX]; /* rank[x]表示x的秩 */ int rank[MAX]; /* father[x]表示x的父节点 */ int father[MAX]; int sum; /* 比较函数,按权值(相同则按x坐标)非降序排序 */ int cmp(const void *a, const void *b){ if ((*(edge *)a).w == (*(edge *)b).w){ return (*(edge *)a).x - (*(edge *)b).x; } return (*(edge *)a).w - (*(edge *)b).w; } /* 初始化集合 */ void Make_Set(int x){ father[x] = x; rank[x] = 0; } /* 查找x元素所在的集合,回溯时压缩路径 */ int Find_Set(int x){ if (x != father[x]){ father[x] = Find_Set(father[x]); } return father[x]; } /* 合并x,y所在的集合 */ void Union(int x, int y, int w){ if (x == y) return; /* 将秩较小的树连接到秩较大的树后 */ if (rank[x] > rank[y]){ father[y] = x; } else{ if (rank[x] == rank[y]){ rank[y]++; } father[x] = y; } sum += w; } /* 主函数 */ int main(){ int i, n; int x, y; char chx, chy; /* 读取边的数目 */ scanf("%d", &n); getchar(); /* 读取边信息并初始化集合 */ for (i = 0; i < n; i++){ scanf("%c %c %d", &chx, &chy, &e[i].w); getchar(); e[i].x = chx - 'A'; e[i].y = chy - 'A'; Make_Set(i); } /* 将边排序 */ qsort(e, n, sizeof(edge), cmp); sum = 0; for (i = 0; i < n; i++){ x = Find_Set(e[i].x); y = Find_Set(e[i].y); if (x != y){ printf("%c - %c : %d\n", e[i].x + 'A', e[i].y + 'A', e[i].w); Union(x, y, e[i].w); } } printf("Total:%d\n", sum); return 0; }
并查集加速后,时间复杂度为O(n*log(n))。