最小生成树--学习笔记

我们把构造连通图的最小代价称为最小生成树。

KrusKal算法

既然要求是边的总长度最短,那我们先把所有的边按照权值从小到大排序,再按照边的权值从小到大去选,直到选择了n-1条边,让整个图连通。

我们可以利用并查集,将所有的顶点放在一个并查集中,判断两个顶点是否连通,只需判断两个顶点是否在同一个集合(即是否有共同的祖先)即可。

算法思路:

首先按照边的权值进行从小到大排序,每次从剩余的边中选择权值较小,且边的两个顶点不在同一个集合内的边(就是不会产生回路的边),加入到生成树中,直到加入了n-1条边为止。

 

#include<stdio.h>
struct node
{
    int u;
    int v;
    int w;
} e[200005];
int n,m;
int f[200005]= {0},sum,count,sign;
void sort(int l,int r)//ÅÅÐò
{
    int i,j;
    struct node t;
    if(l>r)
        return ;
    i=l;
    j=r;
    while(i!=j)
    {
        while(e[j].w>=e[l].w&&i<j)
            j--;
        while(e[i].w<=e[l].w&&i<j)
            i++;
        if(i<j)
        {
            t=e[i];
            e[i]=e[j];
            e[j]=t;
        }
    }
    t=e[l];
    e[l]=e[i];
    e[i]=t;
    sort(l,i-1);
    sort(i+1,r);
    return ;

}

int getf(int v)
{
    if(f[v]==v)
        return v;
    return getf(f[v]);
}

int fun(int v,int u)
{
    int t1,t2;
    t1=getf(v);
    t2=getf(u);
    if(t1!=t2)
    {
        f[t2]=t1;
        return 1;
    }
    return 0;
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1; i<=m; i++)
        scanf("%d %d %d",&e[i].u,&e[i].v,&e[i].w);
    sort(1,m);
    for(int i=1; i<=n; i++)
        f[i]=i;
    for(int i=1; i<=m; i++)
    {
        if(fun(e[i].u,e[i].v))
        {
            count++;
            sum=sum+e[i].w;
        }
        if(count==n-1)
        {
            sign=1;
            break;
        }
    }
    if(sign)
        printf("%d",sum);
    else printf("orz");
    return 0;
}

Prim算法

算法思路:

我们将途中所有的顶点分为两类:树顶点(已被选入生成树的顶点)和非树顶点(还未被选入生成树的顶点)。首先选择任意一个顶点加入生成树然后枚举每一个树顶点到每一个非树顶点所有的边,然后找到最短边加入到生成树。照此方法,重复n-1次,直到所有顶点都加入生成树中。

算法的难点在于,如何找出下一个添加到生成树的边。这个时候,就要请出Dijkstra算法。

算法流程

1.从任意一个顶点开始构造生成树,假设从一号点开始。首先将顶点1加入生成树中,用一个一维数组book标记哪些点加入了生成树。

2.用数组dis数组记录生成树到各个顶点的距离。最初生成树中只有1号顶点,有直连边时数组dis数组中存储的就是1号顶点到该顶点的权值,没有直连边时,就是无穷大,即初始化数组dis。

3.从数组dis中选出离生成树最近的顶点(假设为j)加入到生成树中(即在dis数组中找最小值)。再以j为中间点,更新生成树到每一个非树顶点的距离(即松弛),如果dis[k]>e[j][k]则更新dis[k]=e[j][k]。

4.重复第三步,直到生成树中有n个顶点为止。

代码展示

#include<stdio.h>
int main()
{
    int n,m,i,j,k,min,t1,t2,t3;
    int e[7][7],dis[7],book[7]= {0};
    int inf=99999999;
    int count=0,sum=0;//count用来记录生成树中顶点的个数,sum用来存储路径之和
    scanf("%d %d",&n,&m);

    //初始化
    for(i=1; i<=n; i++)
        for(j=1; j<=n; j++)
        {
            if(i==j)e[i][j]=0;
            else e[i][j]=inf;
        }
    //读边
    for(i=1;i<=m;i++)
    {
        scanf("%d %d %d",&t1,&t2,&t3);
        //无向图,两边都要存
        e[t1][t2]=t3;
        e[t2][t1]=t3;
    }
    //初始化dis数组
    for(i=1;i<=n;i++)
        dis[i]=e[1][i];
    //Prim核心
    book[1]=1;
    count++;
    while(count<n)
    {
        min=inf;
        for(i=1;i<=n;i++)
        {
            if(book[i]==0&&dis[i]<min)
            {
                min=dis[i];
                j=i;
            }
        }
        book[j]=1;
        count++;
        sum=sum+dis[j];
        //扫描当前顶点j所有的边,再以j为中间点,更新生成树到每一个非树顶点的距离
        for(k=1;k<=n;k++)
        {
            if(book[k]==0&&dis[k]>=e[j][k])
                dis[k]=e[j][k];
        }
    }
    printf("%d",sum);
    return 0;
}

上面这种方法时间复杂度为O(N^2)。如果借助堆,时间复杂度为O(MlogN)

#include<stdio.h>
int dis[7],book[7]= {0};
int h[7],pos[7],size;//h用来保存堆,pos用来存储每个顶点在堆中的位置,size为堆的大小

//交换函数
void swap(int x,int y)
{
    int t;
    t=h[x];
    h[x]=h[y];
    h[y]=t;

    //同步更新pos
    t=pos[h[x]];
    pos[h[x]]=pos[h[y]];
    pos[h[y]]=t;
}

//向下调整函数
void down(int i)
{
    int t,flag=0;//flag用来标记是否需要继续向下调整
    while(i*2<=size&&flag==0)
    {
        //比较i和它左儿子i*2在dis中的值,并用t记录值较小的结点编号
        if(dis[h[i]]>dis[h[i*2]])
            t=i*2;
        else t=i;
        //如果右儿子的值更小,再对右儿子进行讨论
        if(i*2+1<=size)
        {
            //如果右儿子的值更小,更新较小的结点编号
            if(dis[h[t]]>dis[h[i*2+1]])
                t=i*2+1;
        }
        //如果发现最小的结点编号不是自己,说明子结点中有比父结点更小的
        if(t!=i)
        {
            swap(t,i);//交换它们
            i=t;//更新i为刚才与它交换的儿子结点的编号,便于接下来继续向下调整
        }
        else
            flag=1;//否则说明当前父结点已经比两个子结点都要小了,不需要再进行调整了
    }
}

void up(int i)//向上调整
{
    int flag=0;
    if(i==1) return ;
    while(i!=1&&flag==0)
    {
        if(dis[h[i]]<dis[h[i/2]])
            swap(i,i/2);
        else flag=1;
        i=i/2;
    }
}

//从堆顶取出一个元素
int pop()
{
    int t;
    t=h[1];//用一个临时变量记录堆顶点的值
    pos[t]=0;
    h[1]=h[size];//将堆顶的最后一个点赋值到堆顶
    pos[h[1]]=1;
    size--;//堆的元素减少1
    down(1);//向下调整
    return t;//返回之前记录的堆顶点
}

int main()
{
    int n,m,i,j,k;
    //u,v,w和next的数组大小要根据实际情况来设置,此图为无向图,要比2*m的最大值要大1
    //first要比n的最大值要大1,要比2*m的最大值要大1
    int u[19],v[19],w[19],frist[7],next[19];
    int inf=99999999;
    int count=0,sum=0;
    scanf("%d %d",&n,&m);

    for(i=1;i<=m;i++)
        scanf("%d %d %d",&u[i],&v[i],&w[i]);

    for(i=m+1;i<=2*m;i++)
    {
        u[i]=v[i-m];
        v[i]=u[i-m];
        w[i]=w[i-m];
    }

    //开始使用邻接表存储边
    for(i=1;i<=n;i++)
        frist[i]=-1;

    for(i=1;i<=2*m;i++)
    {
        next[i]=frist[u[i]];
        frist[u[i]]=i;
    }

    //Prim核心部分
    book[1]=1;
    count++;

    dis[i]=0;
    for(i=2;i<=n;i++)
        dis[i]=inf;
    k=frist[1];
    while(k!=-1)
    {
        dis[v[k]]=w[k];
        k=next[k];
    }

    //初始化堆
    size=n;
    for(i=1;i<=size;i++)
    {
        h[i]=i;
        pos[i]=i;
    }
    for(i=size/2;i>=1;i--)
        down(i);
    pop();
    while(count<n)
    {
        j=pop();
        book[j]=1;
        count++;
        sum=sum+dis[j];

        k=frist[j];
        while(k!=-1)
        {
            if(book[v[k]]==0&&dis[v[k]]>w[k])
            {
                dis[v[k]]=w[k];
                up(pos[v[k]]);
            }
            k=next[k];
        }
    }
    printf("%d",sum);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值