最小生成树

最小生成树

生成树:所有点都连成一个连通块

合并证明:(反证法)
假设当前这条边不选的情况下的最优解得到了生成树, 那么加上这条边后一定会出现一个环, 并且环上一定存在比当前边更大的边, 所以把当前边替换这个更大的边结果一定不会变差

prim

  1. 朴素 O(n^2)邻接矩阵
  2. 堆优化

从某一个点出发 每次加入连通块和外界的最小边

稀疏图用
kruskal O(mlogn)-按三元组直接存边(a,b,w)
1. 按边的权重排序
2. 从边权小的往大的遍历
3. 并查集维护连通块
4. 对边去讨论,只把边两边点不在同一连通块的加入最小生成树

prim
从一个点s开始 
逐步把所有点和s连通起来
每次连通时 选择当前这个点所在的连通块和外界连的边里选择最短的一条边加入到当前的连通块中
选实心点是离现在这个连通块距离最小的点
总共扩展n-1次即可以把所有边加进来

反证法:

假设最终没选当前最短的边,则最终生成的树一定选了其他一条外接路径(而其他路径一定长度比当前最短的一条外接边长)与该点相连则生成的树的所有边之和一定>包括这条边的树

kruskal(基于并查集)
先把所有边从小到大排序

每次从小到大枚举所有边 假设有一个边 o-o 则边左边的点一定在一个连通块里, 右边这个点也在一个连通块里

对当前这个边分情况讨论:

  1. 当前边的两个点已经连通了 那就没有必要加进这条边了
  2. 当前边的两个点不连通 那么就把当前这个边加入最小生成树里

证明:

    最优解里当前两个点不连通的话 一定是可以把当前这条边加进来且得到最优解的
    o-o 当前边w1
    | |
    o-o 排序更大的边w2

反证法:

    假如最优解中没有这条边的话(即当前边的两个点不连通时不把这两个点加入连通块)  
    同时在考虑前面所有边去连到一个连通块的时候这两个点都是不连通的  
    由于我们是按照权重排序这些边的,则我们会在之后权重更大的边和当前边构成的环中  
    通过权重更大的边去把当前两个点加入连通块  

逻辑:
1. 权重小的边→权重大的边
2. 遍历到当前边w1如果不把两个点加入同一连通块
3. 我们最终生成的树是包含所有点的连通块
4. 此时 我们不妨用当前边w1替换w2 可以发现依然是一个生成树,且总权重不变大 与假设相悖


代码:

prim:

#include
#include
#include

using namespace std;

const int N=110;
int n;
int w[N][N];
int dist[N];//dist存的是当前连通块和外面的每个点直接相连的边的长度的最小值
bool st[N];//当前这个点是否在连通块内

int prim()
{
    int res = 0;
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n; i ++ )//循环n次 每次循环连通块通过最小边加入一个点
    {
        //第2及2次以上的循环 最小边所连的点 也是初始化为-1
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            //j不在连通块内 如果当前还没设置连通块 与外接的最小边(t==-1) || 与j的距离比当前点最小距离的点t还小
            if (!st[j] && (t == -1 || dist[t] > dist[j]))//因为dist[1] = 0 则1肯定作为第一个点加入连通块
                t = j;
        res += dist[t];
        st[t] = true;
        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], w[t][j]);
    }
    return res;
}
int main()
{
    cin >> n;
    //输入邻接矩阵
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            cin >> w[i][j];

    cout << prim() << endl;

    return 0;

}

kruskal:

#include 
#include 
using namespace std;
const int N = 110,M = 10010;
struct Edge {
    int a,b,w;
}edges[M];
int n,cnt = 0;
int p[N];
int find (int x) {
    if (p[x] != x) p[x] = find (p[x]);
    return p[x];
}
int kruskal () {
    sort (edges+1,edges+1+cnt,[](Edge a,Edge b) {
        return a.w < b.w;
    });
    int ans = 0;
    for (int i = 1;i <= cnt;i++) {
        auto [a,b,w] = edges[i];
        a = find (a),b = find (b);
        if (a != b) {
            p[a] = b;
            ans += w;
        }
    }
    return ans;
}
int main () {
    cin >> n;
    for (int i = 1;i <= n;i++) p[i] = i;
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j <= n;j++) {
            int x;
            cin >> x;
            edges[++cnt] = {i,j,x};
        }
    }
    cout << kruskal () << endl;
    return 0;
}

你可能感兴趣的:(算法,图论,数据结构)