1. Prim算法
该算法与Dijkstra算法十分相似,不同的地方在于Dist矩阵的算法有细微差别。见下面的实现:
/* poj 1251 time: 0ms memory:220k PS:该题目的输入中每行结尾可能有几个空格,要是用scanf函数,字符串的输入要用字符数组,而不能用单个字符。或者用cin输入。 */ #include <iostream> using namespace std; #define MaxNode 27 #define MaxEdge 150 #define INF 1000000 struct VertexEntry { int next, e, cost; }; VertexEntry vertex[MaxEdge + MaxNode]; int Cost[MaxNode]; int Path[MaxNode]; int Known[MaxNode]; void Init(int Num) { int i, j, k = Num + 1, cost, num; char id; for(i = 1; i <= Num; ++i) { Cost[i] = INF; Path[i] = 0; Known[i] = 0; vertex[i].next = 0; } Cost[1] = 0; while(--Num) { cin >> id >> num; i = id - '@'; while(num--) { cin >> id >> cost; j = id - '@'; vertex[k].cost = cost; vertex[k].e = j; vertex[k].next = vertex[i].next; vertex[i].next = k++; vertex[k].cost = cost; vertex[k].e = i; vertex[k].next = vertex[j].next; vertex[j].next = k++; } } } int Prim(int Num) { int i, j, min, sum = 0; while(1) { min = INF; for(i = 1; i <= Num; ++i) { if(!Known[i] && Cost[i] < min) { min = Cost[i]; j = i; } } if(min == INF) break; Known[j] = 1; i = j; while(vertex[j].next) { j = vertex[j].next; if(!Known[vertex[j].e] && vertex[j].cost < Cost[vertex[j].e]) { Cost[vertex[j].e] = vertex[j].cost; Path[vertex[j].e] = i; } } } for(i = 1; i <= Num; ++i) { sum += Cost[i]; } return sum; } int main() { int Num; cin >> Num; while(Num) { Init(Num); cout << Prim(Num) << endl; cin >> Num; } return 0; }
PS:开始我使用scanf搭配char,一直不过,一查才知道数据输入可能每行结尾有多余的空格。虽然空格、制表符和新行符都用做域分割符号,但读单字符操作中却按一般字符处理。例如,对输入流 "x y" 调用:
scanf("%c%c%c",&a,&b,&c); //x 在变量 a 中,空格在变量 b 中,y 在变量 c 中
使用scanf则要与char[]搭配才行,如下代码:
/* poj 1251 time: 0ms memory: 220k */ #include <iostream> #include <stdio.h> using namespace std; #define MaxNode 27 #define MaxEdge 150 #define INF 1000000 struct VertexEntry { int next, e, cost; }; VertexEntry vertex[MaxEdge + MaxNode]; int Cost[MaxNode]; int Path[MaxNode]; int Known[MaxNode]; void Init(int Num) { int i, j, k = Num + 1, cost, num; char id[3]; //注意此处 for(i = 1; i <= Num; ++i) { Cost[i] = INF; Path[i] = 0; Known[i] = 0; vertex[i].next = 0; } Cost[1] = 0; while(--Num) { scanf("%s%d", id, &num); i = id[0] - '@'; //注意此处 while(num--) { scanf("%s%d", id, &cost); //注意此处 j = id[0] - '@'; vertex[k].cost = cost; vertex[k].e = j; vertex[k].next = vertex[i].next; vertex[i].next = k++; vertex[k].cost = cost; vertex[k].e = i; vertex[k].next = vertex[j].next; vertex[j].next = k++; } } } int Prim(int Num) { int i, j, min, sum = 0; while(1) { min = INF; for(i = 1; i <= Num; ++i) { if(!Known[i] && Cost[i] < min) { min = Cost[i]; j = i; } } if(min == INF) break; Known[j] = 1; i = j; while(vertex[j].next) { j = vertex[j].next; if(!Known[vertex[j].e] && vertex[j].cost < Cost[vertex[j].e]) { Cost[vertex[j].e] = vertex[j].cost; Path[vertex[j].e] = i; } } } for(i = 1; i <= Num; ++i) { sum += Cost[i]; } return sum; } int main() { int Num; cin >> Num; while(Num) { Init(Num); cout << Prim(Num) << endl; cin >> Num; } return 0; }
2. Kruskal算法
kruskal算法总共选择n- 1条边,(共n个点)所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。注意到所选取的边若产生环路则不可能形成一棵生成树。该算法常与并查集,最小堆联合使用。与prim算法像比较,kruskal算法适用于稠密图,而prim算法对稀疏图和稠密图都较适用,原因就在于边的权的存储方式的不同。kruskal算法常用用结构数组将边存储,而prim算法既可以用二维数组(邻接矩阵)存储边的信息,也可以用邻接表存储。
/* poj 1861 time: 16ms memory:428k */ #include <iostream> #include <stdio.h> using namespace std; #define MaxNode 1001 #define MaxEdge 15001 typedef int PriorityQueue[MaxEdge]; typedef int DisjSet[MaxNode]; struct EdgeEntry { int n1, n2, w, flag; }; DisjSet s; //并查集 EdgeEntry Edge[MaxEdge]; PriorityQueue H; //最小堆,H[i],i表示在堆里的位置,H[i]是指向Edge的index,通过改变H[i]的取值完成堆的实现 int max_w = 0; //存储选中边里的最大权值 void Init(int N, int M) { int i, n1, n2, w; for(i = 1; i <= N; ++i) { s[i] = -1; } for(i = 1; i <= M; ++i) { scanf("%d%d%d", &n1, &n2, &w); Edge[i].n1 = n1; Edge[i].n2 = n2; Edge[i].w = w; Edge[i].flag = 0; H[i] = i; //此处对H[i]初始化,相当于将Edge按顺序放入堆里 } } int Find(int v) { if(s[v] < 0) return v; else return s[v] = Find(s[v]); } void Union(int root1, int root2) { if(s[root1] < s[root2]) { s[root1] += s[root2]; s[root2] = root1; } else { s[root2] += s[root1]; s[root1] = root2; } } void PercolateDown(int M, int i) { int FirstElement = H[i]; int child; for(; 2 * i <= M; i = child) { child = 2 * i; if(2 * i + 1 <= M && Edge[H[2 * i + 1]].w < Edge[H[2 * i]].w) ++child; if(Edge[H[child]].w < Edge[FirstElement].w) //务必注意此处,使用FirstElement而不是H[i],虽然容易理解,但也很容易犯错 H[i] = H[child]; else break; } H[i] = FirstElement; } int DeleteMin(int M) { int i, child; int MinElement = H[1]; int LastElement = H[M]; for(i = 1; i * 2 <= M; i = child) { child = 2 * i; if(2 * i + 1 <= M && Edge[H[2 * i + 1]].w < Edge[H[i * 2]].w) ++child; if(Edge[LastElement].w > Edge[H[child]].w) //务必注意此处,使用LastElement而不是H[i] H[i] = H[child]; else break; } H[i] = LastElement; return MinElement; } void Kruskal(int N, int M) { int cnt = 0; int root1, root2; int Edgeid; while(cnt < N - 1) { Edgeid = DeleteMin(M--); root1 = Find(Edge[Edgeid].n1); root2 = Find(Edge[Edgeid].n2); if(root1 != root2) { if(Edge[Edgeid].w > max_w) max_w = Edge[Edgeid].w; Edge[Edgeid].flag = 1; ++cnt; Union(root1, root2); } } } int main() { int N, M, i, cnt; scanf("%d%d", &N, &M); Init(N, M); for(i = M / 2; i > 0; --i) PercolateDown(M, i); Kruskal(N, M); printf("%d\n%d\n", max_w, N - 1); for(i = 1, cnt = 0; cnt < N - 1; ++i) { if(Edge[i].flag) { ++cnt; printf("%d %d\n", Edge[i].n1, Edge[i].n2); } } return 0; }
/* poj 1789 time:766ms memory:30476k 性能有些差啊,主要是该图是个稠密图,应该用prim算法+邻接矩阵 */ #include <stdio.h> #include <string> #include <iostream> #include <stdio.h> using namespace std; #define MaxNode 2001 #define MaxEdge 1999001 typedef int DisjSet[MaxNode]; typedef int Priority[MaxEdge]; struct EdgeEntry { int n1, n2, w; }; EdgeEntry Edge[MaxEdge]; string truck_id[MaxNode]; DisjSet s; Priority H; int sum; void Init(int N, int M) { int i, j, n, k = 1; for(i = 1; i <= N; ++i) { cin >> truck_id[i]; s[i] = -1; } for(i = 1; i <= M; ++i) Edge[i].w = 0; for(i = 1; i <= N; ++i) for(j = 1; j < i; ++j) { for(n = 0; n <= 6; ++n) Edge[k].w += (truck_id[i][n] != truck_id[j][n]); Edge[k].n1 = i; Edge[k].n2 = j; H[k] = k; ++k; } } int Find(int v) { if(s[v] < 0) return v; else return s[v] = Find(s[v]); } void Union(int root1, int root2) { if(s[root1] < s[root2]) { s[root1] += s[root2]; s[root2] = root1; } else { s[root2] += s[root1]; s[root1] = root2; } } void PercolateDown(int M, int i) { int FirstElement = H[i]; int child; for(; 2 * i <= M; i = child) { child = 2 * i; if(2 * i + 1 <= M && Edge[H[2 * i + 1]].w < Edge[H[2 * i]].w) ++child; if(Edge[H[child]].w < Edge[FirstElement].w) H[i] = H[child]; else break; } H[i] = FirstElement; } int DeleteMin(int M) { int i, child; int MinElement = H[1]; int LastElement = H[M]; for(i = 1; i * 2 <= M; i = child) { child = 2 * i; if(2 * i + 1 <= M && Edge[H[2 * i + 1]].w < Edge[H[i * 2]].w) ++child; if(Edge[LastElement].w > Edge[H[child]].w) H[i] = H[child]; else break; } H[i] = LastElement; return MinElement; } void Kruskal(int N, int M) { int cnt = 0; int root1, root2; int Edgeid; while(cnt < N - 1) { Edgeid = DeleteMin(M--); root1 = Find(Edge[Edgeid].n1); root2 = Find(Edge[Edgeid].n2); if(root1 != root2) { sum += Edge[Edgeid].w; ++cnt; Union(root1, root2); } } } int main() { int N, M, i; scanf("%d", &N); while(N) { sum = 0; M = (N * N - N) / 2; Init(N, M); for(i = M / 2; i > 0; --i) PercolateDown(M, i); Kruskal(N, M); cout << "The highest possible quality is 1/" << sum << "." << endl; scanf("%d", &N); } return 0; }