hdu 1233 还是畅通工程(最小生成树的Prim和Kruskal两种算法的c++实现)(prim算法详解)

赤裸裸滴最小生成树(MST),刚学的玩意,用两种方法熟练一下。(都是greedy)

Kruskal方法:先对边按照代价非递减排序,再不断添加边且不产生环路,当边数=点数-1结束。判断加入(v,w)是否会产生环路,可以用并查集,如果检查v和w在同一集合中,说明这两个点已经连通,加入边(v, w)就会产生环路。Kruskal算法总时间复杂度O(eloge).

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>
 4 const int MAXN = 5050;
 5 int n, father[MAXN], m;        //m边数
 6 struct Edge
 7 {
 8     int x, y, length;
 9     bool operator<(const Edge a) const 
10     {
11         return length < a.length;
12     }
13 };
14 Edge e[MAXN];
15 void Init(int *a)
16 {
17     for (int i = 1; i <= n; ++i)
18         father[i] = i;
19 }
20 int Find(int x)
21 {
22     if (x != father[x])
23         father[x] = Find(father[x]);
24     return father[x];
25 }
26 void Union(int x, int y)
27 {
28     int fx = Find(x), fy = Find(y);
29     if (fx != fy)
30         father[fx] = fy;
31 }
32 int Kruskal()
33 {
34     std::sort(e, e + m);
35     Init(father);
36     int sum = 0;
37     for (int i = 0; i < m; ++i)
38         if (Find(e[i].x) != Find(e[i].y))
39         {
40             Union(e[i].x, e[i].y);
41             sum += e[i].length;
42         }
43     return sum;
44 }
45 int main()
46 {
47     while (scanf("%d", &n) != EOF && n)
48     {
49         m = n * (n - 1) / 2;
50         for (int i = 0; i < m; ++i)
51             scanf("%d %d %d", &e[i].x, &e[i].y, &e[i].length);
52         printf("%d\n", Kruskal());
53     }
54     return 0;
55 }

Prim算法:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 const int MAXN = 101;
 5 const int INF = INT_MAX;
 6 int n, m, visited[MAXN], dist[MAXN];  //dist[i]记录i到与i邻接且未被访问的点的最小权值
 7 int map[MAXN][MAXN];
 8 int Prim(int n)
 9 {
10     memset(visited, 0, sizeof(visited));
11     visited[1] = 1;
12     dist[1] = INF;
13     for (int i = 2; i <= n; ++i)
14         dist[i] = map[1][i];
15     int min, pos, sum = 0;
16     for (int i = 2; i <= n; ++i)    //加入剩下的n-1个点
17     {
18         min = INF;
19         for (int j = 1; j <= n; ++j)
20             if (!visited[j] && dist[j] < min)
21             {
22                 min = dist[j];
23                 pos = j;
24             }
25         sum += min;                    //或者sum += dist[pos];
26         visited[pos] = 1;
27         for (int j = 1; j <= n; ++j)    //刷新最小权值
28             if (!visited[j] && map[pos][j] < dist[j])
29                 dist[j] = map[pos][j];
30     }
31     return sum;
32 }
33 int main()
34 {
35     while (scanf("%d", &n) != EOF && n)
36     {
37         m = n * (n - 1) / 2;
38         int x, y, len;
39         for (int i = 0; i < m; ++i)                //建图
40         {
41             scanf("%d %d %d", &x, &y, &len);
42             map[x][y] = map[y][x] = len;
43         }
44         for (int i = 0; i <= n; ++i)
45             map[i][i] = INF;
46         printf("%d\n", Prim(n));
47     }
48     return 0;
49 }

 

还不是很熟悉,贴一个资料先学一下。

假设G=(V,E)为一网图,其中V 为网图中所有顶点的集合,E 为网图中所有带权边的集合。设置两个新的集合U 和T,其中集合U 用于存放G 的最小生成树中的顶点,集合T 存放G 的最小生成树中的边。令集合U 的初值为U={u1}(假设构造最小生成树时,从顶点u1 出发),集合T 的初值为T={}。Prim 算法的思想是,从所有u∈U,v∈V-U 的边中,选取具有最小权值的边(u,v),将顶点v 加入集合U 中,将边(u,v)加入集合T 中,如此不断重复,直到U=V 时,最小生成树构造完毕,这时集合T 中包含了最小生成树的所有边。

Prim 算法可用下述过程描述,其中用wuv 表示顶点u 与顶点v 边上的权值。
(1)U={u1},T={};
(2)while (U≠V)do
(u,v)=min{wuv;u∈U,v∈V-U }
T=T+{(u,v)}
U=U+{v}
(3)结束。
图8.23 (a)所示的一个网图,按照Prim 方法,从顶点1 出发,该网的最小生成树的产生过程如图8.23 (b)、(c)、(d)、(e)、(f)和(g)所示。


为实现Prim 算法,需设置两个辅助一维数组lowcost 和closevert,其中lowcost 用来保存集合V-U 中各顶点与集合U 中各顶点构成的边中具有最小权值的边的权值;数组closevertex 用来保存依附于该边的在集合U 中的顶点。假设初始状态时,U={u1}(u1 为出发的顶点),这时有lowcost[0]=0,它表示顶点u1 已加入集合U 中,数组lowcost 的其它各分量的值是顶点u1 到其余各顶点所构成的直接边的权值。然后不断选取权值最小的边(ui,uk)(ui∈U,uk∈V-U),每选取一条边,就将lowcost(k)置为0,表示顶点uk 已加入集合U 中。由于顶点uk 从集合V-U 进入集合U 后,这两个集合的内容发生了变化,就需依据具体情况更新数组lowcost 和closevertex 中部分分量的内容。最后closevertex 中即为所建立的最小生成树。

当无向网采用二维数组存储的邻接矩阵存储时,Prim 算法的C 语言实现为:

先从某一点开始,把这一个开始的点放于声明的一个数组或者集合里,表明这一点已经被访问过。然后再从余下的n-1个点里去找那个权值最小的点并记录该点的位置然后再加上这一点的权值,同时将该点放于集合里表明该点已初访问。再更新权值

void Prim(int gm[][MAXNODE],int n,int closevertex[])
{/*用Prim 方法建立有n 个顶点的邻接矩阵存储结构的网图gm 的最小生成树*/
/*从序号为0 的顶点出发;建立的最小生成树存于数组closevertex 中*/
    int lowcost[100],mincost;
    int i,j,k;
    for (i=1;i<n;i++) /*初始化*/
    {
        lowcost[i]=gm[0][i];
        closevertex[i]=0;
    }
    lowcost[0]=0; /*从序号为0 的顶点出发生成最小生成树*/
    closevertex[0]=0;
    for (i=1;i<n;i++) /*寻找当前最小权值的边的顶点*/
    {
        mincost=MAXCOST; /*MAXCOST 为一个极大的常量值*/
        j=1;k=1;
        while (j<n)
        { 
            if (lowcost[j]<mincost && lowcost[j]!=0)
                {    
                    mincost=lowcost[j];
                    k=j;
                }
                j++;
        }
        printf(“顶点的序号=%d 边的权值=%d\n”,k,mincost);
        lowcost[k]=0;
        for (j=1;j<n;j++) /*修改其它顶点的边的权值和最小生成树顶点序号*/
        if (gm[k][j]<lowcost[j])
            { 
                lowcost[j]=gm[k][j];
                closevertex[j]=k;
            }
    }
}

 

算法8.14

图8.24 给出了在用上述算法构造网图8.23 (a)的最小生成树的过程中,数组closevertex、lowcost 及集合U,V-U 的变化情况,读者可进一步加深对Prim 算法的了解。

在Prim 算法中,第一个for 循环的执行次数为n-1,第二个for 循环中又包括了一个while 循环和一个for 循环,执行次数为2(n-1)2,所以Prim 算法的时间复杂度为O(n2)。

如果没有看懂的话,再接着看下面这个比较通俗的:

关于prim算法

先把有的点放于一个集合(或者数组)里,这个集合里存放的是所有走过的点。初始值为0或者false表示还没有点

声明一个一维数组用于记录各点的权值[可理解为起始点到目标点的距离],

声明一个二维数组用于记录某点到某一点的权值,如果这两点不可达到,则设置为无穷大

具体执行过程:

先从某一点开始,把这一个开始的点放于声明的一个数组或者集合里,表明这一点已经被访问过。然后再从余下的n-1个点里去找那个权值最小的点并记录该点的位置然后再加上这一点的权值,同时将该点放于集合里表明该点已初访问。再更新权值

再看下图,从下图分析:

1、

先选取一个点作起始点,然后选择它邻近的权值最小的点(如果有多个与其相连的相同最小权值的点,随便选取一个)。如1作为起点。

isvisited[1]=1;   //表明把1加进来说明是已经访问过

pos=1; //记录该位置

//用dist[]数组不断刷新最小权值,dist[i](0<i<=点数)的值为:i点到邻近点(未被标记)的最小距离。

dist[1]=0;  //起始点i到邻近点的最小距离为0

dist[2]=map[pos][2]=4;

dist[3]=map[pos][3]=2;

dist[4]==map[pos][4]=3;

dist[5]=map[pos][5]=MaxInt;  //无法直达

dist[6]=map[pos][6]=MaxInt;

 

2、

再在伸延的点找与它邻近的两者权值最小的点。

//dist[]以3作当前位置进行更新

isvisited[3]=1;

pos=3;

dist[1]=0;   //已标记,不更新

dist[2]=map[pos][2]=4;  //比5小,不更新

dist[3]=2;  //已标记,不更新

dist[4]=map[pos][4]=3;   //比1大,更新

dist[5]=map[pos][5]=MaxInt;

dist[6]=map[pos][6]=MaxInt;

 

3、最后的结果:

 

当所有点都连同后,结果最生成树如上图所示。

所有权值相加就是最小生成树,其值为2+1+2+4+3=12。

prim算法的实现:

  1. //prim算法  
  2.   
  3. int prim(int n){  
  4.   
  5.          int i,j,min,pos;  
  6.   
  7.          int sum=0;  
  8.   
  9.          memset(isvisited,false,sizeof(isvisited));  
  10.   
  11.    
  12.   
  13.          //初始化  
  14.   
  15.          for(i=1;i<=n;i++){  
  16.   
  17.                    dist[i]=map[1][i];  
  18.   
  19.          }  
  20.   
  21.    
  22.   
  23.          //从1开始  
  24.   
  25.          isvisited[1]=true;  
  26.   
  27.          dist[1]=MAX;  
  28.   
  29.    
  30.   
  31.          //找到权值最小点并记录下位置  
  32.   
  33.          for(i=1;i<n;i++){  
  34.   
  35.                    min=MAX;  
  36.   
  37.                    //pos=-1;  
  38.   
  39.                    for(j=1;j<=n;j++){  
  40.   
  41.                             if(!isvisited[j] && dist[j]<min){  
  42.   
  43.                                      min=dist[j];  
  44.   
  45.                                      pos=j;  
  46.   
  47.                             }  
  48.   
  49.                    }          
  50.   
  51.                    sum+=dist[pos];//加上权值  
  52.   
  53.                    isvisited[pos]=true;  
  54.   
  55.    
  56.   
  57.                    //更新权值  
  58.   
  59.                    for(j=1;j<=n;j++){  
  60.   
  61.                             if(!isvisited[j] && dist[j]>map[pos][j]){  
  62.   
  63.                                      dist[j]=map[pos][j];  
  64.   
  65.                             }  
  66.   
  67.                    }  
  68.   
  69.          }          
  70.   
  71.          return sum;    
  72. }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PegasusWang_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值