玩命加载中 . . .

图论-并查集/最小生成树


并查集

  • 每个节点的祖宗节点都初始化为自己
  • 要把两个节点连起来,先要查找两个节点的祖宗节点,然后把祖宗节点改成一样就行

要把2节点和4节点连起来,查找2节点的祖宗节点为1,4节点的祖宗节点为3,然后把3改为1就行,后续在查找4的祖宗的过程中会修改其祖宗

具体查找程序如下

int find(int x) {
    while (x != fa[x]) {
        fa[x] = find(fa[x]);    // 找祖宗节点,并用找到的结果更新自己的祖宗
    }
    return fa[x];
}

在查找过程中会将路径上的节点的祖宗都修改成一样,称为路径压缩

时间复杂度:$O(nlogn)$


Kruskal

  • 初始化每个节点所在集合为自己
  • 对边进行排序,从小到大一次选取,如果边所连的两个节点在不同的集合中,就可以选,否则不能选
  • 选中n-1条边即可
const int N = 1005;
struct edge {
    int u, v, w;
    bool operator<(const edge& rhs) const {
        return w < rhs.w;
    }
}e[N*N];    // 边集数组,方便排序
int cnt;
int fa[N];
int n, m;
int u, v, w;
void add(int u, int v, int w) {
    e[cnt].u = u;
    e[cnt].v = v;
    e[cnt].w = w;
    cnt++;
}
int find(int x) {
    if (x != fa[x]) {   // 一直查到祖宗节点为止
        fa[x] = find(fa[x]);    // 查找路径上的节点赋值为祖宗节点
    }
    return fa[x];
}
bool merge(int a, int b) {
    int p = find(a);
    int q = find(b);
    if (p == q) return false;   // 祖宗节点相同说明在同一个集合中
    fa[q] = p;  // 否则就合并两个集合
    return true;
}
int kruskal(int n) {
    int res = 0, cnt = 0;
    for (int i = 0; i < m; i++) {   // 遍历m条边
        if (merge(e[i].u, e[i].v)) {
            res += e[i].w;
            cnt++;
            if (cnt == n - 1) return res;   // 找出n-1条就行
        }
    }
    return -1;
}

int main() {
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        cin >> u >> v >> w;
        add(u, v, w);
    }
    sort(e, e + cnt);
    for (int i = 1; i <= n; i++) fa[i] = i; // 初始化为自己
    cout << kruskal(n) << endl;
    return 0;
}
// 5 6
// 1 2 1
// 1 4 4
// 2 3 2
// 2 5 5
// 3 4 3
// 4 5 6
// 11

时间复杂度:$O(mlogm+nlogn)$


Prim

分为两个集合SV-S,每次从V-S集合中找lowcost值最小的节点index加入S集合中,然后用index节点更新剩下的V-S集合中的其他节点

const int N = 1005;
const int inf = 0x3f3f3f3f;

int G[N][N], closest[N], lowcost[N], res[N];
bool used[N];
int n, m;
int u, v, w;
int prim() {
    int res = 0;
    used[1] = true;
    lowcost[1] = 0;
    for (int i = 2; i <= n; i++) {  // 初始化
        lowcost[i] = G[1][i];   // 用1节点来更新V-S
        closest[i] = 1;     // 用1节点来更新V-S
        used[i] = false;
    }
    for (int i = 1; i < n; i++) {   // 循环n-1次
        int tmp = inf;
        int index = 1;
        for (int j = 1; j <= n; j++) {
            if (!used[j] && lowcost[j] < tmp) {
                index = j;
                tmp = lowcost[j];   // 找出最小的lowcost
            }
        }
        if (index == 1) return -1;  // 不是连通图
        res += lowcost[index];  // 最小权值累加
        used[index] = true;
        for (int j = 1; j <= n; j++) {
            if (!used[j] && G[index][j] < lowcost[j]) {
                lowcost[j] = G[index][j];
                closest[j] = index; // 用index来更新V-S中的节点
            }
        }
    }
    return res;
}

int main() {
    memset(G, 0x3f, sizeof(G));
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        cin >> u >> v >> w;
        G[u][v] = w;
        G[v][u] = w;
    }
    for (int i = 1; i <= n; i++) G[i][i] = 0;
    cout << prim() << endl;
    return 0;
}
// 5 6
// 1 2 1
// 1 4 4
// 2 3 2
// 2 5 5
// 3 4 3
// 4 5 6
// 11
// closest:
// 0 1 2 3 2
// lowcost: 
// 0 1 2 3 5 

时间复杂度:$O(n^2)$

改进

Prim的核心思想和Dijkstra是一样的,每次选取V-S集合中lowcost最小值来更新V-S集合中的其他元素的lowcostDijkstra中也是选取V-S集合中dist最小值来更新V-S集合中的其他元素的dist,所以可以套用Dijkstra的模板

const int N=5005;
const int M=200005;

struct edge {
    int v, w, next;
}e[M<<1];

int head[N], dist[N];
bool inq[N];
int cnt;
void add(int u, int v, int w) {
    e[++cnt].v = v;
    e[cnt].w = w;
    e[cnt].next = head[u];
    head[u] = cnt;
}

int n, m;
int u, v, w;
int Prim() {
    memset(dist, 0x3f, sizeof(dist));
    int cnt = 0, res = 0;
    priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> q;
    q.push({0, 1});
    dist[1] = 0;
    while (!q.empty()) {
        auto x = q.top();
        q.pop();
        int u = x.second;
        if (inq[u]) continue;
        inq[u] = true;
        cnt++;  // 累加入队的节点数
        res += dist[u]; // 累加最小权值
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].v;
            int w = dist[u] + e[i].w;
            if (dist[v] > w) {
                dist[v] = w;
                q.push({w, v});
            }
        }
    }
    if (cnt < n) return -1; // 每个点应该都入队一次,否则就不是连通图
    return res;
}
int main() {
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        cin >> u >> v >> w;
        add(u, v, w);
        add(v, u, w);
    }
    cout << Prim();
    return 0;
}

时间复杂度:$O(mlogn)$


文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录