最小生成树

集训第一天

莫名其妙把并查集和最小生成树一起学习了一下,并查集倒是经常会用到克鲁斯卡尔算法,挺好的

最小生成树Prim算法实现

(所谓生成树,就是n个点之间连成n-1个点的图形,而最小生成树,就是权值之和的最小值。)

                                                                                                                   


     用二维数组记录点和权值。如上图所示无向图:

int map[7][7];
       map[1][2]=map[2][1]=4;
       map[1][3]=map[3][1]=2;
       ......

      然后再求最小生成树。具体方法是:

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

visited[1]=1;

pos=1;

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

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

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

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

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

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

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

 

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

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

visited[3]=1;

pos=3;

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

low[2]=map[1][2]=4;  //比5小,不更新

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

low[4]=map[1][4]=3;   //比1大,更新后为:low[4]=map[3][4]=1;

low[5]=map[1][5]=MaxInt;//无法直达,不更新

low[6]=map[1][6]=MaxInt;//比2大,更新后为:low[6]=map[3][6]=2;

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

    

                                                                        

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


POJ1258


Sample Input

4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0

Sample Output

28


题意: 给你N*N矩阵,表示N个村庄之间的距离。FJ要把N个村庄全都连接起来,求连接的最短距离。

下面是代码,很详细

#include <stdio.h>
#include <string.h>
#define MaxInt 0x3f3f3f3f
#define N 110
//创建map二维数组储存图表,low数组记录每2个点间最小权值,visited数组标记某点是否已访问
int map[N][N],low[N],visited[N];
int n;
 
int prim()
{
    int i,j,pos,min,result=0;
    memset(visited,0,sizeof(visited));
//从某点开始,分别标记和记录该点
    visited[1]=1;pos=1;
//第一次给low数组赋值
    for(i=1;i<=n;i++)
        if(i!=pos) low[i]=map[pos][i];
//再运行n-1次
    for(i=1;i<n;i++)
    {
//找出最小权值并记录位置
     min=MaxInt;
     for(j=1;j<=n;j++)
         if(visited[j]==0&&min>low[j])
         {
             min=low[j];pos=j;
         }
//最小权值累加
    result+=min;
//标记该点
    visited[pos]=1;
//更新权值
    for(j=1;j<=n;j++)
        if(visited[j]==0&&low[j]>map[pos][j])
            low[j]=map[pos][j];
    }
    return result;
}
 
int main()
{
    int i,v,j,ans;
    while(scanf("%d",&n)!=EOF)
    {
//所有权值初始化为最大
        memset(map,MaxInt,sizeof(map));
        for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
            {
                scanf("%d",&v);
                map[i][j]=map[i][j]=v;
            }
            ans=prim();
            printf("%d\n",ans);
    }
    return 0;
}

最小生成树Kruskal算法实现

加上并查集,会优化一些

并查集的精妙之处在于用数来表示集合。如果把x的父结点保存在p[x]中(如果没有父亲,p[x]=x),则不难写出结点x所在树的递归程序:

find(int x) {return p[x]==x?x:p[x]=find(p[x]);}

意思是,如果p[x]=x,说明x本身就是树根,因此返回x;否则返回x的父亲p[x]所在树的根结点。

既然每棵树表示的只是一个集合,因此树的形态是无关紧要的,并不需要在“查找”操作之后保持树的形态不变,只要顺便把遍历过的结点都改成树根的儿子,下次查找就会快很多了。

模板代码

<pre name="code" class="cpp">struct KRUSKAL  
{  
    const int MAXN = 109;  
    const int MAXE = 5009;  
  
    struct EDGE  
    {  
        int u, v, length, choose;  
    }   edge[ MAXE ];  
  
    int path[ MAXN ];  
    int N, edgecnt, sum;  
  
    void Addedge(int u, int v, int len)  
    {  
        ++edgecnt;  
        edge[ edgecnt ].u = u;  
        edge[ edgecnt ].v = v;  
        edge[ edgecnt ].length = len;  
        edge[ edgecnt ].choose = false;  
        return ;  
    }  
  
    void Set()  
    {  
        for (int i = 1; i <= N; i++)  
            path[i] = i;  
        return ;  
    }  
  
    int Find_Path(int x)  
    {  
        if (x != path[x]) path[x] = Find_Path( path[x] );  
        return path[x];  
    }  
  
    int Work()  
    {  
        int cnt = 0, x, y;  
        Qsort(1, edgecnt);  // i < j -> edge[i].length < edge[j].length  
        Set();  
        for (int i = 1; i <= E && cnt < N - 1; i++)  
        {  
            x = Find_Path( edge[i].u );  
            y = Find_Path( edge[i].v );  
            if (x == y) continue;  
            path[x] = y;  
            edge[i].choose = true;
            ++cnt;
            sum += edge[i].length;  
        }  
        return sum;  
    }  
}   Kruskal;  


 
 

HDU 1863(基础模板题)一系列畅通工程

//***************************************  
//      hdu 1863 (kruskal)  
//  题意:路修通成本最低
// 
#include <stdio.h>  
#include <algorithm>  
#include <iostream>  
using namespace std;  
struct Fuck  
{  
    int u,v,w;  
}fuck[110];  
int p[110],m,n,sum;  
int cmp(Fuck x,Fuck y)  
{  
    return x.w<y.w;  
}  
int find(int x)  
{  
    return x==p[x]?p[x]:find(p[x]);  
}  
void Kruskal()  
{  
    int i,ans=0;  
    for(i=1;i<=m;i++)  
    p[i]=i;  
    sort(fuck+1,fuck+n+1,cmp);  
    for(i=1;i<=n;i++)  
    {  
        int x=find(fuck[i].u);  
        int y=find(fuck[i].v);  
        if(x!=y)  
        {  
            sum++;  
            ans+=fuck[i].w;  
            p[x]=y;  
        }  
    }  
    if(sum==m)  
    printf("%d\n",ans);  
    else  
    printf("?\n");  
}  
int main()  
{  
    while(cin>>n>>m,n)  
    {  
        int i;  
        sum=1;  
        for(i=1;i<=n;i++)  
        scanf("%d%d%d",&fuck[i].u,&fuck[i].v,&fuck[i].w);  
        Kruskal();  
    }  
    return 0;  
}  
<span style="font-size:24px;"><strong>HDU1879</strong></span>
<pre name="code" class="cpp">//*********************************************  
//           hdu 1879  (kruskal)  
//加了有无修过,修过的成本就是0,修过是1。遍历所有点即可 ,这种想法比较好写。但这里写的是修过的连起来,没修过的加一起

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<iostream>
#include<algorithm>
const int maxn=110000;
using namespace std;

struct Point
{
       int u,v,w,vis;
       }point[11000];

int p[110],n,m,i;

bool cmp(Point x, Point y)
{
     if(x.vis!=y.vis)
     return x.vis<y.vis;
     else return x.w<y.w;
     }
     
int find(int x)  
{  
    return x==p[x]?p[x]:find(p[x]);  
}  
void kruskal()
{
     int ans=0,i;
     sort(point+1,point+1+m,cmp);
     for(i=1;i<m;i++)
     {
          int x=find(point[i].u);
          int y=find(point[i].v);
          if(x!=y&&!point[i].vis)
          {
              ans+=point[i].w;
              p[x]=y;
                               }}
              printf("%d\n",ans);
     }
void merge(int x,int y)  
{  
    x=find(x);  
    y=find(y);  
    if(x!=y)  
    p[x]=y;  
}  

int main()
{
    while(scanf("%d",&n)&&n)
    {
        int i;
        m=n*(n-1)/2;
        for(i=1;i<=n;i++)
        p[i]=i;
        for(i=1;i<=m;i++)
        {
          scanf("%d%d%d%d",&point[i].u,&point[i].v,&point[i].w,&point[i].vis);
          if(point[i].vis==1)
          merge(point[i].u,point[i].v);
                         }
              kruskal();              }
    }


 
 







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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值