Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >最小生成树算法(下)——Kruskal(克鲁斯卡尔)算法

最小生成树算法(下)——Kruskal(克鲁斯卡尔)算法

作者头像
AI那点小事
发布于 2020-04-20 07:22:28
发布于 2020-04-20 07:22:28
1.3K00
代码可运行
举报
文章被收录于专栏:AI那点小事AI那点小事
运行总次数:0
代码可运行

概要

在我的上一篇文章最小生成树算法(上)——Prim(普里姆)算法 主要讲解对于稠密图较为合适的Prim算法。那么在接下里这片文章中我主要讲解对于稀疏图较为合适的Kruskal算法。


Kruskal算法

Kruskal算法思想概述: 如果说Prim算法可以用让一颗小树慢慢长大,那么Kruskal算法也可以用一句话来总结:将森林合并成树。就是说它比Prim算法更直接的贪心,把每个顶点看成一棵树,那么恶整个图就是一个森林。要做的就是一步一步的把最小的边收录到最小生成树且与最小生成树里的边不构成回路。

Kruskal算法过程: 1)首先构造一个有所有顶点构成的并查集(利用路径压缩),并构成的边最小堆。 2)对最小堆进行删除操作,对得到的最小边的两顶点进行回路检测,若不存在回路,则把该边收录到最小生成树中。 3)当最小生成树中的边小于顶点个数-1且最小堆中还有边是一直重复操作2)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//Kruskal算法
int Kruskal(){ 
    int sum_weight = 0;
    UF uf(this->Nv);            //构造并查集
    int cnt = 0;
    //构造边集合最小堆 
    MinHeap minheap(this->Ne);
    minheap.Create(this->Edge_Set);
    int edge_cnt = 0;
    //最小生成树里未收录Nv-1条边且边集合里还有边则一直循环 
    while(edge_cnt != this->Nv-1 && !minheap.IsEmpty()){
        Edge min_edge = minheap.DeleteMin(); 
        int start= min_edge.getStartVertex();
        int end = min_edge.getEndVertex();
        if(uf.CheckCircle(start,end)){//不构成回路
            //对应的边收录到最小生成树 
            this->MST.push_back(min_edge);
            this->MST[edge_cnt++] = min_edge;
            sum_weight += min_edge.getWeight();
        }
    }
    if(edge_cnt < this->Nv-1){
        sum_weight = -1;
    }
    return sum_weight; 
}

例子

我们就一下图模型来检测Kruskal算法:

由于出在考研期间,想巩固各种数据结构的各种操作,故最小堆没有用stl的make_heap()或者优先队列,而是自己手动实现最小堆,故代码看起来很长,故有关最小堆或者最大堆的相关内容请移步我的另一篇博客: 。同样对于并查集的相关内容请移步我的另一篇博客:并查集。在这篇博客里有关上述两个数据结构我不做解释。

全部代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <cstring>
#include <vector> 
using namespace std;

const int MAX = 65535; 
const int Min_Data = -32768;

//并查集 
class UF{
    private:
        int* path;      //父亲结点 
        int size;       //容量 
    public:
        //构造函数 
        UF(int size){
            this->size = size;
            this->path = new int[size+1];
            memset(this->path , -1 ,sizeof(path[0])*(size+1));
        }

        //查找操作,采用路径压缩 
        int Find(int x){
            if(this->path[x] < 0){
                return x;
            }else{
                //首先查找x的父节点path[x],然后把根节点变成path[x],之后再返回根 
                return this->path[x] = this->Find(this->path[x]);
            }
        }

        //并操作
        void Union(int root1 ,int root2){
            root1 = this->Find(root1);
            root2 = this->Find(root2);
            //集合里存的是集合个数的相反数,大集合并小集合 
            if(this->path[root1] < this->path[root2]){//集合1比集合2大 
                this->path[root1] += this->path[root2];
                this->path[root2] = root1;
            }else{//反之集合2比集合1大 
                this->path[root2] += this->path[root1];
                this->path[root1] = root2;
            } 
        } 

        //检查是否产生回路
        bool CheckCircle(int root1, int root2){
            root1 = this->Find(root1);
            root2 = this->Find(root2);
            if(root1 == root2){//如果产生回路 
                return false;
            }else{//不产生回路
                this->Union(root1,root2);
                return true;
            }
        } 
};

//边类 
class Edge{
    private:
        int Start_Vertex;   //起始边
        int End_Vertex;     //终止边
        int Weight;         //边的权重
    public:
        //构造函数
        void Set(int start_vertex,int end_vertex ,int weight){
            this->Start_Vertex = start_vertex;
            this->End_Vertex = end_vertex;
            this->Weight = weight;
        }

        //打印边
        void Print(){
            cout<<this->Start_Vertex<<"\t"<<this->End_Vertex<<"\t"<<this->Weight<<endl;
        }

        int getWeight(){
            return this->Weight;
        }

        int getStartVertex(){
            return this->Start_Vertex;
        }

        int getEndVertex(){
            return this->End_Vertex;
        }
};

class MinHeap{
    private:
        vector<Edge> Edges;     //边集数组
        int capacity;       //最大容量 
        int size;           //当前规模 
    public:
        //构造函数
        MinHeap(int size){
            this->capacity = size*2; 
            this->size = 0;
            this->Edges.reserve(this->capacity);
            Edge edge;
            edge.Set(0,0,Min_Data);
            this->Edges.push_back(edge);
        }

        //把Edge[n]为根的子堆调整为最小堆 
        void PreDown(int n){
            Edge edge = this->Edges[n]; //保存下标为n的边
            int parent,child;
            for(parent = n ; parent*2 <= this->size ; parent = child){
                child = parent*2;
                if(child != this->size){
                    int wl = this->Edges[child].getWeight();//左孩子权重 
                    int wr = this->Edges[child+1].getWeight();//右孩子权重
                    if(wr < wl){//右孩子权重小于左孩子权重 
                        child++;//选择右孩子 
                    }
                }
                //如果根节点权重小于子堆,那么找到合适位置,跳出循环 
                if(edge.getWeight() < this->Edges[child].getWeight()){
                    break;
                }else{
                    this->Edges[parent] = this->Edges[child];
                } 
            }
            this->Edges[parent] = edge;
        } 

        //构造最小堆
        void Create(vector<Edge> &set){
            for(int i = 0 ; i < set.size() ; i++){
                this->Edges[++this->size] = set[i];
            }
            //从最后一个元素的父亲开始,把自己为根的子堆调整为最小堆 
            for(int i = this->size/2 ; i >= 1 ; i--){
                this->PreDown(i);
            }
        }

        //删除操作 
        Edge DeleteMin(){
            if(this->IsEmpty()){
                Edge edge;
                edge.Set(0,0,Min_Data);
                return edge;
            } 
            Edge min_edge = this->Edges[1];
            this->Edges[1] = this->Edges[this->size];
            this->size--;
            this->PreDown(1);
            return min_edge;
        }

        //判断最小堆是否为空 
        bool IsEmpty(){
            return this->size == 0;
        }

        void Print(){
            cout<<"最小堆的元素有:"<<this->size<<endl;
            for(int i = 1 ; i <= this->size ; i++){
                cout<<this->Edges[i].getStartVertex()<<" "<<this->Edges[i].getEndVertex()<<" "<<this->Edges[i].getWeight()<<endl;
            }
        }
};

class Graph{
    private:
        int** G;                //邻接矩阵
        int Nv;                 //顶点数
        int Ne;                 //边数 
        vector<Edge> MST;               //最小生成树的边集合 
        vector<Edge> Edge_Set;          //边集合 
    public:
        //构造函数
        Graph(int nv ,int ne){
            this->Nv = nv;
            this->Ne = ne;
            this->MST.reserve(this->Nv-1);
            this->Edge_Set.reserve(ne);
            this->G = new int*[nv+1];
            for(int i = 0 ; i < nv+1 ; i++){
                this->G[i] = new int[nv+1];
                for(int j = 0 ; j < nv+1 ; j++){
                    this->G[i][j] = MAX;
                }
            }
            cout<<"请输入边与边长:"<<endl;
            for(int i = 0 ; i < ne ; i++){
                int v1,v2,weight;
                cin>>v1>>v2>>weight;
                this->G[v1][v2] = this->G[v2][v1]= weight;
                Edge edge;
                edge.Set(v1,v2,weight);
                Edge_Set.push_back(edge); 
            }
        }

        //Kruskal算法
        int Kruskal(){ 
            int sum_weight = 0;
            UF uf(this->Nv);            //构造并查集
            int cnt = 0;
            //构造边集合最小堆 
            MinHeap minheap(this->Ne);
            minheap.Create(this->Edge_Set);
            int edge_cnt = 0;
            //最小生成树里未收录Nv-1条边且边集合里还有边则一直循环 
            while(edge_cnt != this->Nv-1 && !minheap.IsEmpty()){
                Edge min_edge = minheap.DeleteMin(); 
                int start= min_edge.getStartVertex();
                int end = min_edge.getEndVertex();
                if(uf.CheckCircle(start,end)){//不构成回路
                    //对应的边收录到最小生成树 
                    this->MST.push_back(min_edge);
                    this->MST[edge_cnt++] = min_edge;
                    sum_weight += min_edge.getWeight();
                }
            }
            if(edge_cnt < this->Nv-1){
                sum_weight = -1;
            }
            return sum_weight; 
        }

        void Print_Kruskal(){
            cout<<"Kruskal算法构造的最小生成树的边集合为:"<<endl;
            cout<<"源点\t终点\t权重"<<endl;
            for(int i = 0 ; i < this->Nv-1 ; i++){
                Edge edge = this->MST[i];
                edge.Print();
            }   
        }
};

int main()
{
    int nv,ne;
    cout<<"请输入顶点数与边数:"<<endl;
    cin>>nv>>ne;
    Graph graph(nv,ne);
    int min_weight = graph.Kruskal();
    if(min_weight != -1){
        cout<<"Kruskal算法生成的最小生成树的权重和为:"<<endl;
        cout<<min_weight<<endl;
        graph.Print_Kruskal();  
    } 

    return 0;
 } 

截图:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C++ Prim和 Kruskal 求最小生成树算法
生成树:在图中找一棵包含图中的所有节点的树,生成树是含有图中所有顶点的无环连通子图。所有可能的生成树中,权重和最小的那棵生成树就叫最小生成树。在无向加权图中计算最小生成树,使用最小生成树算法的现实场景中,图的边权重一般代表成本、距离这样的标量。
一枚大果壳
2023/11/07
3050
C++ Prim和 Kruskal 求最小生成树算法
数据结构与算法-最小生成树之克鲁斯卡尔(Kruskal)算法
Kruskal 算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
越陌度阡
2020/11/26
6760
数据结构与算法-最小生成树之克鲁斯卡尔(Kruskal)算法
文心一言 VS 讯飞星火 VS chatgpt (345)-- 算法导论23.2 4题
四、假定图中的边权重全部为整数,且在范围$1 \sim |V|$内。在此种情况下,Kruskal算法最快能多快?如果边的权重取值范围在1到某个常数$W$之间呢?如果要写代码,请用go语言。
福大大架构师每日一题
2024/09/13
1140
文心一言 VS 讯飞星火 VS chatgpt (345)-- 算法导论23.2 4题
文心一言 VS 讯飞星火 VS chatgpt (336)-- 算法导论23.1 5题
要证明在连通图G=(V,E)中,如果e是某条环路上权重最大的边,则图G'=(V,E-{e})中存在一棵最小生成树,这棵生成树同时也是G的最小生成树,我们可以按照以下步骤进行:
福大大架构师每日一题
2024/09/06
1260
文心一言 VS 讯飞星火 VS chatgpt (336)-- 算法导论23.1 5题
文心一言 VS 讯飞星火 VS chatgpt (348)-- 算法导论23.2 7题
在图中加入一个新节点及其相关边后,要更新最小生成树(MST),通常意味着需要重新计算包含新节点的最小生成树,因为新节点的加入可能改变了原有MST的结构。直接更新原有MST的复杂度可能很高,且不一定能保证结果正确,因此一般的方法是重新计算包含新节点和旧图的最小生成树。
福大大架构师每日一题
2024/09/18
930
文心一言 VS 讯飞星火 VS chatgpt (348)-- 算法导论23.2 7题
最小生成树算法(上)——Prim(普里姆)算法
最小生成树:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。根据定义可知对于一个有V个顶点的图来说,其最小生成树定包含V个顶点与V-1条边。反过来如果一个图的最小生成树存在,那么图一定是连通图。 对于最小生成树算法最著名的有两种:Prim算法与Kruskal算法。
AI那点小事
2020/04/20
9230
文心一言 VS 讯飞星火 VS chatgpt (340)-- 算法导论23.1 9题
由于这个证明主要是理论性的,不涉及具体的图数据结构操作,但我们可以给出一个示意性的 Go 代码框架,用于表示图、生成树等概念(注意,这不是一个完整的实现,而是概念性的):
福大大架构师每日一题
2024/09/06
870
文心一言 VS 讯飞星火 VS chatgpt (340)-- 算法导论23.1 9题
文心一言 VS 讯飞星火 VS chatgpt (347)-- 算法导论23.2 6题
在比较Prim算法和Kruskal算法在特定条件下的性能时,我们需要考虑几个因素,主要是图的表示方式、边的数量、顶点的数量以及边的权重分布。由于在这个问题中,所有的边权重都均匀分布在半开区间[0,1)内,且我们并没有明确图的密度(即边数与顶点数的关系),但我们可以基于算法的基本性质给出一些一般性的分析。
福大大架构师每日一题
2024/09/18
1010
文心一言 VS 讯飞星火 VS chatgpt (347)-- 算法导论23.2 6题
最小生成树,克鲁斯卡尔算法入门。
带权图:边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权。
全栈程序员站长
2021/12/23
5040
生成树和最小生成树prim,kruskal
普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。 中文名 普里姆算法 外文名 Prim Algorithm 别 称 最小生成树算法 提出者 沃伊捷赫·亚尔尼克(Vojtěch Jarník) 提出时间 1930年 应用学科 计算机,数据结构,数学(图论) 适用领域范围 应用图论知识的实际问题 算 法 贪心 目录 1 算法描述 2 时间复杂度 3 图例描述 4 代码 ▪ PASCAL代码 ▪ c代码 ▪ C++代码 5 时间复杂度 算法描述编辑 1).输入:一个加权连通图,其中顶点集合为V,边集合为E; 2).初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空; 3).重复下列操作,直到Vnew = V: a.在集合E中选取权值最小的边,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一); b.将v加入集合Vnew中,将边加入集合Enew中; 4).输出:使用集合Vnew和Enew来描述所得到的最小生成树。
废江_小江
2022/09/05
9690
算法:图解最小生成树之克鲁斯卡尔(Kruskal)算法
我们在前面讲过的《克里姆算法》是以某个顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树的。同样的思路,我们也可以直接就以边为目标去构建,因为权值为边上,直接找最小权值的边来构建生成树也是很自然的
s1mba
2018/01/12
2.8K0
算法:图解最小生成树之克鲁斯卡尔(Kruskal)算法
最小生成树——克鲁斯卡尔(Kruskal)算法
之前学了用普里姆算法来求最小生成树的权值和,但是它的时间复杂度为O(|V2|),使用优先级队列优化后,可以优化为O(|E|log|V|)。
灯珑LoGin
2022/10/31
7540
文心一言 VS 讯飞星火 VS chatgpt (332)-- 算法导论23.1 1题
为了证明边(u,v)是图G的某棵最小生成树中的一条边,我们可以使用反证法结合最小生成树的性质来进行证明。
福大大架构师每日一题
2024/08/29
1040
文心一言 VS 讯飞星火 VS chatgpt (332)-- 算法导论23.1 1题
文心一言 VS 讯飞星火 VS chatgpt (339)-- 算法导论23.1 8题
要证明对于图G的任何其他最小生成树T',列表L(作为树T的边权重有序列表)也是T'中一个边权重的有序列表,我们可以从最小生成树的定义和性质出发。
福大大架构师每日一题
2024/09/06
500
文心一言 VS 讯飞星火 VS chatgpt (339)-- 算法导论23.1 8题
Python 算法基础篇之最小生成树算法: Prim 算法和 Kruskal 算法
在图论中,最小生成树是一个重要的概念,它是一个连通图的子图,包含图中的所有节点,并且边的权重之和最小。 Prim 算法和 Kruskal 算法是两种常用的最小生成树算法。本篇博客将重点介绍这两种算法的原理、应用场景以及使用 Python 实现,并通过实例演示每一行代码的运行过程。
小蓝枣
2023/07/25
1.3K0
图论-最小生成树kruskal算法(Java)
和prim算法以顶点为出发点不同,kruskal算法以边为中心,将所有边以小到大排序,遍历边,如果当前边的两个顶点有一个没有访问过,则记录该边,直到记录的边到达顶点数-1时,即所有顶点都可以相连,为最小生成树 实现代码:
aruba
2021/12/06
8320
图论-最小生成树kruskal算法(Java)
【数学建模】——【python】实现【最短路径】【最小生成树】【复杂网络分析】
假设有一个包含多个城市及其之间距离的列表(或图结构),其中每个城市是图中的一个节点,城市之间的距离是边的权重。使用Dijkstra算法或Floyd-Warshall算法(视情况而定,如果图中节点数较多,推荐使用Dijkstra;如果需要求出所有点对间的最短路径,则使用Floyd-Warshall)来计算并绘制出从一个指定城市到其他所有城市的最短路径图。
小李很执着
2024/08/05
3330
【数学建模】——【python】实现【最短路径】【最小生成树】【复杂网络分析】
加权无向图----Kruskal算法实现最小生成树
上一篇:加权无向图的实现 加权无向图----Prim算法实现最小生成树 数据结构: 用一条优先队列将边按照权重从小到大排序 用union-find数据结构来识别会形成环的边 用一条队列来保存最小生成树的所有边 Kruskal算法的计算一个含V个顶点和E条边的连通加权无向图的最小生成树所需空间与E成正比,所需时间与ElogE成正比(最坏情况)。 方法:将边都添加进最小优先权队列中,每次从中取出最小的边,检查会不会与已经选出的边构成环(使用union-find算法),如果构成环,则弃掉这条边,否则将这条边加入最
SuperHeroes
2018/05/30
1.1K0
使用贪心算法解决最小生成树问题
大家好,我是 V 哥。今天跟大家聊一聊贪心算法问题,因为遇到这个面试题,问贪心算法解决最小生成树是怎么设计的,以及如何应用?好家伙,这面试官一上来就不按套路出牌,直接上难度,如果你遇到这样的问题,该怎么办呢。下面 V 哥来详细聊一聊。
威哥爱编程
2025/01/22
1270
【c++高阶DS】最小生成树
连通图中的每一棵生成树,都是原图的一个极大无环子图,即:从其中删去任何一条边,生成树就不在连通;反之,在其中引入任何一条新边,都会形成一条回路,且这些边的权值之和最小
用户11029103
2024/12/28
1810
【c++高阶DS】最小生成树
推荐阅读
相关推荐
C++ Prim和 Kruskal 求最小生成树算法
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验