问题引入:
假设要在n个城市之间建立通信网络,则连通n个城市至少需要n-1条线路。n个城市之间,最多可能设置n*(n-1)/2条线路。这时,自然会考虑一个问题:如何在这些可能的线路中选择n-1条,使得在最节省费用的前提下建立该网络通信?
解决方法:
1.prim算法
算法思想:
G=(V,E)是无向连通带权图,V=(1,2,3,…,n);设最小生成树T=(U,TE),算法结束时U=V,TE属于E。构造最小生成树T的prim算法思想是:首先令U={u0},u0属于V,TE={}。然后,只要U是V的真子集,就做如下贪心选择:选取满足条件i属于U,j属于V-U,且边(i,j)是连接U和V-U的所有边中的最短边,即该边的权值最小。然后,将顶点j加入集合U,边(i,j)加入结合TE。继续上面的贪心选择一直到U=V为止,此时选取到的所有的边恰好构成G的一棵最小生成树T。(看成是一个集合到另外一个集合,逐次从中选出一个路线)
算法设计
- 将数据存放在数组,如果两个点不通,则设置为无穷大(0x33f3f3f)
- 初始化数组lowcost[],假设从1号点(下标1,lowcost[0]=0)开始,将与1相连的边存放到数组lowcost[]中
- 寻找到与1相连的最小的边,记录其在lowcost数组中的小标和权值,
- 将最小权值加入total,更新lowcost数组
nyoj题:http://acm.nyist.net/JudgeOnline/problem.php?pid=38
代码:
#include<cstdio>
#include<cstring>
using namespace std;
int lou[506][506],v,e,total;
const int inf=0x3f3f3f3f;
void prim()
{
int lowcost[506],flag,minn;//用于记录每次加入一个点后连通其他点的最小权值,
lowcost[1]=0;
for(int i=2;i<=v;i++)//假设加入了点1,然后给lowcost赋值与1连接的边权值
{
lowcost[i]=lou[1][i];
}
for(int i=1;i<v;i++)//只需要寻找n-1条边
{
flag=0,minn=inf;
for(int j=1;j<=v;j++)//找出当前的点集合中最小的边
{
if(lowcost[j]&&lowcost[j]<minn)
{
minn=lowcost[j];//记录最小的权值
flag=j;//标记位置
}
}
if(flag!=0)
total+=minn;
lowcost[flag]=0;
for(int i=2;i<=v;i++) //更新lowcost[]数组
{
if(lowcost[i]&&lowcost[i]>lou[flag][i])
{
lowcost[i]=lou[flag][i];
}
}
}
}
int main()
{
int n,out[506],a,b,c;
scanf("%d",&n);
while(n--)
{
memset(out,0,sizeof(out));
memset(lou,inf,sizeof(lou));//开始赋初值为整型无穷大量
scanf("%d%d",&v,&e);
total=0;
for(int i=0;i<e;i++)
{
scanf("%d%d%d",&a,&b,&c);
lou[a][b]=c;
lou[b][a]=c;
}
prim();
int sm=inf;
for(int i=0;i<v;i++)
{
scanf("%d",&out[i]);
if(sm>out[i])sm=out[i];
}
printf("%d\n",total+sm);
}
return 0;
}
2.kruskal算法
算法思想:
kruskal算法将n个点看成是n个孤立的连通分支。它首将所有的边按权从小到大排序。然后,只要T中的连通分支数目不为1,就做如下贪心选择:在边集E中选取权值最小的边(i,j),如果将边(i,j)加入集合TE中不产生回路(环),则将边(i,j)加入边集TE中,即用边(i,j)将这两个连通分支合并连接成一个连通分支,否则继续选择下一条最短边。在这两种情况下,把边(i,j)从集合中删除。继续上面的贪心选择知道T中的所有顶点都在同一个连通分支上为止。此时,选取到的n-1条边恰好构成G的一棵最下生成树T。
算法设计:
- 初始化,将图G的边集E中的所有边按权从大到小排序,边集TE={},把每个顶点都初始化为一个孤立的分支,即一个顶点对应的集合。
- 在E中寻找权值最小的边(i,j)。
- 如果顶点i和j位于两个不同的连通分支,则将边(i,j)加入边集TE,并执行合并操作将两个连通分支进行合并。
- 将边(i,j)从集合E中删去,即E=E-{(i,j)}
- 如果连通分支数目不为1,转步骤2;否则算法结束,生成最小生成树T。
代码:
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int fa[2000];
struct building
{
int a,b,cost;
}build[130000];
bool cmp(building x,building y)//让点之间的距离按照花费从小到大排序
{
return x.cost<y.cost;
}
int main()
{
int t,v,e;
scanf("%d",&t);
while(t--)
{
int wei[506],weight=0;
memset(build,0,sizeof(build));
memset(fa,0,sizeof(fa));
scanf("%d%d",&v,&e);
for(int i=0;i<e;i++)
{
scanf("%d%d%d",&build[i].a,&build[i].b,&build[i].cost);
}
sort(build,build+e,cmp);//用sort排序
// for(int j=0;j<e;j++)
// {
// printf("%d %d\n",build[j].a,build[j].cost);
// }
for(int i=0;i<v;i++)
{
scanf("%d",&wei[i]);
fa[i]=i;
}
sort(wei,wei+v);
for(int k=0;k<e;k++)
{
int x=fa[build[k].a-1];//此处也不是很明白
int y=fa[build[k].b-1];
if(x!=y)//当前最小边,如果不为环,那么加入该集合
{
weight+=build[k].cost;
//printf("====weight=%d\n",weight);
for(int i=0;i<v;i++)
{
if(fa[i]==y)//把可以相连的点并入一个集合,通过设置它们的fa[]值相同实现
{
fa[i]=x;
}
}
}
}
printf("%d\n",wei[0]+weight);
}
return 0;
}
如果G中的边比较少,可以采用kruskal,如果边数较多,则适用prim算法。时间上,kruskal时间复杂度为O(eloge),prim算法时间复杂度为O(n^2),prim适用于稠密图,kruskal适用于稀疏图。
另外kruskal算法另外一种通过并查集的方式写
并查集:
我们可以把每个连通分量看成一个集合,该集合包含了连通分量的所有点。而具体的连通方式无关紧要,好比集合中的元素没有先后顺序之分,只有“属于”与“不属于”的区别。图的所有连通分量可以用若干个不相交集合来表示。
而并查集的精妙之处在于用数来表示集合。如果把x的父结点保存在p[x]中(如果没有父亲,p[x]=x),则不难写出结点x所在树的递归程序:
find(int x) {return p[x]==x?x:p[x]=find(p[x]);}
意思是,如果p[x]=x,说明x本身就是树根,因此返回x;否则返回x的父亲p[x]所在树的根结点。
既然每棵树表示的只是一个集合,因此树的形态是无关紧要的,并不需要在“查找”操作之后保持树的形态不变,只要顺便把遍历过的结点都改成树根的儿子,下次查找就会快很多了
代码:
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int fa[2000];
struct building
{
int a,b,cost;
}build[130000];
bool cmp(building x,building y)
{
return x.cost<y.cost;
}
int find(int x)
{
//printf("-->%d\n",x);
if(x!=fa[x-1])fa[x-1]=find(fa[x-1]);//并查集
return fa[x-1];
}
int main()
{
int t,v,e;
scanf("%d",&t);
while(t--)
{
int wei[506],weight=0;
memset(build,0,sizeof(build));
memset(fa,0,sizeof(fa));
scanf("%d%d",&v,&e);
for(int i=0;i<e;i++)
{
scanf("%d%d%d",&build[i].a,&build[i].b,&build[i].cost);
}
sort(build,build+e,cmp);
// for(int j=0;j<e;j++)
// {
// printf("%d %d\n",build[j].a,build[j].cost);
// }
for(int i=0;i<v;i++)
{
scanf("%d",&wei[i]);
fa[i]=i+1;
}
sort(wei,wei+v);
for(int k=0;k<e;k++)
{
int x=find(build[k].a);
int y=find(build[k].b);
//printf("%d %d\n",x,y);
if(x!=y)
{
weight+=build[k].cost;
fa[x-1]=y;
// printf("%d\n",weight);
}
}
printf("%d\n",wei[0]+weight);
}
return 0;
}