Kruskal算法刷题笔记

理论基础:

例题:

卡码网---53:寻宝

题目

题目描述

在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。

不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通起来。 

给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。以最小化公路建设长度,确保可以链接到所有岛屿。

输入描述

第一行包含两个整数V 和 E,V代表顶点数,E代表边数 。顶点编号是从1到V。例如:V=2,一个有两个顶点,分别是1和2。

接下来共有 E 行,每行三个整数 v1,v2 和 val,v1 和 v2 为边的起点和终点,val代表边的权值。

输出描述

输出联通所有岛屿的最小路径总距离

笔记:

这道题的总体思路是:先将所有边进行权值排序优先选择权值小的边,然后从权值最小的边开始判断该边左右两点是否为同一个祖先,如果为同一祖先那么联通图中就存在环,否则我们就将这两点加入到联通图中,这里就用到了我们并查集的思路。然后我们将同意联通图中的带权边的权值加起来就是连接所有节点的最小连通子图。

最小生成树问题解决的都是求所给带权有向图中的所有节点的最小连通子图,prim算法是从节点的角度出发,不断选择里最小生成树最近的节点从而达到最小联通子图,而kruskal算法是从边的角度出发,对边的权值进行排序,优先选择权值最小的边加入到最小生成树中,从而构成最小联通子图。

prim算法是将符合条件的点一个一个加入到最小生成树中,kruskal算法是将符合条件的带权边一条一条的加入到最小生成树中。kruskal算法是从所有带权边中寻找符合条件的边加入到最小生成树。

#include<bits/stdc++.h>
using namespace std;

struct Edge{
    int l;
    int r;
    int val;
};

bool s_rule(const Edge& a, const Edge& b){
    return a.val < b.val;
}

vector<int> father;

void init(int n){
    for(int i = 1; i <= n; i++){
        father[i] = i;
    }
}

int find(int x){
    return x == father[x] ? x : father[x] = find(father[x]);
}

void join(int u, int v){
    u = find(u);
    v = find(v);
    father[v] = u;
}

int main(){
    int v,e;
    int res = 0;
    cin >> v >> e;
    father = vector<int>(v + 1, 0);
    vector<Edge> edges;
    while(e--){
        int v1, v2, val;
        cin >> v1 >> v2 >> val;
        edges.push_back({v1, v2, val});
    }
    sort(edges.begin(), edges.end(), s_rule);
    int n = edges.size();
    init(v);
    for(int i = 0; i < n; i++){
        if(find(edges[i].l) != find(edges[i].r)){
            join(edges[i].l, edges[i].r);
            res += edges[i].val;
        }
    }
    cout << res << endl;
    
    
    return 0;
}

Leetcode-1489:找到最小生成树里的关键边和伪关键边

题目:

给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1 ,同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti] 表示在 fromi 和 toi 节点之间有一条带权无向边。最小生成树 (MST) 是给定图中边的一个子集,它连接了所有节点且没有环,而且这些边的权值和最小。

请你找到给定图中最小生成树的所有关键边和伪关键边。如果从图中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边。伪关键边则是可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边。

请注意,你可以分别以任意顺序返回关键边的下标和伪关键边的下标。

样例:

输入:n = 5, edges = [[0,1,1],[1,2,1],[2,3,2],[0,3,2],[0,4,3],[3,4,3],[1,4,6]]
输出:[[0,1],[2,3,4,5]]
解释:上图描述了给定图。
下图是所有的最小生成树。

笔记:

这道题的关键在于我们要明确关键边与伪关键边的具体定义是什么:

关键边:如果从图中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边。

通过题目描述的定义,我们可以推断出关键边一定是在最小生成树内,并且如果删掉这条边就会导致最小生成树的权值和发生变化。

伪关键边:可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边。

从题目中给出的定义我们可以得知:伪关键边对于最小生成树的权值和并没有影响。

所以我们就可以得出以下的解题策略:

(1)首先我们现将原图的最小生成树的权值和求出,作为后续比较的依据。

(2)我们遍历所有边,一一尝试删除该边并计算对应的最小生成树的权值和,如果与基础权值和不相等,那么这条边就是关键边,并将其符号加入到关键边数组中。

(3)我们遍历所有边,尝试加入不同的边,要在建立最小生成树之前将这条边加入到最小生成树中并查看该权值和是否与基础权值和相等,如果相等那么改变可能为伪关键边,这里我们还需要注意要把关键边从这个可能是伪关键边的选项中排除,所以我们需要设置一个set容器进行对关键边的存储,在使用迭代器对该容器查找,如果当前遍历到的边是关键边则跳过下面的建立最小生成树环节。

(4)将得到的伪关键边加入到伪关键边数组中。

注意:

我们这里寻找最小生成树的方法采用了kruskal算法,这个算法运用到了并查集,以及内部需要对edges数组进行排序,那么排序后各个变对应的序号也就混乱了,会导致后续的错误,所以这里我们采用为一条边建立结构体,内部存储左右两点,权值,以及其在原数组中的下表索引。

class Solution {
public:
// 定义每条边的结构体
    struct Edge{
        int v1,v2,val;
    };

    vector<int> father;

    void init(int n){
        father.resize(n);
        for(int i = 0; i < n; i++){
            father[i] = i;
        }
    }

    int find(int x){
        return x == father[x] ? x : father[x] = find(father[x]);
    }

    void join(int u, int v){
        u = find(u);
        v = find(v);
        father[v] = u;
    }

    bool issame(int u, int v){
        u = find(u);
        v = find(v);
        if(u == v)	return true;
        else	return false;
    }

// 对结构体数组进行排序的准则:
    static bool s_opt(const Edge& a, const Edge& b){
        return a.val < b.val;
    }

// 判断结构体是否相等的准则:
    bool st(Edge& a, Edge& b){
        if(a.v1 == b.v1 && a.v2 == b.v2 && a.val == b.val){
            return true;
        }else{
            return false;
        }
    }

// 求关键边:删除一条边后的最小生成树:
    int del_p(int n, vector<Edge>& edges, Edge& del){
        int n2 = edges.size();
        init(n);
        int res = 0;
        for(int i = 0; i < n2; i++){
            if(st(edges[i], del)){
                continue;
            }
            if(find(edges[i].v1) != find(edges[i].v2)){
                join(edges[i].v1, edges[i].v2);
                res += edges[i].val;
            }
        }
        return res;
    }

// 求伪关键边:添加一条边之后的最小生成树:
    int add_p(int n, vector<Edge>& edges, Edge& add){
        int n2 = edges.size();
        init(n);
        int res = 0;
        join(add.v1, add.v2);
        res += add.val;
        for(int i = 0; i < n2; i++){
            if(st(edges[i], add)){
                continue;
            }
            if(find(edges[i].v1) != find(edges[i].v2)){
                join(edges[i].v1, edges[i].v2);
                res += edges[i].val;
            }
        }
        return res;
    }

    vector<vector<int>> findCriticalAndPseudoCriticalEdges(int n, vector<vector<int>>& edges) {
        int m = edges.size();
        int minres1 = 0;

        // 建立包含每条边的数据的结构体数组:
        vector<Edge> edges2;
        for(int i = 0; i < m; i++){
            edges2.push_back({edges[i][0], edges[i][1], edges[i][2]});
        }
        // 求原图的最小生成树权值和:
        sort(edges2.begin(), edges2.end(), s_opt);
        init(n);
        int n2 = edges2.size();
        for(int i = 0; i < n2; i++){
            if(find(edges2[i].v1) != find(edges2[i].v2)){
                join(edges2[i].v1, edges2[i].v2);
                minres1 += edges2[i].val;
            }
        }

        vector<int> mindel;// 基础权值和

        // 求关键边:
        for(int i = 0; i < n2; i++){
            if(del_p(n, edges2, edges2[i]) != minres1){
                mindel.push_back(i);
            }
        }
        set<int> del_set(mindel.begin(), mindel.end());

        // 求伪关键边:
        vector<int> minadd;
        for(int i = 0; i < n2; i++){
            if(del_set.find(i) != del_set.end()){
                continue;
            }
            if(add_p(n, edges2, edges2[i]) == minres1){
                minadd.push_back(i);
            }
        }
        
        return {mindel, minadd};
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值