算法【最小生成树】

最小生成树:在无向带权图中选择择一些边,在保证联通性的情况下,边的总权值最小。

最小生成树可能不只一棵,只要保证边的总权值最小,就都是正确的最小生成树。

如果无向带权图有n个点,那么最小生成树一定有n-1条边。

扩展:最小生成树一定是最小瓶颈树(题目5)

Kruskal算法(最常用)

1.把所有的边,根据权值从小到大排序,从权值小的边开始考虑。

2.如果连接当前的边不会形成环,就选择当前的边。

3.如果连接当前的边会形成环,就不要当前的边。

4.考察完所有边之后,最小生成树的也就得到了。

证明略,其中,判断是否形成环可以使用并查集,也就是说Kruskal算法并不需要建图。

时间复杂度O(m * log m) + O(n) + O(m)

Prim算法(不算常用)

1.解锁的点的集合叫set(普通集合)、解锁的边的集合叫heap(小根堆)。set和heap都为空。

2.可从任意点开始,开始点加入到set,开始点的所有边加入到heap。

3.从heap中弹出权值最小的边e,查看边e所去往的点x

A.如果x已经在set中,边e舍弃,重复步骤3。

B.如果x不在set中,边e属于最小生成树,把x加入set,重复步骤3。

4.当heap为空,最小生成树的也就得到了。

证明略!

时间复杂度O(n + m) + O(m * log m)

Prim算法的优化(比较难,不感兴趣可以跳过)请一定要对堆很熟悉

1.小根堆里放(节点,到达节点的花费),根据到达节点的花费来组织小根堆。

2.小根堆弹出(u节点,到达u节点的花费y),y累加到总权重上去,然后考察u出发的每一条边

假设,u出发的边,去往v节点,权重w

A.如果v已经弹出过了(发现过),忽略该边。

B.如果v从来没有进入过堆,向堆里加入记录(v, w)。

C. 如果v在堆里,且记录为(v, x)。

1)如果w < x,则记录更新成(v, w),然后调整该记录在堆中的位置(维持小根堆)。

2)如果w >= x,忽略该边。

3.重复步骤2,直到小根堆为空。

时间复杂度O(n+m) + O((m+n) * log n)

下面通过一些题目加深对最小生成树的理解。

题目一

测试链接:https://www.luogu.com.cn/problem/P3366

分析:这个就是一个最小生成树模板代码。代码如下。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int N, M;
int father[200002] = {0};
vector<vector<int>> b;
int find(int i){
    if(i != father[i]){
        father[i] = find(father[i]);
    }
    return father[i];
}
bool Union(int a, int b){
    int behalf_a = find(a);
    int behalf_b = find(b);
    if(behalf_a != behalf_b){
        father[behalf_a] = behalf_b;
        return true;
    }else{
        return false;
    }
}
int main(void){
    int temp1, temp2, temp3;
    int ans = 0;
    int num = 0;
    scanf("%d%d", &N, &M);
    for(int i = 0;i < M;++i){
        vector<int> temp;
        b.push_back(temp);
        scanf("%d%d%d", &temp1, &temp2, &temp3);
        b[i].push_back(temp1);
        b[i].push_back(temp2);
        b[i].push_back(temp3);
    }
    sort(b.begin(), b.end(), [](vector<int> v1, vector<int> v2)->bool{
        return v1[2] < v2[2];
    });
    for(int i = 1;i <= N;++i){
        father[i] = i;
    }
    for(int i = 0;i < M;++i){
        if(Union(b[i][0], b[i][1])){
            ans += b[i][2];
            ++num;
        }
    }
    if(num == N-1){
        printf("%d", ans);
    }else{
        printf("orz");
    }
}

其中,采用并查集可以知道两个节点是否属于同一个集合,故不需要建图。

题目二

测试链接:https://www.luogu.com.cn/problem/P3366

分析:这和题目一一样,不过题目一采用Kruskal算法,题目二采用Prim算法。代码如下。

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
vector<vector<vector<int>>> graph;
vector<bool> visited;
int main(void){
    int N, M, ans = 0;
    int temp1, temp2, temp3;
    int num = 0;
    scanf("%d%d", &N, &M);
    visited.assign(N+1, false);
    vector<vector<int>> temp;
    for(int i = 0;i <= N;++i){
        graph.push_back(temp);
    }
    for(int i = 0;i < M;++i){
        scanf("%d%d%d", &temp1, &temp2, &temp3);
        vector<int> tmp1;
        tmp1.push_back(temp2);
        tmp1.push_back(temp3);
        graph[temp1].push_back(tmp1);
        vector<int> tmp2;
        tmp2.push_back(temp1);
        tmp2.push_back(temp3);
        graph[temp2].push_back(tmp2);
    }
    visited[1] = true;
    ++num;
    auto cmp = [](vector<int> v1, vector<int> v2)->bool{
        return v1[1] > v2[1];
    };
    priority_queue<vector<int>, vector<vector<int>>, decltype(cmp)> q(cmp);
    for(int i = 0;i < graph[1].size();++i){
        q.push(graph[1][i]);
    }
    while (!q.empty())
    {
        int cur = (q.top())[0];
        int weigth = (q.top())[1];
        q.pop();
        if(visited[cur] == false){
            ans += weigth;
            visited[cur] = true;
            ++num;
            for(int i = 0;i < graph[cur].size();++i){
                q.push(graph[cur][i]);
            }
        }
    }
    if(num == N){
        printf("%d", ans);
    }else{
        printf("orz");
    }
}

其中,采用邻接表方式建图;使用优先队列辅助。

题目三

测试链接:https://leetcode.cn/problems/checking-existence-of-edge-length-limited-paths/

分析:可以先将queries数组按limit从小到大排序,将边数组按权值从小到大排序。这样在生成最小生成树的时候,遍历边数组,对于每一个queries,如果边的权值大于limit,则停止,查询queries的两个节点是否在同一个集合,在则为true,不在则为false。代码如下。

cpp 复制代码
class Solution {
public:
    vector<int> father;
    int find(int i){
        if(i != father[i]){
            father[i] = find(father[i]);
        }
        return father[i];
    }
    void Union(int a, int b){
        int behalf_a = find(a);
        int behalf_b = find(b);
        if(behalf_a != behalf_b){
            father[behalf_a] = behalf_b;
        }
    }
    void build(int n){
        for(int i = 0;i < n;++i){
            father[i] = i;
        }
    }
    vector<bool> distanceLimitedPathsExist(int n, vector<vector<int>>& edgeList, vector<vector<int>>& queries) {
        vector<bool> ans;
        father.assign(n, 0);
        sort(edgeList.begin(), edgeList.end(), [](vector<int> v1, vector<int> v2)->bool{
            return v1[2] < v2[2];
        });
        int legnth1 = edgeList.size();
        int length2 = queries.size();
        ans.assign(length2, false);
        for(int i = 0;i < length2;++i){
            queries[i].push_back(i);
        }
        sort(queries.begin(), queries.end(), [](vector<int> v1, vector<int> v2)->bool{
            return v1[2] < v2[2];
        });
        build(n);
        for(int i = 0, j = 0;i < length2;++i){
            for(;j < legnth1 && edgeList[j][2] < queries[i][2];++j){
                Union(edgeList[j][0], edgeList[j][1]);
            }
            ans[queries[i][3]] = (find(queries[i][0]) == find(queries[i][1]));
        }
        return ans;
    }
};

其中,采用Kruskal算法生成最小生成树。

题目四

测试链接:https://www.luogu.com.cn/problem/P2330

分析:题目挺花里胡哨的,其实就是一个最小生成树。代码如下。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n, m;
vector<vector<int>> road;
int father[301] = {0};
int find(int i){
    if(i != father[i]){
        father[i] = find(father[i]);
    }
    return father[i];
}
bool Union(int a, int b){
    int behalf_a = find(a);
    int behalf_b = find(b);
    if(behalf_a != behalf_b){
        father[behalf_a] = behalf_b;
        return true;
    }else{
        return false;
    }
}
int main(void){
    int u, v, c;
    int s = 0, max_score = 0;
    scanf("%d%d", &n, &m);
    for(int i = 0;i < m;++i){
        scanf("%d%d%d", &u, &v, &c);
        vector<int> temp;
        temp.push_back(u);
        temp.push_back(v);
        temp.push_back(c);
        road.push_back(temp);
    }
    for(int i = 1;i <= n;++i){
        father[i] = i;
    }
    sort(road.begin(), road.end(), [](vector<int> v1, vector<int> v2)->bool{
        return v1[2] < v2[2];
    });
    for(int i = 0;i < m;++i){
        if(Union(road[i][0], road[i][1])){
            ++s;
            max_score = road[i][2];
        }
    }
    printf("%d %d", s, max_score);
}

其中,主体和题目一差不多,只是求的东西不一样。

相关推荐
逝去的秋风4 分钟前
【代码随想录训练营第42期 Day61打卡 - 图论Part11 - Floyd 算法与A * 算法
算法·图论·floyd 算法·a -star算法
zero_one_Machel13 分钟前
leetcode73矩阵置零
算法·leetcode·矩阵
青椒大仙KI111 小时前
24/9/19 算法笔记 kaggle BankChurn数据分类
笔记·算法·分类
^^为欢几何^^1 小时前
lodash中_.difference如何过滤数组
javascript·数据结构·算法
豆浩宇1 小时前
Halcon OCR检测 免训练版
c++·人工智能·opencv·算法·计算机视觉·ocr
浅念同学1 小时前
算法.图论-并查集上
java·算法·图论
何不遗憾呢1 小时前
每日刷题(算法)
算法
立志成为coding大牛的菜鸟.1 小时前
力扣1143-最长公共子序列(Java详细题解)
java·算法·leetcode
鱼跃鹰飞1 小时前
Leetcode面试经典150题-130.被围绕的区域
java·算法·leetcode·面试·职场和发展·深度优先
liangbm31 小时前
数学建模笔记——动态规划
笔记·python·算法·数学建模·动态规划·背包问题·优化问题