最小生成树总结

一.最小生成树问题

给定一张图,图中有许多的节点还有许多长度不同的边将这些点点相互连接,找出连接所有点的最短方式就是最小生成树,可以证明,这样一种最小的情况是不会出现环的,由于所有的无环图都可以看做树,所以成为最小生成树。

二.kruskal算法

顶层思想是分治,选择策略是贪心,实现方法如下:以边为中心,先将所有的边从小到大进行排序,之后依照大小顺序依次选择,如果这条边连接的两个节点是不连通的(判断联通状态可以使用并查集),那么久选择这条边,否则就不选择,因为这个算法是以边为中心的所以用来计算稀疏图更快一些

问题描述

给定一个有权值的图,找出联通图内所有节点的最小路径。

数据

6 9

2 4 11

3 5 13

4 6 3

5 6 4

2 3 6

4 5 7

1 2 1

3 4 9

1 3

 

代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int f[50];
struct line
{
    int u;
    int v;
    int w;
}lines[50];
int cmp(const void *a,const void *b)
{
    return (*(line *)a).w>(*(line *)b).w?1:-1;
}
void init(int n)
{
    for(int i=1;i<=n;i++) f[i]=i;
}
int getf(int a)
{
    if(f[a]==a) return a;
    else
    {
        f[a]=getf(f[a]);
        return f[a];
    }
}
int  merge(int a,int b)
{
    int t1,t2;
    t1=getf(a);
    t2=getf(b);
    if(t1!=t2)
    {
        f[t2]=t1;
        return 1;
    }
    else return 0;
}
int main(int argc, const char * argv[]) {
    freopen("/Users/zhangjiatao/Desktop/input.txt","r",stdin);
    int n,m,sum;
    sum=0;
    cin>>n>>m;
    init(n);
    for(int i=0;i<=m-1;i++)
    {
        cin>>lines[i].u>>lines[i].v>>lines[i].w;
    }
    qsort(lines,m,sizeof(lines[0]),cmp);
    for(int i=0;i<=m-1;i++) cout<<lines[i].w<<" ";
    cout<<endl;
    for(int i=0;i<=m-1;i++)
    {
        if(merge(lines[i].u,lines[i].v)==1)
        {
            sum+=lines[i].w;
            cout<<i+1<<" ";
        }
    }
    cout<<endl;
    cout<<sum<<endl;
    return 0;
}


 

二.prime算法

一.算法分析
 
首先描述一下prim算法的步骤,首先将所有的点分为两类,第一类是在生成树中的节点,第二类是不在生成树中的节点,我们首先任取一个节点放入生成树中,然后找到距离这棵树(当前状态就是这个点)最近的点,然后将其放入到生成树中,之后再进行寻找距离这棵树最近的点,直到将所有的节点都加入到生成树中。
 
我们发现其实主要的思想方法就是贪心法,那么这个贪心法到底正不正确呢(贪心法好像老是叫人不放心啊)?让我们来证明一下,我们利用反证法,假设我们不选择距离这棵树最近的点(权值最小的边),然后生成了一颗完整的树,我们假设这是最小生成树,之后我们将刚才那个权值最小的边加入这棵树中,因为这是一颗生成树,所以加入了一条边以后一定会产生一个环,那么在这个环中,任意去掉一个权值比这条边大的边,可以发现这棵树还是一个完整的生成树,但是却比原来小,和假设矛盾,所以我们的贪心策略的到证明。
 还要注意,由于这个算法是以点为中心的,所以我们相比kruskal来说更加适合于稠密图。
二.优化分析
 
这里我们有两个地方可以进行优化,首先是图的储存和边的查找,如果使用邻接表的话会比使用邻接矩阵快,其次,找到权值最小的边如果使用堆或者优先队列的话要比普通遍历快。
 
三.代码实现
代码实现的过程要注意,由于要使用邻接表来保存图,所以读入边的时候,要注意要将这条边在该边的两个端点处都保存一次。
 
//
//  main.cpp
//  prim
//
//  Created by 张嘉韬 on 16/3/19.
//  Copyright ? 2016年 张嘉韬. All rights reserved.
//

#include <iostream>
#include <cstring>
using namespace std;
int u[100],v[100],w[100],frist[100],nex[100],n,m,dis[100],book[100],sum;
int const maxn=99999999;
void change(int k)//use k point to change
{
    book[k]=1;
    int temp;
    temp=frist[k];
    while(temp!=-1)//search all the line which begin with k
    {
        if(book[v[temp]]==0&&w[temp]<dis[v[temp]])
            dis[v[temp]]=w[temp];
        temp=nex[temp];
    }
}
int getm()
{
    int minnum=maxn,min=-1;
    for(int i=1;i<=n;i++)
    {
        if(dis[i]<minnum&&book[i]==0) minnum=dis[i],min=i;
    }
    sum+=minnum;
    return min;
}
int main(int argc, const char * argv[]) {
    freopen("/Users/zhangjiatao/Desktop/input.txt","r",stdin);
    cin>>n>>m;
    sum=0;
    memset(book,0,sizeof(book));
    memset(nex,-1,sizeof(nex));
    for(int i=1;i<=n;i++) frist[i]=-1;
    for(int i=1;i<=m;i++)
    {
        cin>>u[i]>>v[i]>>w[i];
        v[i+m]=u[i],u[i+m]=v[i],w[i+m]=w[i];
    }
    for(int i=1;i<=2*m;i++)
    {
        nex[i]=frist[u[i]];
        frist[u[i]]=i;
    }
    for(int i=1;i<=n;i++) dis[i]=maxn;
    change(1);
    for(int i=2;i<=n;i++)
    {
        int min;
        min=getm();
        change(min);
    }
    cout<<sum<<endl;
    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值