最小生成树
n使用不同的遍历图的方法,可以得到不同的生成树;从不同的顶点出发,也可能得到不同的生成树。
n按照生成树的定义,n个顶点的连通网络的生成树有 n个顶点、n-1条边。
n构造最小生成树的准则
n必须使用且仅使用该网络中的n-1条边来联结网络中的 n个顶点;
n不能使用产生回路的边;
n各边上的权值的总和达到最小。
prim算法
普里姆算法的基本思想:
从连通网络 N = { V, E }中的某一顶点 u0 出发,选择与它关联的具有最小权值的边( u0, v ),将其顶点加入到生成树顶点集合U中。
以后每一步从一个顶点在 U中,而另一个顶点不在 U 中的各条边中选择权值最小的边(u, v),把它的顶点加入到集合 U中。如此继续下去,直到网络中的所有顶点都加入到生成树顶点集合 U中为止。
采用邻接矩阵作为图的存储表示
分析以上算法,设连通网络有 n个顶点, 则该算法的时间复杂度为 O(n2), 它适用于边稠密的网络。
注意:当各边有相同权值时,由于选择的随意性,产生的生成树可能不唯一。当各边的权值不相同时,产生的生成树是唯一的。
下面是杭电的一道例题
hdoj1875
畅通工程再续
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 20017 Accepted Submission(s): 6281
Problem Description
相信大家都听说一个“百岛湖”的地方吧,百岛湖的居民生活在不同的小岛中,当他们想去其他的小岛时都要通过划小船来实现。现在政府决定大力发展百岛湖,发展首先要解决的问题当然是交通问题,政府决定实现百岛湖的全畅通!经过考察小组RPRush对百岛湖的情况充分了解后,决定在符合条件的小岛间建上桥,所谓符合条件,就是2个小岛之间的距离不能小于10米,也不能大于1000米。当然,为了节省资金,只要求实现任意2个小岛之间有路通即可。其中桥的价格为 100元/米。
Input
输入包括多组数据。输入首先包括一个整数T(T <= 200),代表有T组数据。
每组数据首先是一个整数C(C <= 100),代表小岛的个数,接下来是C组坐标,代表每个小岛的坐标,这些坐标都是 0 <= x, y <= 1000的整数。
每组数据首先是一个整数C(C <= 100),代表小岛的个数,接下来是C组坐标,代表每个小岛的坐标,这些坐标都是 0 <= x, y <= 1000的整数。
Output
每组输入数据输出一行,代表建桥的最小花费,结果保留一位小数。如果无法实现工程以达到全部畅通,输出”oh!”.
Sample Input
2 2 10 10 20 20 3 1 1 2 2 1000 1000
Sample Output
1414.2 oh!
克鲁斯卡尔算法(已ac)
<span style="font-size:14px;">#include<cstdio>
#include<algorithm>
#include<math.h>
using namespace std;
int per[103];
void init()
{
for(int i=0;i<=102;i++) //由于上边定义的是103数组,此时只能访问0~102;不能用103这个位置,否则越界
per[i]=i;
}
struct node
{
int x;
int y;
}dao[103];
struct node2
{
int a;
int b;
double changdu;
}p[103*103];
double cmp(node2 a,node2 b)
{
return a.changdu<b.changdu;
}
int find(int x)
{
if(x==per[x])
return x;
else
return per[x]=find(per[x]);
}
int join(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)
{
per[fy]=fx;
return 1;
}
else
return 0;
}
int main()
{
int n,m,i,j;
scanf("%d",&n);
while(n--)
{
int x=0;
scanf("%d",&m);
init(); //初始化,容易丢
for(i=0;i<m;i++)
scanf("%d%d",&dao[i].x,&dao[i].y); //这也可以定义成两个 数组来存储
for(i=0;i<m;i++)
{
for(j=i+1;j<m;j++)
{
double f=(double)(dao[j].x-dao[i].x)*(dao[j].x-dao[i].x)+(double)(dao[j].y-dao[i].y)*(dao[j].y-dao[i].y);
p[x].changdu=sqrt(f); //注意前一句的强制转化,也可以不写double,写成1.0*后边的等式
p[x].a=i;
p[x].b=j;
x++;
}
}
sort(p,p+x,cmp);
double sum=0;
int cnt=1;
for(i=0;i<x;i++)
{
if(join(p[i].a,p[i].b)&&p[i].changdu>=10&&p[i].changdu<=1000)
{
sum=sum+p[i].changdu;
cnt++;
}
}
if(cnt>=m)
printf("%.1lf\n",sum*100); //double输出用lf
else
printf("oh!\n") ;
}
return 0;
}</span>
1、建立编号。(找到两个点之间的联系,然后编号成第几组)
2、按照最小代价排序(一般为结构体排序)
3、利用并查集,边连接,边判断,排除成环的情况,并记录连接点的个数。
4、判段连接点个数是否等于所需连接物体数量。
prim算法(ac)
#include<stdio.h>
#include<string.h>
#include<math.h>
#define max 1000
#define INF 0xfffffff
int n;
int x[110],y[110];
double g[110][110];
double dis(int a,int b)
{
return sqrt(1.0*(x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}
void prim()
{
double dis[110],min,sum;
int vis[110];
int v,i,j,k;
memset(vis,0,sizeof(vis)); //此数组为1时,代表进入集合,否则没进入集合
for(i=1;i<=n;i++)
dis[i]=g[1][i]; //记录i点到集合1的距离
vis[1]=1; //让第一个点进入集合
for(v=1;v<n;v++)
{
min=INF;
// k=1; //可以不初始化,因为每次k都是集合外的点距离集合 内点的最近距离坐标,自动更新
for(i=1;i<=n;i++)
if(!vis[i]&&dis[i]<min)
{
min=dis[i]; //把距离1的最近距离赋给了min
k=i; //把点的坐标赋给了k
}
if(min==INF) //说明外面的点要想进来,距离都不符合题意
{
printf("oh!\n");
return ;
}
vis[k]=1;
for(i=1;i<=n;i++)
if(!vis[i]&&dis[i]>g[k][i])
dis[i]=g[k][i]; //注意dis记录的是集合内部距离集合外每一个点的最小距离,不是某个点
}
sum=0;
for(i=2;i<=n;i++)
sum+=dis[i]*100.0;
printf("%.1lf\n",sum) ;
return ;
}
int main()
{
int i,j,m;
scanf("%d",&m);
while(m--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d%d",&x[i],&y[i]);
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
g[i][j]=dis(i,j); //记录i点到j点的距离
if(g[i][j]<10||g[i][j]>1000)
g[i][j]=INF; //把不符合题意的点直接赋值无穷大,相当于做标记,便于输出
}
prim();
}
return 0;
}