返回

敏捷探索最小高度树:LeetCode 310 解题指南

前端

前言

在计算机科学的世界中,树形结构无处不在,从文件系统到网络拓扑,它们都扮演着至关重要的角色。本文将深入探讨 LeetCode 上的 310. 最小高度树,探索如何找出给定加权无向图中高度最小的树。

问题陈述

给定一个加权无向图,其中顶点由数字标识,并且每个边都有一个权重。目标是找到一棵高度最小的树,其中树的高度定义为根节点到最远叶子节点的最长路径长度。

解题方法

克鲁斯卡尔算法

克鲁斯卡尔算法是一种贪心算法,用于求解加权无向图中的最小生成树。我们可以使用它来解决 310. 最小高度树,因为最小高度树实际上就是加权无向图中的最小生成树。

算法的步骤如下:

  1. 将图中的所有顶点初始化为单独的连通分量。
  2. 从所有边中选择一条权重最小的边。
  3. 如果选择的边连接了两个不同的连通分量,则将这两个连通分量合并为一个连通分量,并将该边添加到最小生成树中。
  4. 重复步骤 2 和 3,直到所有的顶点都连接在一个连通分量中。

Prim 算法

Prim 算法也是一种贪心算法,用于求解加权无向图中的最小生成树。与克鲁斯卡尔算法不同,Prim 算法从一个顶点开始,逐步扩展最小生成树,直到所有的顶点都包括在内。

算法的步骤如下:

  1. 从图中任意选择一个顶点作为根节点。
  2. 将根节点添加到最小生成树中。
  3. 从根节点开始,为每个尚未添加到最小生成树中的顶点找到权重最小的边。
  4. 如果边连接了未添加到最小生成树中的顶点,则将该边和顶点添加到最小生成树中。
  5. 重复步骤 3 和 4,直到所有的顶点都添加到最小生成树中。

代码示例

以下是使用克鲁斯卡尔算法解决 LeetCode 310. 最小高度树的 C++ 代码示例:

#include <vector>
#include <algorithm>
using namespace std;

struct Edge {
    int from, to, weight;
};

bool compareEdges(const Edge& a, const Edge& b) {
    return a.weight < b.weight;
}

class DSU {
public:
    vector<int> parent, rank;

    DSU(int n) {
        parent.resize(n);
        rank.resize(n, 0);
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            if (rank[rootX] > rank[rootY]) {
                parent[rootY] = rootX;
            } else if (rank[rootX] < rank[rootY]) {
                parent[rootX] = rootY;
            } else {
                parent[rootY] = rootX;
                rank[rootX]++;
            }
        }
    }
};

vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
    // 1. Sort edges by weight
    vector<Edge> edges_sorted;
    for (auto& edge : edges) {
        edges_sorted.push_back({edge[0], edge[1], edge[2]});
    }
    sort(edges_sorted.begin(), edges_sorted.end(), compareEdges);

    // 2. Create DSU
    DSU dsu(n);

    // 3. Process edges
    vector<int> minHeightTrees;
    for (auto& edge : edges_sorted) {
        int rootX = dsu.find(edge.from);
        int rootY = dsu.find(edge.to);
        if (rootX != rootY) {
            dsu.union(rootX, rootY);
            if (minHeightTrees.empty()) {
                minHeightTrees.push_back(edge.from);
                minHeightTrees.push_back(edge.to);
            } else {
                minHeightTrees.clear();
                minHeightTrees.push_back(edge.from);
                minHeightTrees.push_back(edge.to);
            }
        }
    }

    return minHeightTrees;
}

复杂度分析

克鲁斯卡尔算法和 Prim 算法的时间复杂度都是 O(E log V),其中 E 是图中的边数,V 是顶点数。在 310. 最小高度树中,图是一棵树,所以 E = V - 1,因此算法的时间复杂度为 O(V log V)。

结论

本文详细介绍了如何使用克鲁斯卡尔算法和 Prim 算法解决 LeetCode 310. 最小高度树。这些算法提供了高效的方法来求解加权无向图中的最小生成树,从而找出最小高度树。理解这些算法对于解决图论问题至关重要,并且它们在计算机科学的各个领域都有广泛的应用。