次小生成树

来源:互联网 发布:手机淘宝怎么小二介入 编辑:程序博客网 时间:2024/05/17 20:26

最小生成树与次小生成树

我们都知道要求最小生成树有两种方法可求,分别是prim算法和kruskal算法

1.prim算法

设图G =(V,E),其生成树的顶点集合为U。

  ①、把v0放入U。

  ②、在所有u∈U,v∈V-U的边(u,v)∈E中找一条最小权值的边,加入生成树。

  ③、把②找到的边的v加入U集合。如果U集合已有n个元素,则结束,否则继续执行②。

2.kruskal算法

首先将所有边按边权排序,然后按照边权从小到大依次处理.这里要用到并查集的思想,假设已有点集U,现在正在处理边i->j,如果i,j已在

U中则处理下一条边,否则将i,j加入并查集中.继续处理下一条边,直到有n-1条边为止.

3 次小生成树
次小生成树可由最小生成树换一条边得到

算法:
1)先用prim求出最小生成树T,在prim的同时,用一个矩阵max[u][v]记录在树中连接u-v的路径中权值最大的边.
2)枚举所有不在T中的边u-v,加入边u-v,删除权值为max[u][v]的边,不断枚举找到次小生成树.

下面给出次小生成树的模板

#include<iostream>#include<cstdio>#include<cstring>using namespace std;const int INF=0x3f3f3f3f;int g[110][110],dist[110],mmax[110][110];  ///g保存地图  dist保存从起点到其余各点的距离 maxn保存从i到j的最大边权值int pre[110]; ///pre保存j的离它最近的是哪个点bool mark[110]; ///相当于vis  用来标记该点是否已经用过bool connect[110][110]; ///保存i-j的那条边是否加入了最小生成树  false 加入 true  没有int mst,mint; ///mst保存最小生成树的权值和int n,m;int prim(){       int res=0,fa,p,min,i,j;       memset(mmax,0,sizeof(mmax));       for(i=1;i<=n;i++)       {              dist[i]=g[1][i];              pre[i]=1;              mark[i]=false;       }       dist[1]=0;       mark[1]=true;       for(i=1;i<n;i++)       {              p=-1;min=INF;              for(j=1;j<=n;j++)              {                     if(!mark[j]&&dist[j]<min)                     {                            p=j;                            min=dist[j];                     }              }              if(p==-1) return res;              mark[p]=true;              res+=dist[p];              fa=pre[p]; ///找到离p最近的点              connect[fa][p]=false;              connect[p][fa]=false;              mmax[fa][p]=min;              ///遍历所有的点 求其余点到p的最大权值              for(j=1;j<=n;j++)                     mmax[j][p]=(mmax[fa][p]>mmax[j][fa])?mmax[fa][p]:mmax[j][fa];              for(j=1;j<=n;j++)              {                     if(!mark[j]&&dist[j]>g[p][j])                     {                            dist[j]=g[p][j];                            pre[j]=p;                     }              }       }       return res;}int main(){       int tc;       scanf("%d",&tc);       while(tc--)       {              scanf("%d %d",&n,&m);              memset(g,INF,sizeof(g));              memset(connect,false,sizeof(connect));              while(m--)              {                     int u,v,c;                     scanf("%d %d %d",&u,&v,&c);                     g[u][v]=c;                     g[v][u]=c;                     connect[u][v]=true;                     connect[v][u]=true;              }              mst=prim();              int i,j;              bool flag=false;              for(i=1;i<=n;i++)                     for(j=1;j<=n;j++)                     {                            ///如果i-j这条边加入了最小生成树 或者i-j这条路不通  continue                            if(connect[i][j]==false||g[i][j]==INF)                                   continue;                            ///如果加入的边和删除的边的大小是一样的  说明次小生成树的权值和等于最小生成树的权值和                            ///也就是说最小生成树不唯一                            if(g[i][j]==mmax[i][j])                            {                                   flag=true;                                   break;                            }                     }              if(flag)                     printf("Not Unique!\n");              else                     printf("%d\n",mst);       }       return 0;}

下面来一道题练练手

http://acm.hdu.edu.cn/showproblem.php?pid=4081

题意:

题目大意:

有n个城市,秦始皇要修用n-1条路把它们连起来,要求从任一点出发,都可以到达其它的任意点。秦始皇希望这所有n-1条路长度之和最短。然后徐福突然有冒出来,说是他有魔法,可以不用人力、财力就变出其中任意一条路出来。

秦始皇希望徐福能把要修的n-1条路中最长的那条变出来,但是徐福希望能把要求的人力数量最多的那条变出来。对于每条路所需要的人力,是指这条路连接的两个城市的人数之和。

最终,秦始皇给出了一个公式,A/B,A是指要徐福用魔法变出的那条路所需人力, B是指除了徐福变出来的那条之外的所有n-2条路径长度之和,选使得A/B值最大的那条。

分析与总结

为了使的A/B值最大,首先是需要是B尽量要小,所以可先求出n个城市的最小生成树。然后,就是决定要选择那一条用徐福的魔法来变。

因此,可以枚举每一条边,假设最小生成树的值是MinMST, 而枚举的那条边长度是w[i][j],  如果这一条边已经是属于最小生成树上的,那么最终式子的值是A/(MinMST-w[i][j])。如果这一条不属于最小生成树上的, 那么添加上这条边,就会有n条边,那么就会使得有了一个环,为了使得它还是一个生成树,就要删掉环上的一棵树。 为了让生成树尽量少,那么就要删掉除了加入的那条边以外,权值最大的那条路径。 假设删除的那个边的权值是path[i][j], 那么就是A/(MinMST-path[i][j]).

解这题的关键也在于怎样求出次小生成树。


AC代码:

#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#include <cmath>#define INF 0x3f3f3f3f#define MAX 1010using namespace std;double Map[MAX][MAX], dis[MAX], maxn[MAX][MAX]; ///Map数组保存地图  dis保存从起点到其他点的距离 maxn保存i到j的最大权值int pre[MAX], vis[MAX];bool used[MAX][MAX];int n;struct Edge{    double x, y, p;}edge[MAX];double cal(Edge a, Edge b){    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y)*(a.y - b.y));}double prim(){    double ret = 0, minn;    int father;    memset(vis, 0, sizeof(vis));    memset(maxn, 0, sizeof(maxn));    memset(used, 0, sizeof(used));    for(int i = 1; i <= n; i++)    {        dis[i] = Map[1][i];        pre[i] = 1;    }    vis[1] = 1;    int k;    for(int i = 1; i < n; i++)    {        minn = INF;        for(int j = 1; j <= n; j++)        {            if(!vis[j]&&dis[j] < minn)            {                minn = dis[j];                k = j;            }        }        if(minn == INF) return -1;        vis[k] = 1;        ret+=minn;        father = pre[k];        used[father][k] = used[k][father] = 1;        maxn[father][k] = minn;        for(int j = 1; j <= n; j++)        {            if(vis[j]&&j!=k) ///不明白为什么j为什么不能等于k                  maxn[j][k] = maxn[k][j] = max(maxn[j][father], maxn[father][k]);        }        for(int j = 1; j <= n; j++)        {            if(!vis[j]&&dis[j] > Map[k][j])            {                dis[j] = Map[k][j];                pre[j] = k;            }        }    }    return ret;}int main(){    int t;    scanf("%d",&t);    while(t--)    {        scanf("%d",&n);        for(int i = 1; i <= n; i++)        {            for(int j = 1; j <= n; j++)            {                Map[i][j] = INF;            }            Map[i][i] = 0;        }        for(int i = 1; i <= n; i++)        {            scanf("%lf%lf%lf",&edge[i].x, &edge[i].y, &edge[i].p);        }        for(int i = 1; i <= n; i++)        {            for(int j = i + 1; j <= n; j++)            {                Map[i][j] = Map[j][i] = cal(edge[i], edge[j]);            }        }        double ans = prim();        double ans1 = -1;        for(int i = 1; i<= n; i++)        {            for(int j = 1; j <= n; j++)            {                if(i != j)                {                    if(used[i][j])                        ans1 = max(ans1, (edge[i].p + edge[j].p)/(ans - Map[i][j]));                    else                        ans1 = max(ans1, (edge[i].p + edge[j].p)/(ans - maxn[i][j]));                }            }        }        printf("%.2lf\n",ans1);    }    return 0;}

但是一直有一个疑问,就是不明白为什么遍历各个点到k的最大边权值的时候,j不能等于k,有明白的大神们,请留言啊。


0 0
原创粉丝点击