中级篇——最小生成树

来源:互联网 发布:java官方下载地址 编辑:程序博客网 时间:2024/06/08 10:02

最小生成树可以解决一类最短路径问题,现在给出一幅图:

每条路上的数值代表权值。现在需要从1到6找一条最便捷的路径。

第一步:从1出发有1——2,1——3,两条边。1——2比1——3短,故先连通1——2。


第二步,接下来看1——3,2——3,2——4,三条边,1——3和2——3都能连通1,2,3三个点,但1——3明显小于2——3故连通2——3.


接下来,最短的边是4——6边权值为3,那就连上吧。再然后就是5——6边权值小,也连上!


从现在开始该思考了!剩下的边2——3最小,然而2,3已经连通,再连起来就不符合树的定义,同理4——5也是一样。只能在2——4,3——4,3——5中选择,此时3——4边权值最小,链接3——4。

最小生成树诞生!

回顾刚才的步骤,首先判断两个点是否连通,优先选择边权值小的点加入树中,重复步骤即可。最先就是将所给的边按照全值从小到大排列,那么怎么判断两个点是否连通?这就需要之前讲过的并查集算法,套模板即可。这种最小生成树的方法叫做最小生成树的Kruskal算法。

以本题为例,代码实现如下:

#include <iostream>using namespace std;struct edge{    int u,v,w;//记录起点终点边长}a[1005];int n,m;int f[1005],sum,cnt;//并查集要用到的东西void quicksort(int left,int right){    int i=left,j=right;    struct edge t;    if(left>right) return ;    while(i!=j)    {        //顺序很重要,先从右边开始        while(a[j].w>=a[left].w&&i<j)            j--;        //再从左边开始        while(a[i].w<=a[left].w&&i<j)            i++;        //交换        if(i<j)        {            t=a[i];a[i]=a[j];a[j]=t;        }    }    t=a[left];a[left]=a[i];a[i]=t;    quicksort(left,i-1);    quicksort(i+1,right);    return ;}//并查集开始int get(int v){    if(f[v]==v) return v;    else    {        f[v]=get(f[v]);        return f[v];    }}//合并祖宗节点int join(int v,int u){    int t1=get(v),t2=get(u);    if(t1!=t2)//如两个的祖宗不同则合并    {        f[t2]=t1;return 1;    }    return 0;}int main(){    while(cin>>n>>m)    {        cnt=0;sum=0;        for(int i=1;i<=m;i++)        {            cin>>a[i].u>>a[i].v>>a[i].w;        }        quicksort(1,m);//按权值排序        //并查集初始化        for(int i=1;i<=n;i++)            f[i]=i;        //Kruskal算法核心        for(int i=1;i<=m;i++)        {            if(join(a[i].u,a[i].v))//判断是否已经连通            {                cnt++;sum+=a[i].w;            }            if(cnt==n-1) break;//n个点链接只需n-1条边        }        cout<<sum<<endl;    }    return 0;}

上面介绍的是kruskal最小生成树法,主要步骤就是将边按照边权值大小排序并查看点与点是否在一条路径上。而下面这种算法是prim最小生成树法,其与最短路径时的Dijkstra算法相似(真TM极其相似),而且代码长度短于kruskal算法。

算法步骤:

一:从一点构造最小生成树,假设从1点开始。将1加入树中,用一个一维数组vis[]来标记每个点是否在树中。

二:用dis[]数组记录生成树到各顶点的距离。初始化时,与1有直接连边的赋值为其边权值,无连边的设为无穷大。

三:从树中选出离起点最近的点加入到生成树中,按照Dijkstra算法松弛各边。

四:重复第三步,直至树中有n个点为止。

代码实现:

#include <iostream>#define max 9999999using namespace std;int n,m,dis[1005],vis[1005],e[1005][1005];int u,v,w,cnt,sum;int main(){    while(cin>>n>>m)    {        cnt=0;sum=0;        for(int i=1;i<=n;i++)        {            vis[i]=0;            for(int j=1;j<=n;j++)            {                if(i==j) e[i][j]=0;                else e[i][j]=max;            }        }        while(m--)        {            cin>>u>>v>>w;            e[u][v]=e[v][u]=w;        }        for(int i=1;i<=n;i++)            dis[i]=e[1][i];        //核心代码,将起点加入树中,记得cnt++        vis[1]=1;cnt++;        while(cnt<n)        {            int min=max;            for(int i=1;i<=n;i++)            {                if(vis[i]==0&&dis[i]<min) {min=dis[i];u=i;}            }            vis[u]=1;cnt++;sum+=dis[u];            for(v=1;v<=n;v++)            {                if(vis[v]==0&&dis[v]>e[u][v])                    dis[v]=e[u][v];            }        }        cout<<sum<<endl;    }    return 0;}

这种方法的运算时间是O(N^2),已经还可以了,但如果利用堆进行优化时间可降为O(MlogN)。对的优化之后会讲!


hdu1102

There are N villages, which are numbered from 1 to N, and you should build some roads such that every two villages can connect to each other. We say two village A and B are connected, if and only if there is a road between A and B, or there exists a village C such that there is a road between A and C, and C and B are connected.

We know that there are already some roads between some villages and your job is the build some roads such that all the villages are connect and the length of all the roads built is minimum.
 

Input
The first line is an integer N (3 <= N <= 100), which is the number of villages. Then come N lines, the i-th of which contains N integers, and the j-th of these N integers is the distance (the distance should be an integer within [1, 1000]) between village i and village j.

Then there is an integer Q (0 <= Q <= N * (N + 1) / 2). Then come Q lines, each line contains two integers a and b (1 <= a < b <= N), which means the road between village a and village b has been built.
 

Output
You should output a line contains an integer, which is the length of all the roads to be built such that all the villages are connected, and this value is minimum.
 

Sample Input
30 990 692990 0 179692 179 011 2
 

Sample Output
179
题意和讲算法时的例题差不多,就是给出每边的权值的方式不太一样,好好读题就行,注意未连通的点不一定只有一个,所以输出时要输出所有未连通点的距离和(我在这里错了)。我的做法是先输入每边的权值,再将m对连通的点的距离记为0,在进行最小生成树的计算。

#include <iostream>#define max 9999999using namespace std;int n,m,dis[1005],vis[1005],e[1005][1005];int u,v,w,cnt,sum;int main(){    while(cin>>n)    {        cnt=0;sum=0;        for(int i=1;i<=n;i++)        {            vis[i]=0;            for(int j=1;j<=n;j++)            {                if(i==j) e[i][j]=0;                else e[i][j]=max;            }        }        for(int i=1;i<=n;i++)        for(int j=1;j<=n;j++)        {            cin>>w;            if(w<e[i][j]) e[i][j]=e[j][i]=w;        }        cin>>m;        while(m--)        {            cin>>u>>v;            e[u][v]=e[v][u]=0;        }        for(int i=1;i<=n;i++)            dis[i]=e[1][i];        //核心代码,将起点加入树中,记得cnt++        vis[1]=1;cnt++;        while(cnt<n)        {            int min=max;            for(int i=1;i<=n;i++)            {                if(vis[i]==0&&dis[i]<min) {min=dis[i];u=i;}            }            vis[u]=1;cnt++;sum+=dis[u];            for(v=1;v<=n;v++)            {                if(vis[v]==0&&dis[v]>e[u][v])                    dis[v]=e[u][v];            }        }        cout<<sum<<endl;    }    return 0;}








0 0
原创粉丝点击