最小生成树MST
Prim算法,以顶点为单元,与图中边数无关,比较适合于稠密图
Kruskal算法,以边为顶点,时间主要取决于边数,适合稀疏图
下面代码的来源
采用的是曼哈顿距离。
class Solution {
public:
int prim(vector<vector<int> >& points, int start) {
int n = points.size();
int res = 0;
// 1. 将points转化成邻接矩阵, 这一步可有可无
vector<vector<int> > g(n, vector<int>(n));
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
int dist = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
g[i][j] = dist;
g[j][i] = dist;
}
}
// 记录V[i]到Vnew的最近距离
vector<int> lowcost(n, INT_MAX);
// 记录V[i]是否加入到了Vnew
vector<int> v(n, -1);
// 2. 先将start加入到Vnew
v[start] = 0;
for (int i = 0; i < n; i++) {
if (i == start) continue;
lowcost[i] = g[i][start];
}
// 3. 剩余n - 1个节点未加入到Vnew,遍历
for (int i = 1; i < n; i++) {
// 找出此时V中,离Vnew最近的点
int minIdx = -1;
int minVal = INT_MAX;
for (int j = 0; j < n; j++) {
if (v[j] == 0) continue;
if (lowcost[j] < minVal) {
minIdx = j;
minVal = lowcost[j];
}
}
// 将该点加入Vnew,更新lowcost和v
res += minVal;
v[minIdx] = 0;
lowcost[minIdx] = -1;
// 更新集合V中所有点的lowcost
for (int j = 0; j < n; j++) {
if (v[j] == -1 && g[j][minIdx] < lowcost[j]) {
lowcost[j] = g[j][minIdx];
}
}
}
return res;
}
int minCostConnectPoints(vector<vector<int>>& points) {
return prim(points,0);
}
};
楼上是O(n^2)
再补充一个堆优化的,O(mlogn),m为边的数目
class Solution {
public:
int prim(vector<vector<int> >& points, int start) {
int n = points.size();
if (n == 0) return 0;
int res = 0;
// 将points转化成邻接表
vector<vector<int> > g(n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j) continue;
g[i].push_back(j);
g[j].push_back(i);
}
}
// 记录V[i]到Vnew的最近距离
vector<int> lowcost(n, INT_MAX);
// 记录V[i]是否加入到了Vnew
vector<int> v(n, -1);
// 格式:<距离, 下标>
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<> > pq;
pq.push(make_pair(0, start));
while (!pq.empty()) {
auto [dist, i] = pq.top();
pq.pop();
if (v[i] == 0) continue;
v[i] = 0;
res += dist;
for (int k = 0; k < g[i].size(); k++) {
int j = g[i][k];
int w = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
if (v[j] == -1 && lowcost[j] > w) {
lowcost[j] = w;
pq.push(make_pair(w, j));
}
}
}
return res;
}
int minCostConnectPoints(vector<vector<int>>& points) {
return prim(points, 0);
}
};
依次选择最小的边,看是否形成环
时间复杂度O(m log(m) + m α(m) )
class Djset {
public:
vector<int> parent; // 记录节点的根
vector<int> rank; // 记录根节点的深度(用于优化)
vector<int> size; // 记录每个连通分量的节点个数
vector<int> len; // 记录每个连通分量里的所有边长度
int num; // 记录节点个数
Djset(int n): parent(n), rank(n), len(n, 0), size(n, 1), num(n) {
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
int find(int x) {
// 压缩方式:直接指向根节点
if (x != parent[x]) {
parent[x] = find(parent[x]);
}
return parent[x];
}
int merge(int x, int y, int length) {
int rootx = find(x);
int rooty = find(y);
if (rootx != rooty) {
if (rank[rootx] < rank[rooty]) {
swap(rootx, rooty);
}
parent[rooty] = rootx;
if (rank[rootx] == rank[rooty]) rank[rootx] += 1;
// rooty的父节点设置为rootx,同时将rooty的节点数和边长度累加到rootx,
size[rootx] += size[rooty];
len[rootx] += len[rooty] + length;
// 如果某个连通分量的节点数 包含了所有节点,直接返回边长度
if (size[rootx] == num) return len[rootx];
}
return -1;
}
};
struct Edge {
int start; // 顶点1
int end; // 顶点2
int len; // 长度
};
class Solution {
public:
int minCostConnectPoints(vector<vector<int>>& points) {
int res = 0;
int n = points.size();
Djset ds(n);
vector<Edge> edges;
// 建立点-边式数据结构
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
Edge edge = {i, j, abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1])};
edges.emplace_back(edge);
}
}
// 按边长度排序
sort(edges.begin(), edges.end(), [](const auto& a, const auto& b) {
return a.len < b.len;
});
// 连通分量合并
for (auto& e : edges) {
res = ds.merge(e.start, e.end, e.len);
if (res != -1) return res;
}
return 0;
}
};