最小生成树

最小生成树

问题描述

最小生成树算法用于在带权无向图中找到一棵权值和最小的生成树,包括Kruskal、Prim等算法。

Kruskal算法

算法思想

  1. 基于并查集实现
  2. 按边权值排序
  3. 适用于稀疏图
  4. 时间复杂度

代码实现

class Solution {
public:
    vector<vector<int>> kruskal(int n, vector<vector<int>>& edges) {
        vector<int> parent(n);
        vector<vector<int>> mst;
        
        // 初始化并查集
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
        
        // 按权值排序
        sort(edges.begin(), edges.end(), 
             [](const auto& a, const auto& b) {
                 return a[2] < b[2];
             });
        
        // Kruskal算法
        for (const auto& edge : edges) {
            int u = edge[0], v = edge[1];
            if (find(u, parent) != find(v, parent)) {
                unite(u, v, parent);
                mst.push_back({u, v, edge[2]});
            }
        }
        
        return mst;
    }
    
private:
    int find(int x, vector<int>& parent) {
        if (parent[x] != x) {
            parent[x] = find(parent[x], parent);
        }
        return parent[x];
    }
    
    void unite(int x, int y, vector<int>& parent) {
        parent[find(x, parent)] = find(y, parent);
    }
};
class Solution {
    public List<int[]> kruskal(int n, int[][] edges) {
        int[] parent = new int[n];
        List<int[]> mst = new ArrayList<>();
        
        // 初始化并查集
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
        
        // 按权值排序
        Arrays.sort(edges, (a, b) -> a[2] - b[2]);
        
        // Kruskal算法
        for (int[] edge : edges) {
            int u = edge[0], v = edge[1];
            if (find(u, parent) != find(v, parent)) {
                unite(u, v, parent);
                mst.add(new int[]{u, v, edge[2]});
            }
        }
        
        return mst;
    }
    
    private int find(int x, int[] parent) {
        if (parent[x] != x) {
            parent[x] = find(parent[x], parent);
        }
        return parent[x];
    }
    
    private void unite(int x, int y, int[] parent) {
        parent[find(x, parent)] = find(y, parent);
    }
}
class Solution:
    def kruskal(self, n: int, edges: List[List[int]]) -> List[List[int]]:
        parent = list(range(n))
        mst = []
        
        def find(x: int) -> int:
            if parent[x] != x:
                parent[x] = find(parent[x])
            return parent[x]
        
        def unite(x: int, y: int) -> None:
            parent[find(x)] = find(y)
        
        # 按权值排序
        edges.sort(key=lambda x: x[2])
        
        # Kruskal算法
        for u, v, w in edges:
            if find(u) != find(v):
                unite(u, v)
                mst.append([u, v, w])
        
        return mst

Prim算法

算法思想

  1. 基于贪心策略
  2. 从一个顶点开始生长
  3. 适用于稠密图
  4. 时间复杂度

代码实现

class Solution {
public:
    vector<vector<int>> prim(vector<vector<pair<int, int>>>& adj) {
        int n = adj.size();
        vector<bool> visited(n);
        vector<vector<int>> mst;
        
        priority_queue<pair<int, pair<int, int>>, 
                      vector<pair<int, pair<int, int>>>,
                      greater<>> pq;
        pq.push({0, {0, -1}});
        
        while (!pq.empty()) {
            auto [w, p] = pq.top();
            auto [v, u] = p;
            pq.pop();
            
            if (visited[v]) continue;
            visited[v] = true;
            
            if (u != -1) {
                mst.push_back({u, v, w});
            }
            
            for (auto [next, weight] : adj[v]) {
                if (!visited[next]) {
                    pq.push({weight, {next, v}});
                }
            }
        }
        
        return mst;
    }
};
class Solution {
    public List<int[]> prim(List<List<int[]>> adj) {
        int n = adj.size();
        boolean[] visited = new boolean[n];
        List<int[]> mst = new ArrayList<>();
        
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
        pq.offer(new int[]{0, 0, -1});  // weight, vertex, parent
        
        while (!pq.isEmpty()) {
            int[] cur = pq.poll();
            int w = cur[0], v = cur[1], u = cur[2];
            
            if (visited[v]) continue;
            visited[v] = true;
            
            if (u != -1) {
                mst.add(new int[]{u, v, w});
            }
            
            for (int[] edge : adj.get(v)) {
                int next = edge[0], weight = edge[1];
                if (!visited[next]) {
                    pq.offer(new int[]{weight, next, v});
                }
            }
        }
        
        return mst;
    }
}
class Solution:
    def prim(self, adj: List[List[Tuple[int, int]]]) -> List[List[int]]:
        n = len(adj)
        visited = [False] * n
        mst = []
        
        pq = [(0, 0, -1)]  # weight, vertex, parent
        
        while pq:
            w, v, u = heapq.heappop(pq)
            
            if visited[v]:
                continue
            visited[v] = True
            
            if u != -1:
                mst.append([u, v, w])
            
            for next_v, weight in adj[v]:
                if not visited[next_v]:
                    heapq.heappush(pq, (weight, next_v, v))
        
        return mst

时间复杂度分析

算法 时间复杂度 空间复杂度
Kruskal
Prim
Boruvka

应用场景

  1. 网络设计
  2. 电路布线
  3. 集群连接
  4. 管道铺设
  5. 成本优化

注意事项

  1. 图的连通性
  2. 边权非负性
  3. 重边处理
  4. 自环处理
  5. 并查集优化

常见变形

  1. 次小生成树
class Solution {
public:
    int secondMST(int n, vector<vector<int>>& edges) {
        vector<vector<int>> mst = kruskal(n, edges);
        int minCost = 0, secondCost = INT_MAX;
        
        for (const auto& e : mst) {
            minCost += e[2];
        }
        
        // 枚举替换每条MST中的边
        for (int i = 0; i < mst.size(); i++) {
            int oldCost = mst[i][2];
            int minReplace = INT_MAX;
            
            // 尝试用非MST中的边替换
            for (const auto& e : edges) {
                int u = e[0], v = e[1], w = e[2];
                if (w > oldCost && !inMST(mst, u, v)) {
                    minReplace = min(minReplace, w);
                }
            }
            
            if (minReplace != INT_MAX) {
                secondCost = min(secondCost, minCost - oldCost + minReplace);
            }
        }
        
        return secondCost;
    }
};
class Solution {
    public int secondMST(int n, int[][] edges) {
        List<int[]> mst = kruskal(n, edges);
        int minCost = 0, secondCost = Integer.MAX_VALUE;
        
        for (int[] e : mst) {
            minCost += e[2];
        }
        
        // 枚举替换每条MST中的边
        for (int i = 0; i < mst.size(); i++) {
            int oldCost = mst.get(i)[2];
            int minReplace = Integer.MAX_VALUE;
            
            // 尝试用非MST中的边替换
            for (int[] e : edges) {
                int u = e[0], v = e[1], w = e[2];
                if (w > oldCost && !inMST(mst, u, v)) {
                    minReplace = Math.min(minReplace, w);
                }
            }
            
            if (minReplace != Integer.MAX_VALUE) {
                secondCost = Math.min(secondCost, minCost - oldCost + minReplace);
            }
        }
        
        return secondCost;
    }
    
    private boolean inMST(List<int[]> mst, int u, int v) {
        for (int[] e : mst) {
            if ((e[0] == u && e[1] == v) || (e[0] == v && e[1] == u)) {
                return true;
            }
        }
        return false;
    }
}
class Solution:
    def secondMST(self, n: int, edges: List[List[int]]) -> int:
        mst = self.kruskal(n, edges)
        min_cost = sum(e[2] for e in mst)
        second_cost = float('inf')
        
        def inMST(mst: List[List[int]], u: int, v: int) -> bool:
            return any((e[0] == u and e[1] == v) or 
                      (e[0] == v and e[1] == u) for e in mst)
        
        # 枚举替换每条MST中的边
        for i in range(len(mst)):
            old_cost = mst[i][2]
            min_replace = float('inf')
            
            # 尝试用非MST中的边替换
            for u, v, w in edges:
                if w > old_cost and not inMST(mst, u, v):
                    min_replace = min(min_replace, w)
            
            if min_replace != float('inf'):
                second_cost = min(second_cost, min_cost - old_cost + min_replace)
        
        return second_cost
  1. 最小瓶颈路径
class Solution {
public:
    int bottleneckPath(vector<vector<pair<int, int>>>& adj, int start, int end) {
        int n = adj.size();
        vector<bool> visited(n);
        vector<int> maxEdge(n, INT_MAX);
        maxEdge[start] = 0;
        
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
        pq.push({0, start});
        
        while (!pq.empty()) {
            auto [w, v] = pq.top();
            pq.pop();
            
            if (v == end) return w;
            if (visited[v]) continue;
            visited[v] = true;
            
            for (auto [u, weight] : adj[v]) {
                if (!visited[u]) {
                    int newMax = max(w, weight);
                    if (newMax < maxEdge[u]) {
                        maxEdge[u] = newMax;
                        pq.push({newMax, u});
                    }
                }
            }
        }
        
        return -1;
    }
};
class Solution {
    public int bottleneckPath(List<List<int[]>> adj, int start, int end) {
        int n = adj.size();
        boolean[] visited = new boolean[n];
        int[] maxEdge = new int[n];
        Arrays.fill(maxEdge, Integer.MAX_VALUE);
        maxEdge[start] = 0;
        
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
        pq.offer(new int[]{0, start});
        
        while (!pq.isEmpty()) {
            int[] cur = pq.poll();
            int w = cur[0], v = cur[1];
            
            if (v == end) return w;
            if (visited[v]) continue;
            visited[v] = true;
            
            for (int[] edge : adj.get(v)) {
                int u = edge[0], weight = edge[1];
                if (!visited[u]) {
                    int newMax = Math.max(w, weight);
                    if (newMax < maxEdge[u]) {
                        maxEdge[u] = newMax;
                        pq.offer(new int[]{newMax, u});
                    }
                }
            }
        }
        
        return -1;
    }
}
class Solution:
    def bottleneckPath(self, adj: List[List[Tuple[int, int]]], start: int, end: int) -> int:
        n = len(adj)
        visited = [False] * n
        max_edge = [float('inf')] * n
        max_edge[start] = 0
        
        pq = [(0, start)]
        
        while pq:
            w, v = heapq.heappop(pq)
            
            if v == end:
                return w
            if visited[v]:
                continue
            visited[v] = True
            
            for u, weight in adj[v]:
                if not visited[u]:
                    new_max = max(w, weight)
                    if new_max < max_edge[u]:
                        max_edge[u] = new_max
                        heapq.heappush(pq, (new_max, u))
        
        return -1

经典例题

  1. 【模板】最小生成树
  2. 小欧皇
牛客代码笔记-牛栋 文章被收录于专栏

汗牛充栋,学海无涯。<br/> 内含算法知识点讲解,以及牛客题库精选例题。<br/> 学习算法,从牛栋开始。

全部评论

相关推荐

(黑话警告⚠️:hc=岗位数量,&nbsp;mt=导师,&nbsp;ld=直属领导,&nbsp;cr=代码审查)25年1月,我加入了字节某前端团队,并期望能在这里待到秋招并尝试转正。然而,就在上周,ld&nbsp;找我1v1,告诉我,我的能力和团队预期不太匹配,并和我劝退。晴天霹雳吗?肯定是有的。那一刻,脑子里嗡嗡作响,各种情绪翻涌。但冷静下来想想,这几个月,自己在能掌控的范围内,确实有不少地方做得不尽如人意。所以,我想把这段不算成功的经历复盘一下,希望能给同样在努力转正的你提个醒,避开我踩过的坑。一、ld&nbsp;的要求要注意刚进组时,ld就和我聊过转正的事。我当时发问:“咱们这儿有hc&nbsp;吗?”&nbsp;ld没直接回答,只是说:“看能力,能力到了...
牛客上的彭于晏:过来人告诉你,入职后要做的第一件事儿不是说主动找活儿做,你要先学会融入团队,摸清ld的性格,投其所好。然后才是展示你的能力,能力上可以说技术或者业务,以业务能力为主,技术能力为辅。优先保证自己对业务需求的开发保证质量效率,然后再谈技术的问题,不要你觉得啥啥啥不行就想着整体优化了(发现校招生最喜欢干这事儿),我工作快5年了发现搞这种的最后都没啥好的结果,产出没有还引入新的bug,校招或者实习的水平看到的问题别人看不到嘛?为什么别人不去搞?浪费时间还没收益的事儿不要去做,技术上的能力体现在对于一个新需求,在不符合现在业务发展的架构设计上,你能拿出好的技术方案同时能考虑到后续业务发展逐渐将技术架构引入合理的架构,这是一个漫长的过程而不是一次性的
点赞 评论 收藏
分享
迷茫的大四🐶:自信一点,我认为你可以拿到50k,低于50k完全配不上你的能力,兄弟,不要被他们骗了,你可以的
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务