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

最小生成树之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算法。
  • 18
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
下面是克鲁斯卡尔最小生成树算法C++代码示例: ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; // 边的结构体 struct Edge { int src, dest, weight; }; // 并查集的结构体 struct Subset { int parent, rank; }; // 比较函数,用于按边的权重进行排序 bool compare(Edge a, Edge b) { return a.weight < b.weight; } // 查找操作,寻找元素所属集合的根节点 int find(vector<Subset>& subsets, int i) { if (subsets[i].parent != i) { subsets[i].parent = find(subsets, subsets[i].parent); } return subsets[i].parent; } // 合并操作,将两个集合进行合并 void unionSet(vector<Subset>& subsets, int x, int y) { int xroot = find(subsets, x); int yroot = find(subsets, y); if (subsets[xroot].rank < subsets[yroot].rank) { subsets[xroot].parent = yroot; } else if (subsets[xroot].rank > subsets[yroot].rank) { subsets[yroot].parent = xroot; } else { subsets[yroot].parent = xroot; subsets[xroot].rank++; } } // 克鲁斯卡尔最小生成树算法 void kruskalMST(vector<Edge>& edges, int V) { vector<Edge> result; // 存储最小生成树的边 // 按照边的权重进行排序 sort(edges.begin(), edges.end(), compare); vector<Subset> subsets(V); for (int i = 0; i < V; i++) { subsets[i].parent = i; subsets[i].rank = 0; } int e = 0; // 用于控制结果数组的下标 int i = 0; // 用于控制排序后的边数组的下标 while (e < V - 1 && i < edges.size()) { Edge nextEdge = edges[i++]; int x = find(subsets, nextEdge.src); int y = find(subsets, nextEdge.dest); if (x != y) { result.push_back(nextEdge); unionSet(subsets, x, y); e++; } } // 输出最小生成树的边及其权重 cout << "最小生成树的边:" << endl; for (int i = 0; i < result.size(); i++) { cout << result[i].src << " - " << result[i].dest << ",权重:" << result[i].weight << endl; } } int main() { int V, E; cout << "请输入顶点数:"; cin >> V; cout << "请输入边数:"; cin >> E; vector<Edge> edges(E); for (int i = 0; i < E; i++) { cout << "请输入第 " << i + 1 << " 条边的起点、终点和权重:"; cin >> edges[i].src >> edges[i].dest >> edges[i].weight; } kruskalMST(edges, V); return 0; } ``` 这段代码实现了克鲁斯卡尔最小生成树算法,通过输入顶点数和边数,然后逐条输入边的起点、终点和权重,最后输出最小生成树的边及其权重。注意,该代码中使用了并查集来判断是否形成环路,并使用了排序算法进行边的排序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值