最小生成树Prim算法和Kruskal算法

在学习最小生成树算法之前我们先了解一些基本概念

生成树与最小生成树

生成树是一个连通图的连通子图,包含图中的n个点,n-1条边。最小生成树是这n-1条边的权值之和最小的一颗生成树。

Prim算法

Prim算法可在加权连通图里搜索最小生成树,通俗来说就是在一个带权值的连通图中保留n-1条边(这n-1条边的权值和最小),但依旧使这个图为连通图。Prim算法是基于贪心的思想,每次都选择一条符合条件的权值最小的边对于的点加入生成树

Prim算法的时间复杂度为O(N^2),适合稠密图

通常最小生成树问题一般用邻接矩阵存图,比较方便

流程为:

首先初始化数组dis【】、pre【】,dis【i】表示点 i 在生成树中需要耗费的代价,dis【1】 = 0;pre【i】代表生成树中pre【i】与点 i 相连(前驱),pre【1】 = -1,代表没有前驱,vis【i】 = 1表示以访问。

id123456
vis100000
dis0615infinf
pre- 11111

1

 

之后从上表中选一个dis【】距离最小,且vis【】 = 0 的点,然后用这个点去更新他邻接点(未访问)在最小生成树中的距离,且更新前驱,这里的点为3

更新上表得

id123456
vis101000
dis051564
pre- 13113

3

选择点6,更新其他点

id123456
vis101001
dis051264
pre- 13163

3

 

接下来符合条件的点为4,更新其他数组

id123456
vis101101
dis051264
pre- 13163

3

接下来选择点2,更新数据

id123456
vis111101
dis051234
pre- 13162

3

接下来是点5,已更新完所有数据,dis【】数组中卫最小生成树的权值,pre【】数组中记录的是最小生成树的边

 

例题:HDU1102

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PI;
const int inf = 0x3f7f7f7f;
const int N = 105;
int mp[N][N];
int lowcost[N];
int pre[N];
int vis[N];
int n;
int prim(int s)
{
    int ans = 0;
    memset(vis,0,sizeof(vis));
    for(int i = 1; i <= n; i ++){
        lowcost[i] = mp[s][i];
        pre[i] = s;
    }
    vis[s] = 1;
    pre[s] = -1;
    for(int i = 1; i < n; i ++)
    {
        int pos = -1;
        int mmin = inf;
        for(int j = 1; j <= n; j ++){
            if(vis[j]==0 && lowcost[j] < mmin){
                pos = j;
                mmin = lowcost[j];
            }
        }
        if(pos == -1) return -1;

        vis[pos] = 1;
        ans += mmin;
        for(int j = 1; j <= n; j ++){

            if(lowcost[j] > mp[pos][j] && vis[j] ==0){
                lowcost[j] = mp[pos][j];
                pre[j] = pos;
            }
        }
    }
    return ans;
}
int main()
{

    while(~scanf("%d",&n))
    {
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
                scanf("%d",&mp[i][j]);

        int q;
        scanf("%d",&q);
        for(int i = 0; i < q; i ++){
            int u,v;
            scanf("%d%d",&u,&v);
            mp[u][v] = mp[v][u] = 0;
        }
        printf("%d\n",prim(1));
/*
        for(int i = 2; i <= n; i ++)
        {
            printf("%d-->%d\n",i,pre[i]);
        }
    }
*/
    return 0;
}
//HDU1102

 

Kruskal算法

在学习这个算法之前,我们需要先学习一种数据结构:并查集

并查集:.是一种关于集合的操作,能够快速的找到某个元素所在的集合,而且还能够实现集合之间的合并。

并查集包含三种操作:

1、初始化操作:最开始将每个元素出事化为一个集合。pre【i】代表着i点所在的集合,最开始pre【i】 = i;

2、查找元素所在的集合:也就是查找这个集合所有元素的祖先

如pre【a】 = b,pre【b】 = c,pre【c】 = d,pre【d】 = d,这里d就是所有元素的祖先

3、合并操作,合并两个不同的集合,也就是找到这两个集合的祖先,使其中一个祖先指向另一个集合。

并查集的优化:

这个图中我们可以看出要找到点a的祖先,需要经过好几次循环,那么我就可以让这个集合中的所有元素都直接指向祖先,这样就可以在下次查找时O(1)的复杂度就能够找到。

关键代码:

int findx(int x)
{
    int px = x;
    while(pre[x] != x)
        x = pre[x];

    while(px != x)
    {
        int pr = pre[px];
        pre[px] = x;
        px = pr;
    }
    return x;
}
void link(int u, int v)
{
    pre[u] = v;
}

Kruskal算法

kruskal算法是基于贪心的思想,利用并查集实现的,每次选着一条权值最小的符合条件的边(条件:这条边的两个点不再同一个集合)将其加入生成树集合

过程:

1、将边按权值大小排序

2、选择一条权值最小的边,且这条边的两点属于不同的集合,然后合并这两个集合

3、重复以上操作直到所有点都属于一个集合

例题:HDU1102

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PI;
const int inf = 0x3f7f7f7f;
const int N = 200;
int mp[N][N];
int pre[N];
int n;
struct edge
{
    int u,v,w;

}grap[N*N];
bool cmp(edge a,edge b)
{
    return a.w < b.w;
}
int findx(int x)
{
    int px = x;
    while(pre[x] != x)
        x = pre[x];

    while(px != x)
    {
        int pr = pre[px];
        pre[px] = x;
        px = pr;
    }
    return x;
}
void link(int u, int v)
{
    pre[u] = v;
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
                scanf("%d",&mp[i][j]);
        int q;
        scanf("%d",&q);
        for(int i = 0; i < q; i ++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            mp[u][v] = mp[v][u] = 0;
        }
        int cnt = 0;
        for(int i = 1; i<= n*n; i ++)
        {
            for(int j = i+1; j <= n; j ++)
            {
                grap[cnt].u = i;
                grap[cnt].v = j;
                grap[cnt++].w = mp[i][j];
            }
        }

        for(int i = 1; i <= n; i ++)
            pre[i] = i;
        sort(grap,grap+cnt,cmp);
        int ans = 0;
        for(int i = 0; i < cnt; i ++)
        {
            int u = grap[i].u;
            int v = grap[i].v;
            if(findx(u) != findx(v)){
                ans += grap[i].w;
                link(findx(u),findx(v));
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}
//HDU 1102

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值