POJ2485-Highways 最小生成树求法——Kruskal和Prim算法

目录

——一只菜鸟,还望斧正、指教;

Kruskal Prim算法:

Kruskal:

算法步骤:

Prim算法:

代码如下:

Kruskal:

prim:


Kruskal Prim算法:

该题其实就是求最小生成树,首先介绍下求最小生成树的两个算法:kruskal算法 prim算法。

Kruskal:

最早接触最小生成树是在离散数学,而在离散数学求解的过程中其实利用的就是Kruskal算法思想,首先介绍一下Kruskal算法。

算法步骤:

该算法主要是:

  1. 将边权重从小到大排序
  2. 在未必选择的边中选择权重最小的
  3. 判断该边与已选边形成的树是否形成环(可以利用并查集(在此不再细述,简而言之,判断是否在一棵树上即是否连通)思想,如果本身就连通,再连接肯定会形成环,不连通肯定不会形成环)
  4. 不成环加入该边返回到2,否则直接返回2,然后直到所有顶点在该生成树上。

例如该图(中间点F 忘记标了):

  1. 排序 AF  DE  BC  CF  BF  AD  FD  CE  FE  AB
  2. 点A、F不连通 加入边AF
  3. 点D、E不连通 加入边DE
  4. 点B、C不连通 加入边BC
  5. 点C、F不连通 加入边CF
  6. 点B、F连通(B-C-F) 无法加入边BF
  7. 点A、D不连通 加入边AD
  8. 全部连通 算法结束 最小生成树权值为AF+DE+BC+CF+AD=1+2+3+4+5=15.

Prim算法:

       如果说Kruskal是加边法 那么Prim算法就是加点法

首先定义:在两个连通分支中,所有连接该两个连通分支的线段,权值最小的叫做轻量级边。

如上图中对于{C、F、E}和{A、B、D} AF=1就为轻量级边(算法导论第三版有讲解轻量级边,在此稍作介绍),记录轻量级边由些dijkstra的味道。

       如何加点?对于已选择的顶点的集合S,和未选顶点的集合U中,选择轻量级边加入,轻量级边在U中的顶点加入S,直到所有顶点在S中,算法截止。

也就是说,初始随机选择源点v0(任何一个都行),每次迭代选择代价最小的边加入选择的点中形成的树,这里无需判断是否连通,因为轻量级边已经将两分支分割了。

也就是由一个源点逐渐扩大覆盖整个连通网的过程。

代码如下:

Kruskal:

#include<algorithm>

#include<iostream>

#include<cstring>

#include<cstdio>

#include<cmath>

#include<stdlib.h>



using namespace std;

typedef long long ll;

const int INF = 0x3f3f3f3f;

int far[550];

struct ed{

       int x;

       int y;

       int s;

}e[25000];

bool cmp(ed x,ed y){

       return x.s < y.s;

}

int n;

int fa(int x){//找到一个结点的最原始的祖先结点

       while(x != far[x]){

              far[x] = far[far[x]];

              x = far[x];

       }

       return x;

}

void _union(int x,int y){//连接两个结点,就是连接两个结点分别对应的最原始的祖先结点

       int tx = fa(x);

       int ty = fa(y);

       far[tx] = ty;

}

int main(){

       int t;

       cin >> t;

       while(t--){

              cin >> n;

              int k = 0;

              for(int i = 1;i <= n;i++){

                     far[i] = i;

                     for(int j = 1;j <= n;j++){

                            int m;

                            cin >> m;

                            if(i < j){//用struct代替二维数组,便于并查集的展开

                                   k++;

                                   e[k].x = i;

                                   e[k].y = j;

                                   e[k].s = m;

                            }

                     }

              }

              sort(e+1,e+1+k,cmp);//路径由小到大排序

              int maxs = 0;

              for(int i = 1;i <= k;i++){//如果不连通就连起来

                     if(fa(e[i].x)!= fa(e[i].y)){

                            _union(e[i].x,e[i].y);

                            maxs = max(maxs,e[i].s);

                     }

              }

              cout << maxs << endl;

       }

       return 0;

}

prim:

#include<algorithm>

#include<iostream>

#include<cstring>

#include<cstdio>

#include<cmath>

#include<stdlib.h>



using namespace std;

typedef long long ll;

const int INF = 0x3f3f3f3f;

int _map[550][550];

int dis[550];

int n;

int flag[550];

void prim(int v0){

       int maxs = 0;

       for(int i = 1;i <= n;i++){

              dis[i] = _map[v0][i];

       }

       flag[v0] = 1;//S中仅含有源点v0,到S连通分支的路径长度就是到源点v0的路径长度

       for(int i = 2;i <= n;i++){//加入n-1个点 遍历n-1次 仅仅起到了遍历次数的作用

              int minn = INF,u = v0;

              for(int j = 1;j <= n;j++){

                     if(!flag[j] && dis[j] < minn){

                            u = j;

                            minn = dis[j];

                     }

              }

              flag[u] = 1;  //对每一个在U中的遍历 找到轻量级边(因为不直接连接到S的长度为INF //肯定不是)

              maxs = max(maxs,dis[u]);//每一次的轻量级边都是最小生成树的一部分

              for(int j = 1;j <= n;j++){//对每一个U中的点到S连通分支距离的更新

                     if(!flag[j] && _map[u][j] < dis[j]){

                            dis[j] = _map[u][j];

                     }

              }

       }

       cout << maxs << endl;

}

int main(){

       int T;

       cin >> T;

       while(T--){

              cin >> n;

              for(int i = 1;i <= n;i++)

                     for(int j = 1;j <= n;j++)

                            cin >> _map[i][j];

              memset(flag,0,sizeof(flag));

              prim(1);//随机抽取源点 不妨为1;

       }

       return 0;

}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值