最小生成树

注:我在修炼当中,博客完全是为了自己复习方便看得,如果你不慎点入了我的博客,看看就好,不要相信,误人子弟就不好了- -

最小生成树有Prim 和 Kruskal 两种经典算法 ,这两个呢都是利用MST性质来构造最小生成树的算法的,如果忘记了MST,去翻我的博客。

先说说prim算法,有个V点集,从中先选出一个点u0放入U点集中,然后找到一条与u0相连代价最小的边,且该边另一个点v0在V - U点集中。此时将该边加入最小生成树的边的集合中,然后将v0加入到U点集中,之后不断重复该操作,直到所有点都加入到了U点集中。

Prim + 优先队列

不会算时间复杂度,GG,T_T,大概是O(nloge)的,注意优先队列时间复杂度为O(logn)的

#include <iostream>
#include <queue>
#include<cstring>
#include<cstdio>
using namespace std;

typedef struct
{
    long v;
    long next;
    long cost;
}Edge;

typedef struct
{
    long v;
    long cost;
}node;


bool operator <(const node &a,const node &b)
{
    return a.cost>b.cost;
}

priority_queue<node> q;

const long MAXN=10000;

Edge e[MAXN];
long p[MAXN];
bool vist[MAXN];

long m,n;
long from,to,cost;

void init()
{
    memset(p,-1,sizeof(p));
    memset(vist,0,sizeof(vist));

    while (!q.empty())
        q.pop();

    long i;
    long eid=0;
    for (i=0;i<n;++i)
    {
        scanf("%ld %ld %ld",&from,&to,&cost);

        e[eid].next=p[from];
        e[eid].v=to;
        e[eid].cost=cost;
        p[from]=eid++;

        //以下适用于无向图

        swap(from,to);

        e[eid].next=p[from];
        e[eid].v=to;
        e[eid].cost=cost;
        p[from]=eid++;

    }
}

void Prim()
{
    long cost=0;

    init();
    node t;

    t.v=from;//选择起点
    t.cost=0;

    q.push(t);


    long tt=0;

    while (!q.empty()&&tt<m)
    {
        t=q.top();
        q.pop();

        if (vist[t.v])
        {
            continue;
        }

        cost+=t.cost;
        ++tt;

        vist[t.v]=true;

        long j;
        for (j=p[t.v];j!=-1;j=e[j].next)
        {
            if (!vist[e[j].v])
            {
                node temp;
                temp.v=e[j].v;
                temp.cost=e[j].cost;
                q.push(temp);
            }
        }
    }

    printf("%ld\n",cost);
}


int main()
{
    while (scanf("%ld %ld",&m,&n)!=EOF) //m 代表点  n代表边数
    {
        Prim();
    }
    return 0;
}

Prim

时间复杂度是O(n2),与边数无关,适合求边稠密的网。

int minTree(int n)
{
    int i,j;
    memset(v,0,sizeof(v));
    v[0]=1;
    sum=0;
    for(i=1;i<n;i++)
    {
        min=max;
        for(j=0;j<n;j++)
        {
            if(!v[j]&&map[0][j]<min)
            {
                min=map[0][j];
                flag=j;
            }
        }
        sum+=min;
        v[flag]=1;
        for(j=0;j<n;j++)
        {
            if(!v[j]&&map[0][j]>map[flag][j])
            {
                map[0][j]=map[flag][j];
            }
        }
    }
    return sum;
}

判断次小生成树

#include <iostream>
using namespace std;

#define MAX 101
#define INF 999999999
#define max(a,b) (a>b?a:b)

int dis[MAX], pre[MAX];
int edge[MAX][MAX];
int maxVal[MAX][MAX];
bool inTree[MAX][MAX];
bool vis[MAX];

int Prime ( int n )
{
    int i, j, k, minc, mst;
    for ( i = 1; i <= n; i++ )
    {
        dis[i] = edge[1][i];
        vis[i] = false;
        pre[i] = 1;
    }

    dis[1] = mst = 0; 
    vis[1] = true;

    for ( i = 2; i <= n; i++ ) 
    {
        minc = INF; k = -1;
        for ( j = 1; j <= n; j++ )
        {
            if ( ! vis[j] && dis[j] < minc )
            {
                minc = dis[j];
                k = j;
            }
        }
        if ( minc == INF ) return -1;  // 图不连通,没有找到最小生成树
        mst += minc;
        vis[k] = true;
        inTree[pre[k]][k] = inTree[k][pre[k]] = true;  // 记录加入的树中的边

        for ( j = 1; j <= n; j++ )
            if ( vis[j] == true )
                maxVal[j][k] = max ( maxVal[j][pre[k]], edge[pre[k]][k] ); // 找j-k的路径上权值最大的那条边,并记录在maxVal[j][k]中

        for ( j = 1; j <= n; j++ )
        {
            if ( ! vis[j] && dis[j] > edge[k][j] )
            {
                dis[j] = edge[k][j];
                pre[j] = k;   // 修正前驱
            }
        }
    }
    return mst;
}

void initial ( int n )
{
    for ( int i = 1; i < n; i++ )
    {
        for ( int j = i + 1; j <= n; j++ )
        {
            edge[i][j] = edge[j][i] = INF;
            inTree[i][j] = inTree[j][i] = 0;
            maxVal[i][j] = maxVal[j][i] = 0;
        }
    }
}   

int main()
{
    int t, n, m, u, v, w;
    scanf("%d",&t); 
    while ( t-- )
    {
        scanf("%d%d",&n,&m);
        initial ( n );
        while ( m-- )
        {
            scanf("%d%d%d",&u,&v,&w);
            edge[u][v] = edge[v][u] = w;
        }

        int mst = Prime ( n );
        if ( mst < 0 ) { printf("0\n"); continue; }

        int res;
        bool flag = false;
        for ( int i = 1; i < n; i++ )
        {
            for ( int j = i + 1; j <= n; j++ )
            {
                if ( inTree[i][j] || edge[i][j] == INF ) continue; // 边edge[i][j]在树中或者i,j之间无边
                res = mst + edge[i][j] - maxVal[i][j]; // 用边edge[i][j], 替换i-j路径上权值最大的那条边,得到一棵新的生成树
                if ( res == mst ) { flag = true; break; }
            }
            if ( flag ) break;
        }

        if ( flag )
            printf("Not Unique!\n");
        else
            printf("%d\n",mst);
    }
    return 0;
}

Kruskal

该算法是初始时所有点自成一个连通分量,然后在所有边中选择一条权重最小的边放入边集中,然后再寻找下一条最小的边,并且将该边加入边集的条件是该边
时间复杂度为O(eloge),适合边稀疏的网

#include<iostream>//这里的n代表节点数,给节点初始化,m代表边数,需要给边数排序,
#include<stdio.h>
#include<string.h>//kruskal算法主要用的是并查集
#include<algorithm>
using namespace std;
int inf=999999;
int fa[110000];
int n,m;
struct node
{
    int u,v,w;

}q[110000];
void make_set()
{
    for(int i=1;i<=n;i++)
    {
        fa[i]=i;
    }
}
int Find(int x)
{
    if(x!=fa[x])
        fa[x]=Find(fa[x]);
        return fa[x];

}
bool cmp(node x,node y)
{
    return x.w<y.w;
}
int kru()
{  int ans=0,cnt=0;
    make_set();
    sort(q+1,q+m+1,cmp);//排序要从起始地址,到终止地址此题中i是从1开始的,
    for(int i=1;i<=m;i++)
    {
        int fx=Find(q[i].u);
        int fy=Find(q[i].v);
        if(fx!=fy)
          {

           fa[fx]=fy;
           ans+=q[i].w;
            cnt++;
          }
    }
    return ans;
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&q[i].u,&q[i].v,&q[i].w);

        }

        printf("%d\n",kru());
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值