布线问题
时间限制:
1000 ms | 内存限制:
65535 KB
难度:
4
-
描述
-
南阳理工学院要进行用电线路改造,现在校长要求设计师设计出一种布线方式,该布线方式需要满足以下条件:
1、把所有的楼都供上电。
2、所用电线花费最少
-
输入
-
第一行是一个整数n表示有n组测试数据。(n<5)
每组测试数据的第一行是两个整数v,e.
v表示学校里楼的总个数(v<=500)
随后的e行里,每行有三个整数a,b,c表示a与b之间如果建铺设线路花费为c(c<=100)。(哪两栋楼间如果没有指明花费,则表示这两栋楼直接连通需要费用太大或者不可能连通)
随后的1行里,有v个整数,其中第i个数表示从第i号楼接线到外界供电设施所需要的费用。( 0<e<v*(v-1)/2 )
(楼的编号从1开始),由于安全问题,只能选择一个楼连接到外界供电设备。
数据保证至少存在一种方案满足要求。
输出
- 每组测试数据输出一个正整数,表示铺设满足校长要求的线路的最小花费。 样例输入
-
1 4 6 1 2 10 2 3 10 3 1 10 1 4 1 2 4 1 3 4 1 1 3 5 6
样例输出
-
4
//因为定点数比较少在,这个因为普里母算法的时间复杂度是O(n ^2),所以说整个算法大约是250000左右可以被运行的,因为这种算法的时间复杂度比较依赖于顶点数目也就是说,如果顶点较多那么请放弃普里母算法,可以进行克鲁斯卡尔算法
#include<iostream>//引入标准的输入输出函数
#include<string.h>//需要用到memset,memset()可以用在字符数组的初始化以及类似于memset(arr,0,n*sizeof(int));的情况,效率比手动循环赋值要高的多。
#define MAXNUM 0x3f3f3f3f//初始化最大值,这是因为memset是按字节进行填充的,每个字节填充0x3f恰好不会使一个int溢出
#define MAXN 505//定义最大的顶点数量
using namespace std;//引入标准命名空间
int n,e;//定义全局的顶点个数和边数
int graph[MAXN][MAXN];//因为顶点数目较少并且数组的读取是O(1)所以定义成邻接矩阵
int rmin=MAXNUM;//定义从外边接线最小的代价
int vis[MAXN];//定义访问标志数组
int sum;//定义最后的结果
void prim(int s)//调用普里母算法构建最小生成树,参数S表示从下标为S的点开始构建
{
int low[MAXN];//定义每个点到其他点的最小权值
for(int i=1; i<=n; i++)
{
low[i]=graph[s][i];//把每个点到其他点最小的权值点已成与s的距离,尽管可能不存在边,但是我们初始化为maxnum了
}
vis[s]=1;//把s点添加到已经构建的顶点集合中
for(int i=1; i<n; i++) //接下来的n-1次会找到n-1条最小的边,恰好是一颗生成树的边数
{
int tmin=MAXNUM;//每次定义一个最小值,并且初始化为MAXNUM
int tk=s;//并且标记最小权值的顶点下标
for(int j=1; j<=n; j++)
{
if(vis[j]==0&&low[j]<tmin)//如果该店没有被标记成已经构建好的顶点,并且比已知最小的还小
{
tmin=low[j];//更新最小值
tk=j;//记录该顶点下标
}
}
vis[tk]=1;//把tk点添加到已经构建的顶点集合中
sum+=tmin;//sum更新
//接下来遍历与tk点邻接的所有点
for(int j=1; j<=n; j++)
{
if(vis[j]==0&&low[j]>graph[tk][j])//如果tk下标的顶点与某个未被标记成已经构建点的直接边的距离小于以前已经构建的顶点与该点的直接边的权值
{
low[j]=graph[tk][j];//更新low数组
}
}
}
}
void init()//对于每个测试列初始化
{
memset(vis,0,sizeof(vis));//初始化标记数组为0
memset(graph,0x3f,sizeof(graph));//初始化图的所有点与其点的距离为MAX
sum=0;//初始化sum和是0
rmin=MAXNUM;//从外面接线的代价
}
int main()
{
int ncase;
cin>>ncase;//输入测试用例个数
while(ncase--)
{
cin>>n>>e;
init();//必要初始化
for(int i=0; i<e; i++)
{
int v,w,d;
cin>>v>>w>>d;
graph[v][w]=d;
graph[w][v]=d;
}//构建树
for(int i=0; i<n; i++)
{
int num;
cin>>num;
if(num<rmin)
{
rmin=num;//更新最小的从外接入代价
}
}
prim(1);//调用普里母算法
cout<<sum+rmin<<endl;//输出结果,即最小的生成树加外接最小代价
}
return 0;
}
如此说来我们就需要试一下克鲁斯卡尔算法了
首先分析下这个边是v*(v-1)/2但是因为是结构体所以应该要比prim开的要大些
时间复杂度完全取决于库函sort 对于n^2的结构体数组的排序复杂度
#include<iostream>
#include<algorithm>//导入算法头文件 需要库函数sort
#define MAXN 124755//边的最大数目
#define MAXNUM 0x3f3f3f3f//定义一个无穷大
typedef struct
{
int v;
int w;
int data;
}Edge;//定义结构体-边,即from v,to w和边的权值data
Edge bian[MAXN];//定义边的数组
int minr;//定义从外接入的最小代价
int pre[MAXN];//并查集的父节点数组
using namespace std;//导入标准命名空间
int n,e;
int sum;
int Find(int x)//压缩路径
{
int temp,p=x;
while(x!=pre[x])
x=pre[x];//先找到x所属集合的根节点
while(p!=x)//更新路径上的根节点全部压缩
{
temp=pre[p];//从x到根节点路径遍历
pre[p]=x;
p=temp;
}
return x;//返回该集合的根节点
}
void kruskal()//克鲁斯卡尔算法进行最小生成树的生成算法
{
for(int i=0;i<e;i++)//遍历按照权值从小到大排好序的边
{
if(Find(bian[i].v)!=Find(bian[i].w))//如果边上两个顶点或者说两个点所在的图不是一个,就是说所在的两个图是分离的不通的 ,这样就规避了形成环的问题
{
pre[pre[bian[i].w]]=pre[bian[i].v];//那么就把两个点加入一个点集合
sum+=bian[i].data;//更新sum
}
}
}
void init()//对于每个样例初始化
{
sum=0;//sum初始化为0
minr=MAXNUM;//minr初始化为无穷大
for(int i=1;i<=n;i++)//将父节点初始化为自己
pre[i]=i;
}
bool cmp(Edge a,Edge b)//sort函数的参数函数
{
return a.data<b.data;
}
int main()
{
int ncase;
cin>>ncase;//输入样例个数
while(ncase--)
{
cin>>n>>e;
init();//初始化
for(int i=0;i<e;i++)
{
int w,v,d;
cin>>v>>w>>d;
bian[i].v=v;
bian[i].w=w;
bian[i].data=d;
}//读入到边数组中
sort(bian,bian+e,cmp);//排好序
for(int i=0;i<n;i++)
{
int num;
cin>>num;
if(num<minr)
minr=num;
}//更新从外面传到内部电的最小代价
kruskal();//调用克鲁斯卡尔更新sum
cout<<minr+sum<<endl;//输出结果
}
}
我们可以看下对比
prim :
kruskal:
我们可以看出在点相对比较少的情况下普里母完胜克鲁斯卡尔
但是在边少点多的情况下最好用克鲁斯卡尔
并且在时间不够用的情况下最好用克鲁斯卡尔,应为算法写起来比较简单