第44课 - 最小连通网
1. 运营商的挑战
在下图标出的城市之间架设一条通信线路。
要求:任意两个城市间能够通信,将架设成本降至最低。
2. 问题的提出
如何在图中选择n-1条表使得n个顶点间两两可达,使得这n-1条边的权值之和最小。
(1) 必须使用且仅使用该网络中的n-1条边来连接网络中的n个结点;
(2) 不能使用产生回路的边;
(3) 各个边上的权值的总和达到最小。
3. Prim算法
从图N = {V, E}中选择某一个顶点u0进行标记,之后选择与它关联的具有最小权值的边(u0, v),并且将顶点v进行标记。
反复在一个顶点被标记,而在另一个顶点未被标记的各条边中选择权值最小的边(u0, v),并将为标记的顶点进行标记。
4. 算法步骤
(1) 从某一个顶点u0出发,使得U = {u0},TE={}
(2) 每次选择一条边,这条边是所有(u, v)中权值最小的边,而且,。修改U和TE:,。
(3) 当时,转2,;否则,结束。
5. 程序—Prim算法的实现
#include <stdio.h>
#include <stdlib.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
#define VNUM 9
#define MV 65536
int P[VNUM];
int Cost[VNUM];
int Mark[VNUM];
int Matrix[VNUM][VNUM] =
{
{0, 10, MV, MV, MV, 11, MV, MV, MV},
{10, 0, 18, MV, MV, MV, 16, MV, 12},
{MV, 18, 0, 22, MV, MV, MV, MV, 8},
{MV, MV, 22, 0, 20, MV, MV, 16, 21},
{MV, MV, MV, 20, 0, 26, MV, 7, MV},
{11, MV, MV, MV, 26, 0, 17, MV, MV},
{MV, 16, MV, MV, MV, 17, 0, 19, MV},
{MV, MV, MV, 16, 7, MV, 19, 0, MV},
{MV, 12, 8, 21, MV, MV, MV, MV, 0},
};
void Prim(int sv) // O(n*n)
{
int i = 0;
int j = 0;
if( (0 <= sv) && (sv < VNUM) )
{
for(i=0; i<VNUM; i++)
{
Cost[i] = Matrix[sv][i];
P[i] = sv;
Mark[i] = 0;
}
Mark[sv] = 1;
for(i=0; i<VNUM; i++)
{
int min = MV;
int index = -1;
for(j=0; j<VNUM; j++)
{
if( !Mark[j] && (Cost[j] < min) )
{
min = Cost[j];
index = j;
}
}
if( index > -1 )
{
Mark[index] = 1;
printf("(%d, %d, %d) ", P[index], index, Cost[index]);
}
for(j=0; j<VNUM; j++)
{
if( !Mark[j] && (Matrix[index][j] < Cost[j]) )
{
Cost[j] = Matrix[index][j];
P[j] = index;
}
}
}
}
}
int main(int argc, char *argv[])
{
Prim(0);
return 0;
}
思考:
既然最小联通网是以边的权值之和为最终目标,那么是不是可以直接选择边,而不是通过顶点来选择边呢?
6. Kruskal算法
(1) 对于n个顶点的图G = {V, E}。
(2) 构造一个只有n个顶点,没有边的图。
(3) 在E中选择一条具有最小权值的边,若该边的两个顶点不构成回路,则将此边加入到T中;否则将此边社区,重新选择一条权值最小的边。
(4) 如此重复吓阻,直到所有顶点都联通位置。
7. Kruskal算法实现
步骤1:
定义边结构体
typedef struct _tag_Edge
{
int begin;
int end;
int weight;
}TEdge;
步骤2:
定义边集数组并排序
|
begin |
end |
weight |
edges[0] |
4 |
7 |
7 |
edges[1] |
2 |
8 |
8 |
edges[2] |
0 |
1 |
10 |
edges[3] |
0 |
5 |
11 |
edges[4] |
1 |
8 |
12 |
edges[5] |
3 |
7 |
16 |
edges[6] |
1 |
6 |
16 |
edges[7] |
5 |
6 |
17 |
edges[8] |
1 |
2 |
18 |
edges[9] |
6 |
7 |
19 |
edges[10] |
3 |
4 |
20 |
edges[11] |
3 |
8 |
21 |
edges[12] |
2 |
3 |
22 |
edges[13] |
3 |
6 |
24 |
edges[14] |
4 |
5 |
26 |
步骤3:
定义辅助数组P[n],其中n为顶点数目。
P[]用于记录边顶点的首位连接关系。
8. Kruskal算法核心思想
遍历edges数组中的每一个元素。
通过P数组查找begin顶点的最终连接点v1
通过P数组查找end顶点的最终连接点v2.
v1 != v2则:当前边为最小连通网中的边,记录连接关系,P[v1] = v2;
v1 == v2则:产生回路,舍弃当前边。
算法举例:
小结:
(1) Prim算法是针对顶点展开的,适合于边的数量较,适合于边的数量较多的情况。
(2) Kruskal算法是针对边展开的,适合于边的数量较,适合于边的数量较少的情况。