《算法笔记》第4章 入门篇(2)---算法初步 10.5 最小生成树

本文详细介绍了Prim算法和Kruskal算法求解无向图最小生成树的过程,并提供了两种算法的具体实现代码,包括邻接矩阵法和邻接表法。同时对比了两种算法适用的不同场景。

10.5.1 最小生成树及其性质

在这里插入图片描述

10.5.2 prim算法:(求一个无向图中的最小生成树)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

prim算法的具体实现:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

prim的邻接矩阵法+邻接表法:

/*
    邻接矩阵版本:
    
    1.设置最大顶点数,和INF数
    2.设置顶点数n和二维数组G
    3.设置bool类型 数组 vis 标记其值均为false
    
    int类型的prim函数
    1.默认从0号为初始起点,函数返回最小生成树的边权之和
    2.通过fill,将数组d初始化为INF
    3.设置0号顶点的距离为0
    4.设置int类型 ans为0
    5.和Dijie一样双层for循环找出距离最小的顶点和距离MIN
    6.如果u==-1,返回-1
    7.标价u已经被访问过
    8.将d[u]的值加入到ans中
    9.for循环v从0开始,一直到n,如果vis中没有访问过,u能到v,u为中介点,是v到集合的距离更小,则更新距离
    10.最后放回ans


*/

const int maxn=1000;
const int INF=10000000l;

int n,G[maxn][maxn],d[maxn];

bool vis[maxn]={false};

int prim()
{
    fill(d,d+maxn,INF);
    d[0]=0;
    int ans=0;
    for(int i=0; i<n; i++)
    {
        int u=-1,MIN=INF;
        for(int j=0; j<n; j++)
            if(vis[j]==false && d[j]<MIN)
            {
                u=j;
                MIN=d[j];
            }  
        if(u==-1)
            return -1;
        vis[u]=true;
        ans+=d[u];
        for(int v=0; v<n; v++)
        {
            if(vis[v]==false && G[u][v]!=INF && G[u][v]<d[v])
                d[v]=G[u][v];
        }
    }
    return ans;
}




/*
    邻接表版本:
    1.设置结构类型node:两个结构成员:v,dis
    2.设置vector的结构二维数组
    3.设置顶点数n,顶点距离集合最短的距离d,bool类型 vis
    
    int类型prim函数:
    1.初始化d的值为INF
    2.在d中设置0号位置的距离为0
    3.设置最小边权值为0
    4.双层for不变
    5.如果u为-1,则返回-1,标记u被访问过,将距离累加到ans中
    6.for循环,j从0开始,遍历结点u下的长度
        1.标记v是当前的顶点v
        2.更新距离
    7.最后返回ans值
*/


const int maxn=1000;
const int INF=10000000l;

struct node
{
    int v;
    int dis;
};

vector<node> Adj[maxn];

int n,d[maxn];

bool vis[maxn]={false};

int prim()
{
    fill(d,d+maxn,INF);
    d[0]=0;
    int ans=0;
    for(int i=0; i<n; i++)
    {
        int u=-1,MIN=INF;
        for(int j=0; j<n; j++)
            if(vis[j]==false && d[j]<MIN)
            {
                u=j;
                MIN=d[j];
            }  
        if(u==-1)
            return -1;
        vis[u]=true;
        ans+=d[u];
        for(int j=0; j<Adj[u].size(); j++)
        {
            int v=Adj[u][j].v;
            if(vis[v]==false && d[v]>Adj[u][j].dis)
                d[v]=Adj[u][j].dis;
        }
    }
    return ans;
}

亚历山大攻打恶魔的例子:

//邻接矩阵法:
#include<iostream> 
using namespace std;

const int maxn=100;
const int INF=1000000;

int G[maxn][maxn],n,m,d[maxn];

bool vis[maxn]={false};

int prime()
{
    fill(d,d+maxn,INF);
    d[0]=0;
    int ans=0;
    for(int i=0; i<n; i++)
    {
        int u=-1,MIN=INF;
        for(int j=0; j<n; j++)
        {
            if(vis[j]==false && d[j]<MIN)
            {
                u=j;
                MIN=d[j];
            }
        }
        if(u==-1)
            return -1;
        vis[u]=true;
        ans+=d[u];
        for(int v=0; v<n; v++)
        {
            if(vis[v]==false && G[u][v]!=INF && d[v]>G[u][v])
                d[v]=G[u][v];
        }
    }
    return ans;
}

int main()
{
    cin >> n >> m;
    fill(G[0],G[0]+maxn*maxn,INF);
    for(int i=0; i<m; i++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        G[a][b]=c;
        G[b][a]=c;
    }
    cout << prime();
    return 0;
}

//邻接表法:
#include<iostream>
#include<vector>
using namespace std;

const int maxn=100;
const int INF=1000000;

int n,m,d[maxn];

bool vis[maxn]={false};

struct node
{
    int v,dis;
    node(int _v,int _dis):v(_v),dis(_dis){};
};

vector<node> Adj[maxn];

int prime()
{
    fill(d,d+maxn,INF);
    d[0]=0;
    int ans=0;
    for(int i=0; i<n; i++)
    {
        int u=-1,MIN=INF;
        for(int j=0; j<n; j++)
        {
            if(vis[j]==false && d[j]<MIN)
            {
                u=j;
                MIN=d[j];
            }
        }
        if(u==-1)
            return -1;
        vis[u]=true;
        ans+=d[u];
        for(int j=0; j<Adj[u].size(); j++)
        {
            int v=Adj[u][j].v;
            if(vis[v]==false && d[v]>Adj[u][j].dis)
                d[v]=Adj[u][j].dis;
        }
    }
    return ans;
}

int main()
{
    cin >> n >> m;
    for(int i=0; i<m; i++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        Adj[a].push_back(node(b,c));
        Adj[b].push_back(node(a,c));
    }
    cout << prime();
    return 0;
}

10.5.3 kruskal算法:(求一个无向图中的最小生成树)

在这里插入图片描述

在这里插入图片描述

//krusla算法的结构:
struct node
{
    int u,v;      //边的两个端点编号
    int cost;       //边权
}E[maxn];     //最多有maxn条边

bool cmp(node a, node b)
{
    return a.cost<b.cost;
}


在这里插入图片描述
在这里插入图片描述

/*
    1.并查集数组:int类型 father[N]
    2.int类型 并查集查询函数 findFather 形参为int类型 x
        1.令a=x;
        2.通过whil找到x的根结点
        3.压缩路径
    
    3.int类型 kruskal函数,形参为 int类型 n,m分别表示顶点数和图的边数
        1.ans为所求边之和,Num_Edge为当前生成树的边数初始值均为0
        2.for循环,对并查集数组初始化
        3.调用sort函数,将所有边按照升序排列
        4.for循环枚举所有的边
            1.查询测试边两个端点的根结点
            2.如果两个端点不在一个集合中,合并(将测试边加入到最小生成树中),累加边权
            3.当前生成树的个数++
        5.如果Num_Edge的值为n-1,结束算法
        6.如果Num_Edge的值不为n-1,则返回-1
        7.否则,返回最小生成树的边权之和

*/

int father[n];

int findFather(int x)
{
    int a=x;
    while(x!=father[x])
    {
        x=father[x];
    }
    while(a!=father[a])
    {
        int z=a;
        a=father[a];
        father[z]=x;
    }
    return x;
}

int kruskal(int n, int m)
{
    int ans,Num_Edge;
    ans=0;
    Num_Edge=0;
    for(int i=0; i<n; i++)
        father[i]=i;
    sort(E,E+m,cmp);
    for(int i=0; i<m; i++)
    {
        int Fu=findFather(E[i].u);
        int Fv=findFather(E[i].v);
        if(Fu!=Fv)
        {
            E[Fu]=Fv;
            ans+=E[i].cost;
            Num_Edge++;
            if(Num_Edge==n-1)
                break;  
        }
    }
    if(Num_Edge!=n-1)
        return -1;
    else 
        return ans;
}

prim算法和krusal算法的不同使用情况

顶点数多+边数少=prim算法
顶点数少+边数多
=krusal算法

在这里插入图片描述
在这里插入图片描述

#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=100;
const int INF=10000000;

struct node       //设置一个结构
{
    int u,v;       //两个端点
    int cost;		//边权
}E[maxn];

int father[maxn],n,m;



bool cmp(node a, node b)   //按照边权的升序排列
{
    return a.cost<b.cost;
}

int findFather(int x)   //找到x的根结点
{
    int a=x;
    while(x!=father[x])
        x=father[x];
    while(a!=father[a])
    {
        int z=a;
        a=father[a];
        father[z]=x;
    }
    return x;
}

int krusal(int n, int m)    
{
    int ans=0,Num_Edge=0;   //设置ans为边权之和,Num_Edge为边数
    for(int i=0; i<n; i++)   //初始化并查集数组
        father[i]=i;
    sort(E,E+m,cmp);   //将结构按照边权的升序排列
    for(int i=0; i<m; i++)  //遍历每条边
    {
        int Fu=findFather(E[i].u);
        int Fv=findFather(E[i].v);
        if(Fu!=Fv)
        {
            father[Fu]=Fv;  //将Fu的根结点设为Fv,将两个集合合并
            ans+=E[i].cost;   //累加边权
            Num_Edge++;		//边权个数+1
            if(Num_Edge==n-1)		//如果边数为顶点数-1,则退出循环
                break;
        }
    }
    if(Num_Edge!=n-1)
        return -1;
    else
        return ans;   //返回最小生成树的权值
}


int main()
{
    cin >> n >> m;
    for(int i=0; i<m; i++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        E[i].u=a;
        E[i].v=b;
        E[i].cost=c;
    }
    cout << krusal(n,m);
}

最短路径的方法:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值