【算法学习】最小生成树

最小生成树

最小生成树的算法包括Prim算法和Kruskal算法。
在用Kruscal算法的时候会用到并查集。
完整代码可直接翻到最后~
首先咱们先介绍比较容易理解也是最先会想到的一个算法,Prim算法。

Prim

Prim算法的时间复杂度为O(n^2)。
Prim算法的思想就是从图中任意一点选择为始点,然后开始搜索可以从这个点到达其他点的最短距离。其实这个思想和Dijstra算法很相似。
不同之处在于,Dijstra算法计算的是点到点最短距离,Prim算法是把所有点连通起来权值最小。
实质上也就是,Dijstra算法在把某个点加入到集合中时,它的算法仅仅只是把这个点当做一个跳板,内部点与点之间还是存在距离(得到了某个人却没有得到它的心)。而Prim算法则是把这个点合在了一起,也就是在加入某个点之后,就可以把这些点看成一个点,集合内部没有距离(得到某个人并且得到了它的心)。
看例子(从0开始):
在这里插入图片描述
用Dijstra算法
首先0,然后0->1, 最后0->2。
用Prim算法
首先0,然后0->1,最后1->2。
所以这两个算法的区别就是在于0->1这个距离在后续中考虑存在与否。更一般的就是被添加到集合中的点与点之间的距离是否还存在。
好的,请看算法核心代码

void Primnew () {
    int V[N]; //V[] 记录节点的前驱
    int dis[N];//记录当前到各个节点的权值
    //初始化,起点为3,权值取自于矩阵
    for(int i = 0; i < N; ++i) {
        dis[i] = Edge[3][i];
        V[i] = 3;
    }
    V[3] = 0;
    //迭代寻找N-1条边
    for(int i = 0; i < N-1; ++i) {
        int shorter = M;
        int e = 0;
        //找到当前到其他点的 最短距离 和 其节点
        for(int j = 0; j < N; ++j) {
            if (shorter > dis[j] && dis[j] != 0) {
                shorter = dis[j];
                e = j;
            }
        }
        //与Dijstra的不同之处
        //节点找到后,权值更新为0
        dis[e] = 0;
        //加入节点后,更新当前路径
        for(int i = 0; i < N; ++i) {
            if(dis[i] > Edge[e][i]) {
                dis[i] = Edge[e][i];
                V[i] = e;
            }
        }
    }
}

好的,Prim算法结束。接下来就是Kruskal算法。
在介绍Kruskal算法之前,先了解什么是并查集。

并查集

并查集的最简单的定义,朋友的朋友就是朋友。
通过与树结合,也可以这么说,祖辈的祖辈就是祖辈。
看例子:
阿珍和阿强,他们两个人是大学同学。
他们两个人的族谱图(为了简单,只画到父辈)为:
在这里插入图片描述
在一风雨交加晚,电光雷鸣时,阿珍爱上了阿强。在恋爱期间,两个人相约厮守到老。
毕业后见家长,为了避免近亲,两个人在结婚前需要查自家的族谱图。若祖上有关系,也只能是有情人终成兄妹。但是好在最后结果为两人祖上N代毫无干系。
可以结婚。
婚后,阿珍和阿强的族谱图变成了:
在这里插入图片描述
至此,我们已经了解了并查集的概念,和并查集的两个操作。
并查集的概念
把两个毫无关系的集合通过各自集合中的某个点构成某种关系时(比如阿珍和阿强结婚),则表明可以把这两个集合合并成一个集合了(此后阿珍和阿强两家祖辈为一个祖辈了)。
两个操作
查询:查询自己的祖辈,是否是和对方的祖辈相同(婚前需要查族谱,避免近亲)
合并:当祖辈不同时,两个点又构成关系(结婚),就可以把祖辈合二为一
当了解并查集之后,咱们就可以来看Kruskal算法了。

Kruskal

Kruskal算法的时间复杂度为O(n*m)。
Kruskal算法的思想就是每次遍历所有的边,选择权值最小,并且路径两端的节点属于不同的集合的路径。每次遍历都可以筛选出一条路径,并且合并两个集合。所以只需要遍历N-1次,即可完成搜索。
在了解完并查集之后,Kruskal算法其实也就很简单明了。
请看代码:

//寻找集合的祖辈
int found(int x) {
    if(anc[x] == x) return x;
    else  anc[x] = found(anc[x]);
    return anc[x];
}
//合并祖辈
void unite(int x, int y) {
    int px = found(x);
    int py = found(y);
    if(px == py) return;
    anc[px] = py;
}

void Kruskal () {
    for(int k = 0; k < N - 1; ++k) {
        int shorter = M;
        int s, e;
        for(int i = 0; i < N; ++i) {
            for(int j = i; j < N; ++j) {
                if(shorter > Edge[i][j] && found(i) != found(j)) {
                    s = i;
                    e = j;
                    shorter = Edge[i][j];
                }
            }
        }
        unite(s, e);
    }
}

Prim完整代码

#include <iostream>
#include <vector>

using namespace std;

#define N 7
#define M 999999

char Ch[N] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int Edge[N][N] = {0, 7, M, 5, M, M, M,
                  7, 0, 8, 9, 7, M, M,
                  M, 8, 0, M, 5, M, M,
                  5, 9, M, 0, 15, 6, M,
                  M, 7, 5, 15, 0, 8, 9,
                  M, M, M, 6, 8, 0, 11,
                  M, M, M, M, 9, 11, 0};

struct Edg {
    char start, end;
    int weight;
};

void Prim () {
    int V[N]; //V[] 记录节点的前驱
    Edg E[N-1];
    int dis[N];//记录当前到各个节点的权值
    //初始化,起点为3,权值取自于矩阵
    for(int i = 0; i < N; ++i) {
        dis[i] = Edge[3][i];
        V[i] = 3;
    }
    V[3] = 0;
    //迭代寻找N-1条边
    for(int i = 0; i < N-1; ++i) {
        int shorter = M;
        int e = 0;
        //找到当前到其他点的 最短距离 和 其节点
        for(int j = 0; j < N; ++j) {
            if (shorter > dis[j] && dis[j] != 0) {
                shorter = dis[j];
                e = j;
            }
        }
        //节点找到后,权值更新为0
        dis[e] = 0;
        //记录树的父子以及权值
        E[i].start = Ch[V[e]];
        E[i].end = Ch[e];
        E[i].weight = shorter;
        //加入节点后,更新当前路径
        for(int i = 0; i < N; ++i) {
            if(dis[i] > Edge[e][i]) {
                dis[i] = Edge[e][i];
                V[i] = e;
            }
        }
    }
    for(int i = 0; i < N-1; ++i) {
        cout << E[i].start << " -> " << E[i].end << " = "<< E[i].weight << endl;
    }
}

int main() {
    for(int i = 0; i < N; ++i) {
        for(int j = 0; j < N; ++j)
            cout << Edge[i][j] << "\t";
        cout << "\n";
    }
    Prim();
    return 0;
}

Kruskal完整代码

#include <iostream>
#include <vector>

using namespace std;

#define N 7
#define M 999999

char Ch[N] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int Edge[N][N] = {0, 7, M, 5, M, M, M,
                  7, 0, 8, 9, 7, M, M,
                  M, 8, 0, M, 5, M, M,
                  5, 9, M, 0, 15, 6, M,
                  M, 7, 5, 15, 0, 8, 9,
                  M, M, M, 6, 8, 0, 11,
                  M, M, M, M, 9, 11, 0};

struct Edg {
    char start, end;
    int weight;
};

int anc[N] = {0, 1, 2, 3, 4, 5, 6};

int found(int x) {
    if(anc[x] == x) return x;
    else  anc[x] = found(anc[x]);
    return anc[x];
}

void unite(int x, int y) {
    int px = found(x);
    int py = found(y);
    if(px == py) return;
    anc[px] = py;
}

void Kruskal () {
    Edg E[N];
    for(int k = 0; k < N - 1; ++k) {
        int shorter = M;
        int s, e;
        for(int i = 0; i < N; ++i) {
            for(int j = i; j < N; ++j) {
                if(shorter > Edge[i][j] && found(i) != found(j)) {
                    s = i;
                    e = j;
                    shorter = Edge[i][j];
                }
            }
        }
        unite(s, e);
        E[k].start = Ch[s];
        E[k].end = Ch[e];
        E[k].weight = shorter;
    }
    for(int i = 0; i < N-1; ++i) {
        cout << E[i].start << " -> " << E[i].end << " = "<< E[i].weight << endl;
    }
}

int main() {
    for(int i = 0; i < N; ++i) {
        for(int j = 0; j < N; ++j)
            cout << Edge[i][j] << "\t";
        cout << "\n";
    }
    Kruskal();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值