yishujixiaoxiao 2019-11-03
对于一个图来说,我们可以选择不同的边而产生不同的树,由于边的选择不一样,每一条边的权值不一样,那我们最后生成出来的树的权值也就不一样,Kruskal算法和prim算法就是来找怎样选择边才可以使产生的树的权值最小。
Kruskal算法思路:现在有一个集合Q,来表示图中的所有的点,有一个集合U,来表示已经生成树中的点,当我们选择了一条边,那我们就将这条边的两个顶点加入到U中,初始时有n个点,m条边。既然我们要求的是生成的树的权值最小,那么我们是不是可以依次选择图中的最小边呢?由于一共用n个点,m条边,那么我们要选的边的最小的个数就是n-1。按照这样的思路,那我们首先要做的就是将这m条边进行从小到大排序,我们只要依次选择这些排好序的边就可以了,当我们已经将某个点已经加入到了我们生成树中,我们就不选择这条边。那么问题就来了,怎样来判断某个点是否已经加入到生成树中?这个时候我们可以使用并查集来判断这个点是否已经加入到了生成树中。 关于并查集:https://www.cnblogs.com/zhoubo123/p/11709160.html
代码实现:
#include<iostream> #include<algorithm> using namespace std; int father[1000000]; struct node { int a;//两个顶点 int b; int w;//权值。 }; int find(int root)//并查集查找函数 { if(root!=father[root]) father[root]=find(father[root]);//路径压缩。 return father[root]; } int merge(int a,int b)//并查集合并函数 { int aa=find(a); int bb=find(b); if(aa!=bb) { father[bb]=aa; return 1;//能够合并返回1 } return 0;//不能够合并返回0 } bool cmp(node a,node b)//排序 { return a.w<b.w; } int main() { int n,m; cin>>n>>m; node A[m]; int cnt=0;//记录选择边的个数 。 int sum=0;//生成树的权值。 for(int i=0;i<=n;i++) father[i]=i;//初始化并查集 。 for(int i=0;i<m;i++) cin>>A[i].a>>A[i].b>>A[i].w; sort(A,A+m,cmp);//排序 for(int i=0;i<m;i++)//最小生成树主体。 { if(merge(A[i].a,A[i].b))//能够合并那么久选择这条边 。 { sum+=A[i].w; cnt++; } if(cnt==n-1)//生成树中有n-1个点时,选择完成 。 break; } cout<<sum;//输出最小生成树的权值。 return 0; }
可以看出Kruskal算法选择边,而下面要说是如何通过选择点来实现最小生成树。
prim算法思路:要使用prim算法,首先我们要介绍两个数组,dis[ ]数组和book[ ]数组,dis[ i ]的作用是记录第 i 个点与当前生成的树的最小距离,如果有些点与当前的生成树没有直连边,那么我们就将他的值设为无穷大。我们每一次选择的点都是距离当前生成树距离最小的点。
book[ i ]只有两个值1和0,当book[ i ]=1时,表示 i 这个点已经被选了,book[ i ]=0时表示 i 这个点还没有被选。由于我们不断在选择点加入到树中,那么现在的问题就是如何来更新dis[ i ]数组。
dis[]数组模拟演示:
首先我们任选一个点作为初始点,在这个选择V1。此时相当于生成树中只有一个点。可以看到与V1直接相连的有V2,V3,V4。按照上面的讲述,那么初始化dis[]数组的为:dis[]={ 0,6,1,5,inf,inf }(inf代表无穷大)。
此时我们选择了V1,那么book[ 1 ]=1。
步骤:
1:在dis[ ]数组找到最小值(已经选过的点不考虑)所对应的点(即下标)。
2:将该点加入到生成树中。在这里可以到此时加入的点是V3,那么book [ 3 ]=1。
3:找到V3的直连边,V2,V4,V5,V6。那么我们就要更新dis[ ]数组下标所对应的值。
可以看到V1—>V3—>V2=6与V1—>V2=6相等,那么就不更新dis[ 2 ]。
V1—>V3—>V4=6大于V1—>V4=5,那么不跟新dis[ 4 ]。
V1—>V3—>V5=7小于inf,更新dis[ 5 ]=7。
V1—>V3—>V6=5小于inf,更新dis [ 6 ]=5。
4:重复1—3步骤,直到所有的点都被加入到了生成树中,此时的树就是最小生成树。
代码实现:
#include<iostream> using namespace std; #define inf 99999; int book[10000]; int e[10000][10000];//存图: int dis[100000]; int main() { int n,m; cin>>n>>m; int sum=0; for(int i=0;i<=n;i++)//初始化图 { for(int j=0;j<=n;j++) { if(i==j) e[i][j]=0; else e[i][j]=inf; } } for(int i=0;i<m;i++) { int a,b,w; cin>>a>>b>>w; e[a][b]=w; e[b][a]=w; } for(int i=0;i<=n;i++) book[i]=0;//初始化book数组 for(int i=1;i<=n;i++)//选择第一个点来初始化dis数组 dis[i]=e[1][i]; book[1]=1; for(int i=0;i<=n-2;i++) { int min_=inf; int u; for(int j=1;j<=n;j++) { if(book[j]==0&&dis[j]<min_)//找dis数组中的最小值对应的下标u { u=j; min_=dis[j]; } } book[u]=1;//标记选中的点 sum+=dis[u];//生成树的权值加上此时被选中的权值 for(int k=1;k<=n;k++)//更新dis数组 { if(book[k]==0&&dis[k]>e[u][k])//未被选且满足更新条件的更新 dis[k]=e[u][k]; } } cout<<sum; return 0; }
一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。