最小生成树

  要解决最小生成树问题,首先要了解什么是最小生成树。生成树指选取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算法

  我们还可以采用堆的方法来优化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 }
Prim算法(堆优化版)

 

  另外一种算法是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 }
Kruskal算法

 

posted @ 2020-04-16 16:30  小茗从不写博客  阅读(126)  评论(0)    收藏  举报