什么是树?
树:由边和点组成,n个点,以n-1条边使任意两个点连通。(无回路的连通图)
什么是最小生成树?
给定一个有回路的连通无向图,有n个点,m条边(m>n),每条边都有一个花费值,在此有回路的连通无向图中,找到一个花销最小的无回路的连通图。
求解最小生成树的方法
一、Kruskal算法(选边)
Kruskal算法:
首先按照边的权值进行升序排序,每次从剩余的边中选择权值最小且两点不连通的边加入到生成树中,直到加入n-1条边为止。
如何判断两点是否连通呢?
判断两个点是否连通,只需判断两个点是否在同一个集合,如果在同一个集合,则两点是连通的,否则不是。
#使用并查集(在并查集中,同一集合的点,最终老大是一样的)
实现步骤:
一个含有n个点的树,有n-1条边。由此,我们想到选择n-1条边使n个点连通。
对于给定的有回路的连通无向图:
- 把它所有的边按花费的升序排序,(边是由两个点表示的,如1 2,代表从1到2的那条边)
- 判断第i条边的两个点是否已经连通
- 如果连通,则不加入生成树
- 如果没有连通,则加入生成树
- 直至找到n-1条边为止
kruskal代码实现:
时间复杂度:O(MlogM)
//最小生成树
#include<stdio.h>
#include<algorithm>
using namespace std;
struct edge{
int u;
int v;
int w;;
};
int f[7]={0};
//寻找点v的最终老大
int getf(int v){
if(f[v]==v)
return v;
else
{
f[v]=getf(f[v]);
return f[v];
}
}
int merg(int u,int v){
int t1,t2;
t1=getf(u);
t2=getf(v);
if(t1!=t2){
f[t2]=t1;
return 1;
}
return 0;
}
int cmp(edge e1,edge e2){
return e1.w<e2.w;
}
int main(){
edge e[10];
int n,m;
int sum=0;
int coun=0;
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
sort(e,e+m,cmp);
for(int i=0;i<n;i++)
f[i]=i;
for(int i=0;i<m;i++){
if(merg(e[i].u,e[i].v)){
coun++;
sum+=e[i].w;
}
if(coun==n-1)
break;
}
printf("%d\n",sum);
}
二、Prim算法(选点)
Prim算法:
首先选择一个顶点加入生成树,然后选择生成树中的所有连接着未入树的点的出边,选择一个最小的边连着的点加入生成树,重复n-1次,即加入n个点,则找到最小生成树。
实现步骤:
我在想,既然是选择点,并且是选择够n个点为止。
- 首先随便选择一个点,加入到生成树中,然后在生成树中选择一个未入树的点的最小出边对应的点,入树。这个时候涉及到一个最短路径问题。
如何知道哪个点离生成树最近?
刚开始的时候,生成树中只有一个点,dis[i]就代表第i个点到某某某的最短距离,这个某某某生成树,(和最短路到某某点是一个性质的)
- 每加入一个点到生成树中,需要更新一下其他点到生成树的最短距离。
但是为什么呢?
比如有三个点1 2 3
三条边
1->2 3
2->3 3
1->3 7
dis[1]=0 dis[2]=3 dis[3]=7
现在加入1,1的出边有2和3,选择最短的2,加入。
更新到点i到生成树的最短距离dis[点i]
dis[1]=0 dis[2]=0(这个不用更新了) dis[3]=3
这样的话,点i到生成树的距离就有了更新,所以每次选一个点之后需要更新一下到生成树的最短距离。
- 直到加入n个点为止,返回一个sum值是最小花费。
Prim代码实现:
时间复杂度:O(N^2)
#include<stdio.h>
#include<string.h>
int e[10][10];
int inf=0xffffff;
//返回一个int的最小花费
int Prim(int n){
int book[10];
int dis[10];
int coun=0,sum=0,t;
memset(book,0,sizeof(book));
book[1]=1;
coun++;
//设置每个点到生成树的最短距离,刚开始生成树中只有点1
for(int i=1;i<=n;i++)
dis[i]=e[i][1];
while(coun<n){
int min_=inf;
//找一个到生成树距离最小且没有加入生成树的点
for(int i=1;i<=n;i++){
if(!book[i]&&dis[i]<min_){
min_=dis[i];
t=i;
}
}
coun++;
book[t]=1;
sum+=min_;
//更新没加入生成树的点到生成树的距离
for(int i=1;i<=n;i++){
if(!book[i]&&dis[i]>e[i][t])
dis[i]=e[i][t];
}
}
return sum;
}
int main(){
int n,m;
int x,y,z;
scanf("%d%d",&n,&m);
//录入边值
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
if(i==j)
e[i][j]=0;
else
e[i][j]=inf;
}
for(int i=0;i<m;i++){
scanf("%d%d%d",&x,&y,&z);
e[x][y]=z;
e[y][x]=z;
}
printf("%d",Prim(n));
}
测试数据
输入:
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
输出:
19
还有堆优化之后的Prim算法,并且使用邻接表存图,可使其复杂度有O(N^2)降到O(MlogN),后续再更新