C++ 最小生成树之kruskal(克鲁斯卡尔)算法

来源:互联网 发布:opencv icp算法代码 编辑:程序博客网 时间:2024/06/14 23:36
最小生成树之kruskal(克鲁斯卡尔)算法

kruskal算法:同样解决最小生成树的问题,和prim算法不同,kruskal算法采用了边贪心的策略,思想要比prim算法简单。

关于prim算法可参考:点击打开链接

算法基本思想:在初始状态时隐去图中的所有边,这样图中每个顶点都自成一个连通块。之后执行下面的步骤:
(1)对所有的边按边权从小到大进行排序;
(2)按边权从小到大测试所有边,如果当前测试边所连接的两个顶点不在同一个连通块中,则把这条测试边加入当前最小生成树中;否则,将边舍弃;
(3)执行步骤(2),知道最小生成树中的边数等于总顶点数减1或者测试完所有的边时结束。当结束时,如果最小生成树的边数小于总顶点数减1,则说明该图不连通。

执行过程:
下面通过举例来说明kruskal算法的执行流程,:
(1)如图(a),当前图中边权最小的边为(V0,V4),权值为1。由于V0和V4不在同一个连通块中,因此把(V0,V4)加入最小生成树,此时最小生成树中有1条边,权值之和为1。
(2)如图(b),当前图中边权最小的边为(V1,V2),权值为1。由于V1和V2不在同一个连通块中,因此把(V1,V2)加入最小生成树,此时最小生成树中有2条边,权值之和为2。
(3)如图(c),当前图中边权最小的边为(V0,V5),权值为2。由于V0和V5不在同一个连通块中,因此把(V0,V5)加入最小生成树,此时最小生成树中有3条边,权值之和为4。
(4)当前图中边权最小的边为(V4,V5),权值为3。由于V4和V5在同一个连通块中,如果加入就会形成一个环,因此把(V4,V5)舍弃。
(5)如图(d),当前图中边权最小的边为(V1,V5),权值为3。由于V1和V5不在同一个连通块中,因此把(V1,V5)加入最小生成树,此时最小生成树中有4条边,权值之和为7。
(6)当前图中边权最小的边为(V0,V1),权值为4。由于V0和V1在同一个连通块中,如果加入就会形成一个环,因此把(V0,V1)舍弃。
(7)如图(e),当前图中边权最小的边为(V3,V5),权值为4。由于V3和V5不在同一个连通块中,因此把(V3,V5)加入最小生成树,此时最小生成树中有5条边,权值之和为11。
此时由于最小生成树的边数为5,恰好等于定点数减1,因此kruskal算法结束,最后所得的最小生成树的边权之和为11。

kruskal算法是对边进行遍历,因此需要对边的定义。
struct edge{       int u, v;                                //边的两个端点编号       int cost;                                //边权       edge(int x,int y, int c):u(x),v(y),cost(c){}};

在对边定义完后,需要自定义一个排序函数来对边进行从小到大的排序,我们可以利用STL中的sort函数,然后提供一个自定义的cmp函数。
bool cmp(edge a, edge b){       return a.cost < b.cost;}

下面是kruskal算法的伪代码:
int kruskal(){     令最小生成树的边权之和为ans,最小生成树的当前边数是NumEdge;     将所有边按边权从小到大排序;     for(从小到大遍历所有边){          if(当前测试边的两个端点在不同的连通块中){               将该测试边加入到最小生成树中;               ans += 测试边的边权;               最小生成树的当前边数NumEdge加1;               当边数NumEdge等于顶点数减1时结束循环;          }     }     return ans;}

注意:这个伪代码里需要注意两个细节:
(1)如何判断测试边的两个端点是否在不同的连通块中;
(2)如何将测试边加入到最小生成树中;
其实我们可以把每个连通块当做一个集合,判断两个端点是否在同一个连通块中就可以转换为判断两个端点是否在同一个集合中,解决这个问题的方法是使用并查集。并查集可以通过查询两个结点所在集合的根结点是否相同来判断它们是否在同一个集合中,而合并功能恰好可以解决上面提到的第二个问题,即只要把测试边的两个端点所在集合合并,就能达到将边加入最小生成树的目的。

具体代码实现如下:
#include <iostream>#include <vector>#include <algorithm>using namespace std;/*边的定义*/struct edge{       int u, v;                                //边的两个端点编号       int cost;                                //边权       edge(int x,int y, int c):u(x),v(y),cost(c){}};/*边的比较函数*/bool cmp(edge a, edge b){       return a.cost < b.cost;}/*并查集查询函数,返回x所在集合的根结点*/int findFather(vector<int> father, int x){       int a = x;       while (x != father[x])              x = father[x];       while (a != father[a]) {              int z = a;              a = father[a];              father[z] = x;       }       return x;}/*Kruskal算法求无向图的最小生成树*/int Kruskal(int n, int m, vector<edge>& E){       /*       param       n:                         图的顶点个数       m:                         图中边的个数       E:                         边的集合       */       vector<int> father(n);                                 //并查集数组       int ans = 0;                                           //所求边权之和       int NumEdge = 0;                                       //记录最小生成树边数       for (int i = 0; i < n; i++)                            //初始化并查集              father[i] = i;       sort(E.begin(), E.end(), cmp);                         //所有边按边权从小到大排序       for (int i = 0; i < m; ++i)                            //枚举所有边       {              int faU = findFather(father, E[i].u);           //查询端点u所在集合的根结点              int faV = findFather(father, E[i].v);           //查询端点v所在集合的根结点              if (faU != faV) {                               //如果不在一个集合中                     father[faU] = faV;                       //合并集合(相当于把测试边加入到最小生成树)                     ans += E[i].cost;                     NumEdge++;                               //当前生成树边数加1                     if (NumEdge == n - 1)                    //边数等于顶点数减1,算法结束                           break;              }       }       if (NumEdge != n - 1)                                  //无法连通时返回-1              return -1;       else              return ans;                                     //返回最小生成树边权之和}int main(){       vector<edge> E = { edge(0,1,4),edge(1,2,1),edge(2,3,6),edge(3,4,5),edge(0,4,1),                          edge(0,5,2),edge(1,5,3),edge(2,5,5),edge(3,5,4),edge(4,5,3) };       int n = 6;       int m = 10;       int res = Kruskal(n, m, E);       cout << res << endl;}
运行结果如下:

kruskal算法的时间复杂度主要来源于对边的排序,因此其时间复杂度为O(ElogE),其中E是图中边的个数。该算法适合顶点数较多,边数较少的情况,和prim算法正好相反。
总结:如果是稠密图(边多),选择prim算法,如果是稀疏图(边少),选择kruskal算法。
阅读全文
1 0