图---->最小生成树 ( minimum cost spanning tree )

使用不同的遍历图的方法,可以得到不同的生成树;从不同的顶点出发,也可能得到不同的生成树。
按照生成树的定义, n 个顶点的连通网络的生成树有 n 个顶点、 n - 1 条边。
构造最小生成树    假设有一个网络,用以表示 n 个城市之间架设通信线路,边上的权值代表架设通信线路的成本。如何架设才能使线路架设的成本达到最小?

 

经过算法优化后得到这个树 

 

 所以,这种问题的解决方法通俗的有两种:

普里姆(prim)算法

克鲁斯卡尔(Kruskal)算法

我们首先来入手Kruskal算法

Kruskal

Kruskal算法的基本思想:从边入手找顶点(贪心法)

步骤:

1.先构造一个只含 n 个顶点的子图 SG;

2.然后从权值最小的边开始,若它的添加不使SG 中产生回路,则在 SG 上加上这条边

3.反复执行第2步,直至加上 n-1 条边为止。

(下图中浅色的就是舍弃掉的边,根据上面的描述很快能理解这个图) 

 

 下面的算法描述中,适用并查集来做 

/*
构造非连通图 ST=( V,{ } );
k = i = 0; // k 计选中的边数
while (k<n-1)
{
    ++i;
    检查边集 E 中第 i 条权值最小的边(u,v);
    若(u,v)加入ST后不使ST中产生回路,则 输出边(u,v); 且 k++;
}
*/

 还没学到并查集。。等学到了再补充(听说要用到路径压缩?!)

先放大佬博客最小生成树(kruskal算法)_Superb_day-CSDN博客


 更新了,并查集由某位大佬指点其实并不难

洛谷也有这一道题,很棒的思路,怕自己写的代码不自信可以oj一下

P3366 【模板】最小生成树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

/*
算法思想是:如果顶点x  x->f[x]->f[f[x]]==z;    y->f[y]->f[f[x]]->f[f[f[y]]]==z; 若
若存在顶点z=f[z],即说明x和y在同一个集合当中!
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
int n,m,i,j,u,v,total;
struct edge{
    int from;    //边的起点
    int to;                //终点
    int val;    //边的权值
}bian[2000005];    //默认边的存放数量最大

int f[100000];    //并查集数组

long long ans;    //最小生成树的各边的长度之和

int find(int x)    //并查集部分,find()函数主要找x顶点的
{
    if (f[x]==x) 
            return x; 
    else 
    {
        f[x]=find(f[x]);        //一直往复查找,递归查找
        return f[x];
    }	
}

bool cmp(edge a,edge b)    //结构体快排时用到的,STL的sort()函数
{
    return a.val<b.val;
}

void kruskal(edge bian[],int m,int n)//最小生成树
{
    
    int total=0;
    for(int i=1;i<=m;i++)
    {
        u=find(bian[i].from);
        v=find(bian[i].to);       //找到
        if(u==v) 
            continue;//判断在不在同一个并查集里面,在就下一个循环

        ans+=bian[i].val;//不在,就加上,或者是输出

        f[u]=v;        //将u,v两点链接的并查集都加入到同一个并查集中!

        total++;    //成功遍历了图的一个顶点,total加一

        if(total==n-1) 
             break;//当形成了最小生成树后,退出(之后做的也没用了)
    }
} 
int main()
{
    scanf("%d%d",&n,&m);    //输入点和边的个数
    for(i=1;i<=n;i++) 
                f[i]=i;  //分成了n个集合,每个顶点相当于枢纽,区分出find()操作结束的终点!
    for(i=1;i<=m;i++)
    {
        cin>>bian[i].from>>bian[i].to>>bian[i].val;
    }
    sort(bian+1,bian+m+1,cmp);//cmp为规则,stl快排
    kruskal();
    printf("%d",ans);
    return 0;
}

上面的代码不理解?下面有推导过程,画的有点丑,但是很容易get到这个算法的点

路径压缩

在上面find操作上修改一下即可,在下一次操作更快地查找一个集合内的元素,更快找

//这个是查找,顺带把路径压缩了
int find(int x)
{
	int temp=x;
	while(x!=f[x])
		x=f[x];
	f[temp]=x;
	return x;
}

Prim

普里姆算法的基本思想: 从顶点入手找边 [贪心法 ]

实施步骤:

1.分组,出发点为第一组,其余结点为第二组。

2.在一端属于第一组和另一端属于第二组的边中选择一条权值最小的一条

3.把原属于第二组的结点放入第一组中。

4.反复2,3两步,直到第二组为空为止

 图示手工

这两张图联合在一起看,很好理解的 

 

 

 实现代码

#include<iostream.h>
#define M 20
int G[M][M];
int n;       //图所有结点
void Prim()
{ 
    int temp[M]; //存放已经加入的结点
    int size;     //已加入的结点个数
    int i,j,k; 
    int curnode,pos1,pos2;
    int min;
    temp[0]=0; 
    size=1;
    G[0][0]=1;
    for( i=0;i<n-1;i++)
    {
        min=32767; // 极大值
        for ( j=0;j<size;j++)
        {
            curnode=temp[j];
            for( k=0;k<n;k++)
                if ( G[curnode][k]<min && G[k][k]==0){ 
                    min=G[curnode][k];
                    pos1=curnode;
                    pos2=k; 
                 }
        }
     cout<<"edge "<<size<<" ( "<<pos1<<" -----> "<<pos2<<" ):" <<G[pos1][pos2]<<endl;
     G[pos2][pos2]=1;
     temp[size]=pos2; 
     size++;
    }
}

void Input()
{
    cin>>n;
    for(i=0;i<n;i++)
        for (j=0;j<n;j++)
            cin>>G[i][j];
}

void main()
{ 
    cout<<"please input the data of graph:"<<endl;
    Input(); 
    Prim();
}

上面的有点难懂?

其实

Prim算法从一个给定起点开始一步一步生长成最小生成树:在算法的每一次迭代中,往生成树中增加一个顶点。总是加入所有“从树外到树中”的边中权值最小的一条 。只关心把节点从树外移到树内的最后一条边的权值。

与dijkstra非常像
(5条消息) 图的最短路径(dijkstra算法)_inbSorryMaker的博客-CSDN博客https://blog.csdn.net/weixin_51578598/article/details/121134676

dijkstra算法从一个给定起点s开始一步一步生长成最短路树(包含所有已经算出最短路的节点,在这颗树上,从s到每个节点的唯一简单路径就是原图中从s到这些点的最短路):在算法的每一次迭代中,往最短路树中增加一个顶点。总是加入所有“s到树外最短路径”的权值最小的那个节点 。关心的是从出发到达各个树外节点的路径总长。这个跟最后加入的边权值相关,也与从起点到新增点前一个点的路径长度有关。

除了这个迭代公式计算和代表意义不一样,prim跟dijkstra其他均一样,算法实现基本一样。  只是松弛策略不一样。

dijistra 

void Dijkstra(typec cost[][MAXN],typec lowcost[],int n,int beg) { 
    for(int i=0;i<n;i++)  {    lowcost[i]=INF;vis[i]=false;pre[i]=-1;     } 
    lowcost[beg]=0; 
    for(int j=0;j<n;j++)  { 
        int k=-1; 
        long long  Min=INF; //改下模板的int类型 以匹配最大long long
        for(int i=0;i<n;i++) //找出最小的未访问的最小顶点k
            if(!vis[i]&&lowcost[i]<Min) 
                {  Min=lowcost[i];  k=i;   } 
        if(k==-1)break; 
        
        vis[k]=true; 
        for(int i=0;i<n;i++) 
       	    if(!vis[i]&&lowcost[k]+cost[k][i]<lowcost[i]) //松弛
        	{ 
           	 lowcost[i]=lowcost[k]+cost[k][i];
             pre[i]=k; 
        	} 
    } 
} 

prim

void Prim(typec cost[][MAXN],typec lowcost[],int n,int beg) { //名字改prim
    for(int i=0;i<n;i++)  
    {    
    lowcost[i]=INF;
    vis[i]=false;
    pre[i]=-1;     
    } 
    lowcost[beg]=0; 
    for(int j=0;j<n;j++)  { 
        int k=-1; 
        long long  Min=INF; //改下模板的int类型 以匹配最大long long
        for(int i=0;i<n;i++) //找出最小的未访问的lowcost[i]值最小顶点k
            if(!vis[i]&&lowcost[i]<Min) 
                    {  Min=lowcost[i];  k=i;   } 
        if(k==-1)break; 
        vis[k]=true; 
        for(int i=0;i<n;i++) 
       	 if(!vis[i]&&cost[k][i]<lowcost[i]) {
                        //松弛策略改为i到原来的S的最小值大于i到新加入的点边权值
           	 lowcost[i]=cost[k][i];       
             pre[i]=k; 
        	} 
    } 
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值