实用算法实现-第 19 第 最小生成树

19.1 Prim算法

《算法导论》中,Prim算法的伪代码如下:

MST-PRIM(G, w, r)

1 for each u ∈ V[G]

2 do key[u] ← NIL

3 ∏[u] ← NIL

4 key [r] ← 0

5 Q ← V[G]

6 while Q ≠ Ф

7 do u ← EXTRACT-MIN(Q)

8 for each v ∈ Adj[u]

9 do if v ∈ Q and w (u, v) < key[v]

10 then [v] ← u

11 key[v] ← w(u, v)

其中,key[u]是所有将u与树某一顶点连接的边中的最小权值。

EXTRACT-MIN(Q)是找出连接V-Q集合和Q集合的最小权值的那条边,然后从Q集合中删除,加入V-Q中。

19.1.1 实例

PKU JudgeOnline, 2485, Highways.

19.1.2 问题描述

要修建几条高速公路将所有村庄连接起来,已知村庄之间的距离,修建公路里程数最小的情况下,最长的那段公路有多长。

PKU JudgeOnline, 2560, Freckles,和PKU JudgeOnline, 2075, Tangled in Cables是牵涉到浮点数运算的Prim算法。前者要简单不少。

19.1.3 输入

1

3

0 990 692

990 0 179

692 179 0

19.1.4 输出

692

19.1.5 程序

#include <iostream.h> #include <stdio.h> #include <string.h> #define MAX 0x7F7F7F7F int route[502][502]; int key[502]; bool visited[502]; int N; int Prim(){ int i, j; int max; int minU; int U; memset(key, 0x7F, sizeof(key)); memset(visited, 0, sizeof(visited)); key[1] = 0; max = 0; while(1){ minU= MAX; for(i = 1; i <= N; i++){ if(visited[i] == false && minU > key[i]){ minU = key[i]; U = i; } } if(minU == MAX) break; visited[U] = true; if(max < key[U]) max = key[U]; for(j = 1; j <= N; j++){ int distance = route[U][j]; if(distance == MAX || visited[j] == true ){ continue; } if(distance < key[j]){ key[j] = distance; } } } return max; } int main() { //again: int i; int j; int T; scanf("%d", &T); for(;T >0 ; T--){ scanf("%d", &N); memset(route, 0x7F, sizeof(route)); for(i = 1; i <= N; i++){ for(j = 1; j <= N; j++){ scanf("%d", &route[i][j]); } } printf("%d\n", Prim()); } return 1; }

19.2 最小度限制生成树

《算法艺术与信息学竞赛》中提供了最小度限制生成树的算法及其证明。

这里的度限制指的是无向图中的某一个结点的度数有上限,所以导致最小生成树相对于没有度限制的生成树代价高。可以证明这个问题可以通过如下的贪心法来实现,但是如果图中的每个结点的度数都有上限,那么问题就是一个NP难的了。

《算法艺术与信息学竞赛》中的最小度限制生成树算法如下:

先求出v1,…,vn的最小生成树T,再加上与v0相关联的边。

求v0度数不超过K的最小生成树,不妨令Hi为v0的度数为i的最小生成树。则问题所求就是min{Hi,1<=i<=K},显然H1=T+{(0,x)},(0,x)时所有与v0关联的边中权值最小的。

在Hi-1上加入一条边(0,x),得到一个环,然后再删掉环中一条不与0关联的边,设为(xa,xb),这样就能得到一颗v0的度数为i的生成树,这样 得到的生成树的权值和为cost(Hi-1)+cost(0,x)-cost(xa,xb),为了使权值最小,我们可以枚举x的值,删除时选取一条权值最大的。这样得到一颗v0的度数为i的最小生成树。

算法流程如下:

1. 找{v1,…,vn}所有的连通块,求出每个连通块的最小生成树Ti。

2. 在每个块中,选择v0相邻的最小边,得到Ht. Min<-Ht,V<-cost(Ht)。

3. 循环i<-i+1 to k do:在Hi-1上选择“差额最小添删操作”,添加并删除一条边得到Hi,令v<-v+cost(添边)-cost(删边),若v<min,则令Min<-v;如果找不到“差额最小添删操作”则Break。

4. 输出Min。

19.2.1 实例

PKU JudgeOnline, 1639, Picnic Planning.

19.2.2 问题描述

一些人想从各自的家中开车到一个地方野餐,每个人的家中都可以容纳无限多的车子,每个人的车子可以容纳无限多的人。每个人可以先开车到另一人家中,将车停在那人家中,两人(或多人)再开同一辆车开往目的地。但野餐的地方只有有限个停车位k,告诉你一些路程的长度,问你将所有人都聚集再野餐地点,所使用的最短路程是多少。

19.2.3 输入

10

Alphonzo Bernardo 32

Alphonzo Park 57

Alphonzo Eduardo 43

Bernardo Park 19

Bernardo Clemenzi 82

Clemenzi Park 65

Clemenzi Herb 90

Clemenzi Eduardo 109

Park Herb 24

Herb Eduardo 79

3

19.2.4 输出

Total miles driven: 183

19.2.5 分析

这个问题是基本的最下度限制生成树问题。算法使用到Prim算法,这里的Prim算法比较复杂的在于需要在Prim算法中记住每条边加入的是哪条树中。对于环的遍历可以实用DFS来实现。

19.2.6 程序

#include <iostream> #include <stdio.h> #include <string.h> using namespace std; #define MAX 0x7F7F7F7F int route[30][30]; int key[30]; int former[30]; int visited[30]; int used[30][30]; int park; int N; int degree; int Prim(int begin, int treeNum) { int i, j; int sum; int minU; int U; memset(key, 0x7F, sizeof(key)); key[begin] = 0; former[begin] = begin; sum = 0; while(1){ minU = MAX; for(i = 0; i < N; i++){ if(visited[i] == 0 && minU > key[i]){ minU = key[i]; U = i; } } if(minU == MAX) break; visited[U] = treeNum; used[former[U]][U] = treeNum; used[U][former[U]] = treeNum; sum += key[U]; for(j = 0; j < N; j++){ int distance = route[U][j]; if(distance == MAX || visited[j] != 0){ continue; } if(distance < key[j]){ key[j] = distance; former[j] = U; } } } used[begin][begin] = 0; //循环的时候会被赋值,为保持一致性,改之 return sum; } int DFSvisited[22]; struct edge{ int from; int to; int length; }; edge DFSfindCircle(int begin) { int i; edge e; edge max; edge maxi; max.from = -1; max.to = -1; max.length = -1; DFSvisited[begin] = 1; if(begin == park) { max.length = 0; return max; } for(i = 0; i < N; i++){ if((used[begin][i] != 0)&& (DFSvisited[i] == 0)) { maxi.from = begin; maxi.to = i; maxi.length = route[begin][i]; e = DFSfindCircle(i); if(e.length < 0){ maxi.from = -1; maxi.to = -1; maxi.length = -1; }else if(maxi.length < e.length){ maxi = e; } if(max.length < maxi.length) { max = maxi; } } } return max; } int degreeLimitedTree(){ int i; int j; int min, minNode; int limited[30]; int treeNum; int temp; edge e; int sum; edge maxEdge; int maxNode; for(i = 0; i < N; i++){ limited[i] = route[park][i]; route[park][i] = MAX; route[i][park] = MAX; } treeNum = 0; memset(visited, 0, sizeof(visited)); memset(used, 0, sizeof(used)); sum = 0; for(i = 0; i < N; i++){ if(i == park) { continue; } if(visited[i] == 0) { treeNum++; sum += Prim(i, treeNum); } } for(i = 0; i < N; i++){ route[park][i] = limited[i]; route[i][park] = limited[i]; } for(i = 1; i <= treeNum; i++){ min = MAX; for(j = 0; j < N; j++){ if((visited[j] == i) &&(min > route[park][j])){ min = route[park][j]; minNode = j; } } used[minNode][park] = 1; used[park][minNode] = 1; sum += min; } //至此为止已经形成一个完整的树 for(i = treeNum + 1; i <= degree; i++){ maxEdge.from = -1; maxEdge.to = -1; maxEdge.length = -MAX; for(j = 0; j < N; j++){ if((used[park][j] == 0) &&(route[park][j] != MAX)){ memset(DFSvisited, 0, sizeof(DFSvisited)); e = DFSfindCircle(j); temp = e.length - limited[j]; if(maxEdge.length < temp){ maxEdge = e; maxEdge.length = temp; maxNode = j; } } } temp = -maxEdge.length; if(temp > 0) { break; } sum += temp; used[maxEdge.from][maxEdge.to] = 0; used[maxEdge.to][maxEdge.from] = 0; used[park][maxNode] = 1; used[maxNode][park] = 1; } return sum; } int main() { int i, j, k; int n; char brother[30][15]; char from[15]; char to[15]; int distance; scanf("%d\n", &n); N = 0; memset(route, 0x7F, sizeof(route)); memset(brother, NULL, sizeof(brother)); for(i = 0; i < n; i++){ scanf("%s %s %d", from, to, &distance); for(j = 0; j < N; j++) { if(strcmp(from, brother[j]) == 0) { break; } } if(j == N) { strcpy(brother[N], from); N++; } for(k = 0; k < N; k++) { if(strcmp(to, brother[k]) == 0) { break; } } if(k == N) { strcpy(brother[N], to); N++; } if(route[j][k] > distance) { route[j][k] = distance; route[k][j] = distance; } } for(park = 0; park < N; park++){ if(strcmp(brother[park], "Park") == 0) { break; } } cin >> degree; i = degreeLimitedTree(); printf("Total miles driven: %d\n", i); return 1; }

19.3 最优比率生成树

最优比率生成树是0-1分数规划算法的典型应用。0-1分数规划又是分数规划的一个特例。

分数规划的分析在《最小割模型在信息学竞赛中的应用》中介绍得比较详细。最优比率生成树在《算法艺术与信息学竞赛》中有所介绍,但是介绍的最优比率生成树算法是0-1分数规划的二分查找算法,显然没有Dinkelbach算法的性能好。Dinkelbach算法的介绍参见[i]

19.3.1 实例

PKU JudgeOnline, 2728, Desert King.

19.3.2 问题描述

给定几个村庄的描述x, y, z。两个村庄之间修建的水渠的距离是由坐标x, y决定的,每个水渠的成本是两个村庄之高度z的差决定的。要建造连接这些村庄的水渠,使得水渠的成本除以总距离最小。问这个最小比率是多大。

19.3.3 输入

4

0 0 0

0 1 1

1 1 2

1 0 3

0

19.3.4 输出

1.000

19.3.5 分析

这是典型的最优比率生成树问题。

19.3.6 程序

#include <iostream> #include <stdio.h> #include <string.h> #include <math.h> using namespace std; #define MAX 0x7F7F7F7F #define TINY -0.00001 #define maxNum 1002 double route[maxNum][maxNum]; double key[maxNum]; bool visited[maxNum]; int used[maxNum][maxNum]; int N; int former[maxNum]; double Prim(){ int i, j; double sum; double minU; bool changed; int U; memset(visited, 0, sizeof(visited)); memset(used, 0, sizeof(used)); for(i = 1; i <= N; i++){ key[i] = MAX; } key[1] = 0; former[1] = 1; sum = 0; while(1){ minU = MAX; changed = false; for(i = 1; i <= N; i++){ if(visited[i] == false && minU > key[i]){ changed = true; minU = key[i]; U = i; } } if(changed == false) break; visited[U] = true; used[former[U]][U] = 1; used[U][former[U]] = 1; sum += key[U]; for(j = 1; j <= N; j++){ double distance = route[U][j]; if(distance != MAX && visited[j] == true ){ continue; } if(distance < key[j]){ key[j] = distance; former[j] = U; } } } used[1][1] = 0; //循环的时候会被赋值,为保持一致性,改之 return sum; } struct village{ int x; int y; int z; }; double cost[maxNum][maxNum]; double length[maxNum][maxNum]; int main() { int i, j; double x, y; int temp; double maxC; double optimal; double lamta; while(1){ cin >> N; if(N == 0) { break; } village village[maxNum]; for(i = 1; i <= N; i++){ scanf("%d%d%d", &village[i].x, &village[i].y, &village[i].z); } maxC = 0; for(i = 1; i <= N; i++){ for(j = i; j <= N; j++){ temp = village[i].x - village[j].x ; temp = temp * temp; x = (double)temp; temp = village[i].y - village[j].y ; temp = temp * temp; y = (double)temp; length[i][j] = sqrt(x + y); length[j][i] = length[i][j]; //cout << "DIS " << i <<", "<< j <<" :" << length[i][j] << endl; temp = village[i].z - village[j].z; if(temp < 0) { temp = -temp; } cost[i][j] = (double)temp; cost[j][i] = cost[i][j]; if(maxC < cost[i][j]) { maxC = cost[i][j]; } } } if(maxC < 1) { maxC = 1; } optimal = -1; lamta = N * maxC; while(1) { if(optimal > TINY) { break; } for(i = 1; i <= N; i++){ for(j = i; j <= N; j++){ route[i][j] = cost[i][j] - lamta * length[i][j]; route[j][i] = route[i][j]; } } optimal = Prim(); //cout << optimal << endl; x = 0; y = 0; for(i = 1; i <= N; i++){ for(j = 1; j <= N; j++){ x += cost[i][j] * used[i][j]; y += length[i][j] * used[i][j]; } } lamta = x / y; } printf("%.3f\n", lamta); } return 1; }

1.4 实例

1.4.1 最小生成树实例

PKU JudgeOnline, 2485, Highways.

PKU JudgeOnline, 2349, Arctic Network.

PKU JudgeOnline, 1861, Network.

PKU JudgeOnline, 1789, Truck History.

PKU JudgeOnline, 2253, Frogger.

PKU JudgeOnline, 2395, Out of Hay.

PKU JudgeOnline, 2377, Bad Cowtractors.

PKU JudgeOnline, 1287, Networking.

PKU JudgeOnline, 2075, Tangled in Cables.

PKU JudgeOnline, 2421, Constructing Roads.

PKU JudgeOnline, 2560, Freckles.

1.4.2 最小度限制生成树实例

PKU JudgeOnline, 1639, Picnic Planning.

1.4.3 最优比率生成树实例

PKU JudgeOnline, 2728, Desert King.

本文章欢迎转载,请保留原始博客链接http://blog.csdn.net/fsdev/article

[i] An Analysis of Dinkelbach's Algorithm for 0-1 Fractional Programming Problems. Tomomi Matsui, Yasufumi Saruwatari, Maiko Shigeno. Technical Report METR92-14, Department of Mathematical Engineering and Information Physics, Unversity of Tokyo,1992.

转载于:https://www.cnblogs.com/jpa2/archive/2011/11/02/2527904.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用C语言实现Kruskal算法最小生成树的代码。 ```c #include <stdio.h> #include <stdlib.h> #define MAXVEX 100 // 最大顶点数 #define MAXEDGE 100 // 最大边数 typedef struct Edge { int u, v; // 边的两个端点 int w; // 边的权值 }Edge; typedef struct { int vexs[MAXVEX]; // 顶点集合 Edge edges[MAXEDGE]; // 边集合 int n, e; // 顶点数和边数 }Graph; // 初始化图 void initGraph(Graph *G) { int i, j; G->n = G->e = 0; for (i = 0; i < MAXVEX; i++) { G->vexs[i] = i; // 默认顶点编号为0~n-1 } for (i = 0; i < MAXEDGE; i++) { G->edges[i].u = G->edges[i].v = G->edges[i].w = 0; } } // 添加边 void addEdge(Graph *G, int u, int v, int w) { G->edges[G->e].u = u; G->edges[G->e].v = v; G->edges[G->e].w = w; G->e++; } // 交换两个结构体变量 void swap(Edge *a, Edge *b) { Edge tmp = *a; *a = *b; *b = tmp; } // 快速排序 void quickSort(Edge *edges, int low, int high) { if (low < high) { int i = low, j = high; Edge pivot = edges[low]; while (i < j) { while (i < j && edges[j].w >= pivot.w) j--; edges[i] = edges[j]; while (i < j && edges[i].w <= pivot.w) i++; edges[j] = edges[i]; } edges[i] = pivot; quickSort(edges, low, i - 1); quickSort(edges, i + 1, high); } } // 查找顶点的祖先 int find(int *parent, int f) { while (parent[f] > 0) { f = parent[f]; } return f; } // Kruskal算法最小生成树 void kruskal(Graph *G) { int i, u, v, m = G->e; int parent[MAXVEX] = { 0 }; // 记录顶点的祖先 quickSort(G->edges, 0, m - 1); // 对边进行排序 for (i = 0; i < m; i++) { u = find(parent, G->edges[i].u); v = find(parent, G->edges[i].v); if (u != v) { // 如果两个顶点不在同一个集合中,说明不会形成环 parent[u] = v; printf("(%d, %d) weight=%d\n", G->edges[i].u, G->edges[i].v, G->edges[i].w); } } } int main() { Graph G; initGraph(&G); addEdge(&G, 0, 1, 10); addEdge(&G, 0, 5, 11); addEdge(&G, 1, 6, 16); addEdge(&G, 1, 2, 18); addEdge(&G, 2, 6, 19); addEdge(&G, 2, 3, 22); addEdge(&G, 3, 6, 24); addEdge(&G, 3, 4, 20); addEdge(&G, 4, 6, 6); addEdge(&G, 4, 5, 26); kruskal(&G); return 0; } ``` 这是一个简单的例子,通过添加 `addEdge` 函数来添加边,使用 `kruskal` 函数来求最小生成树。其中 `quickSort` 函数用于对边进行快速排序,`find` 函数用于查找顶点的祖先。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值