最小生成树
要解决最小生成树问题,首先要了解什么是最小生成树。生成树指选取V-1条边(V为图的顶点个数),将图的V的顶点连通,且没有回路的图。最小生成树指这些边的权重之和最小。
解决最小生成树问题,主要依赖贪心算法,即在不形成回路的约束条件下,每一次都选取权重最小的边。我们有两种贪心算法。
第一种是Prim算法。我们任意选择一个顶点开始,将该顶点加入到最小生成树中,然后选取与最小生成树相连的权重最小的且不会形成回路的边,将这条边连通的顶点加入到最小生成树中。选取V-1次后形成了最小生成树。
1 #include <stdio.h> 2 3 #define maxVsize 1050 4 #define inf 0x7ffffff 5 6 //顶点与边的总数 7 int v, e; 8 int EDGE[maxVsize][maxVsize]; 9 10 //用于记录顶点与最小生成树的最小距离 11 int dis[maxVsize]; 12 13 void Prim(void); 14 void initialize(void); 15 int findmin(void); 16 17 int main() { 18 scanf("%d %d", &v, &e); 19 20 initialize(); 21 22 int i; 23 for (i = 0; i < e; ++i) { 24 int from, to, w; 25 scanf("%d %d %d", &from, &to, &w); 26 EDGE[from][to] = w; 27 EDGE[to][from] = w; 28 } 29 30 Prim(); 31 32 return 0; 33 } 34 35 void Prim(void) { 36 int total = 0; //记录权重之和 37 int i; 38 for (i = 1; i <= v; ++i) { 39 dis[i] = EDGE[1][i]; 40 } 41 42 for (i = 1; i < v; ++i) { 43 //选取与生成树相连的边中权重最小且不会形成回路的边所连接的点 44 int x = findmin(); 45 46 //若dis[x]=inf,说明图不连通,不可能存在最小生成树 47 if (dis[x] == inf || x == 0) { 48 total = -1; 49 break; 50 } 51 52 //将顶点加入最小生成树 53 total += dis[x]; 54 dis[x] = 0; 55 56 //遍历与新加入顶点直接相连的顶点,更新dis 57 int j; 58 for (j = 1; j <= v; ++j) { 59 if (dis[j] && dis[j] > EDGE[x][j]) { 60 dis[j] = EDGE[x][j]; 61 } 62 } 63 } 64 65 printf("%d\n", total); 66 } 67 68 void initialize(void) { 69 int i, j; 70 for (i = 1; i <= v; ++i) { 71 dis[i] = inf; 72 for (j = 1; j <= v; ++j) { 73 if (i == j) { 74 EDGE[i][j] = 0; 75 } 76 else { 77 EDGE[i][j] = inf; 78 } 79 } 80 } 81 dis[1] = 0; 82 } 83 84 int findmin(void) { 85 int mincost = inf, minVertex = 0; 86 int i; 87 for (i = 1; i <= v; ++i) { 88 if (dis[i] && dis[i] < mincost) { 89 mincost = dis[i]; 90 minVertex = i; 91 } 92 } 93 return minVertex; 94 }
我们还可以采用堆的方法来优化Prim算法。
1 #include <stdio.h> 2 3 #define maxVsize 1050 4 #define inf 0x7ffffff 5 6 //顶点与边的总数 7 int v, e; 8 int EDGE[maxVsize][maxVsize]; 9 10 struct { 11 int num; 12 int vertex[maxVsize]; 13 int pos[maxVsize]; //需要多开一个数组记录i号结点在堆中的位置 14 } heap; 15 16 //用于记录顶点与最小生成树的最小距离 17 int dis[maxVsize]; 18 19 void Prim(void); 20 void initialize(void); 21 void buildHeap(void); 22 void down(int index); 23 void up(int index); 24 int pop(void); 25 void swap(int index1, int index2); 26 27 int main() { 28 scanf("%d %d", &v, &e); 29 30 initialize(); 31 32 int i; 33 for (i = 0; i < e; ++i) { 34 int from, to, w; 35 scanf("%d %d %d", &from, &to, &w); 36 EDGE[from][to] = w; 37 EDGE[to][from] = w; 38 } 39 40 Prim(); 41 42 return 0; 43 } 44 45 void Prim(void) { 46 int i; 47 for (i = 1; i <= v; ++i) { 48 dis[i] = EDGE[1][i]; 49 } 50 buildHeap(); 51 int total = 0; //记录权重之和 52 53 for (i = 1; i < v; ++i) { 54 //选取与生成树相连的边中权重最小且不会形成回路的边所连接的点,用堆实现 55 int x = pop(); 56 57 //若dis[x]=inf,说明图不连通,不可能存在最小生成树 58 if (dis[x] == inf) { 59 total = -1; 60 break; 61 } 62 63 //将顶点加入最小生成树 64 total += dis[x]; 65 dis[x] = 0; 66 67 //遍历与新加入顶点直接相连的顶点,更新dis 68 int j; 69 for (j = 1; j <= v; ++j) { 70 if (dis[j] && dis[j] > EDGE[x][j]) { 71 dis[j] = EDGE[x][j]; 72 up(heap.pos[j]); 73 } 74 } 75 } 76 77 printf("%d\n", total); 78 } 79 80 void initialize(void) { 81 int i, j; 82 for (i = 1; i <= v; ++i) { 83 for (j = 1; j <= v; ++j) { 84 if (i == j) { 85 EDGE[i][j] = 0; 86 } 87 else { 88 EDGE[i][j] = inf; 89 } 90 } 91 } 92 } 93 94 void buildHeap(void) { 95 if (v == 1) { 96 return ; 97 } 98 99 heap.num = v-1; 100 int i; 101 for (i = 1; i < v; ++i) { 102 heap.vertex[i] = i+1; 103 heap.pos[i+1] = i; 104 } 105 106 for (i = heap.num/2; i > 0; --i) { 107 down(i); 108 } 109 } 110 111 void down(int index) { 112 while (index*2 <= heap.num) { 113 int minson = index*2; 114 115 if (minson+1 <= heap.num && dis[heap.vertex[minson+1]] < dis[heap.vertex[minson]]) { 116 ++minson; 117 } 118 119 if (dis[heap.vertex[minson]] < dis[heap.vertex[index]]) { 120 swap(minson, index); 121 index = minson; 122 } 123 else { 124 break; 125 } 126 } 127 } 128 129 void up(int index) { 130 while (index/2 > 0) { 131 if (dis[heap.vertex[index]] < dis[heap.vertex[index/2]]) { 132 swap(index, index/2); 133 index /= 2; 134 } 135 else { 136 break; 137 } 138 } 139 } 140 141 int pop(void) { 142 int t = heap.vertex[1]; 143 heap.pos[heap.vertex[1]] = 0; 144 heap.vertex[1] = heap.vertex[heap.num]; 145 --heap.num; 146 down(1); 147 148 return t; 149 } 150 151 void swap(int index1, int index2) { 152 int t = heap.vertex[index1]; 153 heap.vertex[index1] = heap.vertex[index2]; 154 heap.vertex[index2] = t; 155 156 t = heap.pos[heap.vertex[index1]]; 157 heap.pos[heap.vertex[index1]] = heap.pos[ heap.vertex[index2]]; 158 heap.pos[ heap.vertex[index2]] = t; 159 }
另外一种算法是Kruskal算法。我们可以将图中的一个个结点想象成一个个集合,每一次从图中选取一条权重最短的边,将这条边所连的集合合并起来。(若这条边连接的两个集合是同一个集合,我们就要把这条边抛弃,因为会形成回路),选取V-1次后,便找到了最小生成树。
1 #include <stdio.h> 2 3 int v, e; 4 5 #define maxVsize 1050 6 #define maxEsize 3050 7 8 struct node { 9 int from; 10 int to; 11 int weight; 12 }; 13 struct { 14 struct node array[maxEsize]; 15 int num; 16 } EDGE; 17 18 struct { 19 int index[maxEsize]; 20 int num; 21 } heap; 22 23 int father[maxVsize]; 24 25 void Kruskal(void); 26 void initialize(void); 27 void buildHeap(void); 28 void add(int from, int to, int weight); 29 int findRoot(int x); 30 int isSameRoot(int v1, int v2); 31 void merge(int v1, int v2); 32 int pop(void); 33 void down(int index); 34 void swap(int* p1, int* p2); 35 36 37 int main() { 38 scanf("%d %d", &v, &e); 39 40 initialize(); 41 42 int i; 43 for (i = 0; i < e; ++i) { 44 int from, to, weight; 45 scanf("%d %d %d", &from, &to, &weight); 46 add(from, to, weight); 47 } 48 49 Kruskal(); 50 51 return 0; 52 } 53 54 void Kruskal() { 55 buildHeap(); 56 int total = 0,counter = 0; 57 while (counter < v-1) { 58 int index = pop(); 59 60 if (index == -1) { 61 total = -1; 62 break; 63 } 64 if (isSameRoot(EDGE.array[index].from, EDGE.array[index].to)) { 65 continue; 66 } 67 68 //printf("%d %d %d\n", EDGE.array[index].from, EDGE.array[index].to, EDGE.array[index].weight); 69 70 merge(EDGE.array[index].from, EDGE.array[index].to); 71 total += EDGE.array[index].weight; 72 ++counter; 73 } 74 75 printf("%d\n", total); 76 } 77 78 void initialize(void) { 79 int i; 80 for (i = 1; i <= v; ++i) { 81 father[i] = i; 82 } 83 } 84 85 void buildHeap(void) { 86 if (EDGE.num == 0) { 87 return ; 88 } 89 90 heap.num = EDGE.num; 91 int i; 92 for (i = 1; i <= heap.num; ++i) { 93 heap.index[i] = i; 94 } 95 96 for (i = heap.num/2; i > 0; --i) { 97 down(i); 98 } 99 } 100 101 void add(int from, int to, int weight) { 102 ++EDGE.num; 103 EDGE.array[EDGE.num].from = from; 104 EDGE.array[EDGE.num].to = to; 105 EDGE.array[EDGE.num].weight = weight; 106 } 107 108 int findRoot(int x) { 109 if (father[x] == x) { 110 return x; 111 } 112 113 father[x] = findRoot(father[x]); 114 return father[x]; 115 } 116 117 int isSameRoot(int v1, int v2) { 118 int root1 = findRoot(v1); 119 int root2 = findRoot(v2); 120 121 return (root1 == root2); 122 } 123 124 void merge(int v1, int v2) { 125 int root1 = findRoot(v1); 126 int root2 = findRoot(v2); 127 father[root2] = root1; 128 } 129 130 int pop(void) { 131 if (heap.num == 0) { 132 return -1; 133 } 134 135 int t = heap.index[1]; 136 heap.index[1] = heap.index[heap.num]; 137 --heap.num; 138 down(1); 139 return t; 140 } 141 142 void down(int index) { 143 while (index*2 <= heap.num) { 144 int minson = index * 2; 145 if (minson+1 <= heap.num && EDGE.array[heap.index[minson+1]].weight < EDGE.array[heap.index[minson]].weight) { 146 ++minson; 147 } 148 149 if (EDGE.array[heap.index[minson]].weight < EDGE.array[heap.index[index]].weight) { 150 swap(&heap.index[minson], &heap.index[index]); 151 index = minson; 152 } 153 else { 154 break; 155 } 156 } 157 } 158 159 void swap(int* p1, int* p2) { 160 int t = (*p1); 161 (*p1) = (*p2); 162 (*p2) = t; 163 }

浙公网安备 33010602011771号