Kruskal求解无向图的最小生成树

1.最小生成树是什么

图的最小生成树可以理解为用最少的边让图连通,即就是让图的任意两点之间可以互相到达,并且将多余的边去掉。
所以我们的目标就是在图连通的条件下使边的总长度最短。

2.算法思想

基于上述描述,大神Kruskal就提出了一种以自己名字命名的Krusal算法,用来解决图的最小生成树问题。
该算法的核心思想是:
①首先按照边的权值进行从小到大的排序。
②循环地从剩余边中选择权值较小且不会产生回路的边,加入生成树中,直到加入了n-1条边为止。

2.1算法具体实现步骤

以下图为例:
在这里插入图片描述

  1. 先将最小生成树初始化为孤立的节点
    在这里插入图片描述
    2.循环地从剩余边中选择权值较小且不会产生回路的边,加入生成树中,直到加入了n-1条边为止。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.具体实现

#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

typedef struct Info
{
    int u;//起点
    int v;//终点
    int w;//权重
}Info;

//并查集,判断图是否连通
class UnionFind {

private:
    // 记录每一个节点的父节点father<当前节点下标,父节点下标>
    unordered_map<int, int> father;

public:
    //找节点x的父节点
    int find(int x)
    {
        int root = x;

        while (father[root] != -1)
        {
            root = father[root];
        }
        //优化的点:如果我们树很深,那么每次查询的效率都会非常低。这一步把树的深度固定为二。
        while (x != root)
        {
            int original_father = father[x];
            father[x] = root;
            x = original_father;
        }

        return root;
    }

    bool is_connected(int x, int y)
    {
        return find(x) == find(y);
    }

    //将连通的两个节点合并为同一个祖先
    bool merge(int x, int y)
    {
        int root_x = find(x);
        int root_y = find(y);

        if (root_x != root_y)
        {
            father[root_y] = root_x;
            return true;
        }
        return false;
    }
    //将新节点添加到并查集中
    void add(int x)
    {
        if (!father.count(x))
        {
            father[x] = -1;
        }
    }
    
};


/*
测试用例:
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
*/
class Kruskal:protected UnionFind
{
private:
    vector<Info> graph;
    int vertice = 0;//顶点数
    int edge = 0;//边数
    int sum = 0;//最小生成树的权重总和
    int count = 0;//控制遍历次数

    //将图的边进行排序(从小到大)
    //快速排序的一次划分(Partion)
    int Partion(int start, int end)//O(n)
    {
        Info tmp = graph[start];//基准
        int left = start, right = end;
        while (left < right) //i==j要退出循环
        {
            while (left < right && graph[right].w >= tmp.w)//从后向前找比基准值小的数据
            {
                right--;
            }
            if (graph[right].w < tmp.w)
            {
                graph[left] = graph[right];
            }

            while (left < right && graph[left].w <= tmp.w)//从前往后找比基准值大的数据
            {
                left++;
            }
            if (graph[left].w > tmp.w)
            {
                graph[right] = graph[left];
            }
        }

        graph[left] = tmp;//基准应该放的位置
        return left;
    }

    void Quick_recur(int start, int end)//O(log(n))
    {
        int mid = Partion(start, end);//一次划分

        //递归:O(log(n))
        if ((mid - start) > 1)//mid的左部分数据,左边的数据超过一个就需要排序
        {
            Quick_recur(start, mid - 1);
        }
        if ((end - mid) > 1)//mid的右部分数据
        {
            Quick_recur(mid + 1, end);
        }
    }

public:
    //构造函数初始化(顶点数和边数)
    Kruskal(int x,int y):vertice(x),edge(y)
    {
        graph.resize(edge + 1);//0号下标不用
    }
    void Init_Graph()
    {
        for (int i = 1; i <= edge; i++)
        {
            int u = 0, v = 0, w = 0;
            cin >> u >> v >> w;
            graph[i].u = u;
            graph[i].v = v;
            graph[i].w = w;
        }
    }

    int Kruskal_Alg()
    {
        Quick_recur(1, edge);

        //并查集初始化
        UnionFind fff;        

       
        //从小到大遍历每一条边
        for (int i = 1; i <= edge; i++) 
        {
            fff.add(graph[i].u);
            fff.add(graph[i].v);//将两个节点加入并查集

            //判断加入边之图是否连通
            if (fff.merge(graph[i].u, graph[i].v))
            {
                count++;
                sum += graph[i].w;
            }
            if (count == edge - 1)
            {
                break;
            }
        }

        return sum;
    }
};

int main()
{
    Kruskal kk(6,9);//6个顶点,9条边
    int u = 0, v = 0, w = 0;
    //输入边的信息
    cout << "请输入边的信息:" << endl;
    kk.Init_Graph();
    int num = kk.Kruskal_Alg();
 
    cout << num;

    return 0;
}

在这里插入图片描述

END

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值