zoukankan      html  css  js  c++  java
  • 拓扑排序 详解 + 并查集 详解 + 最小生成树(MST)详解 【普利姆算法 + 优先队列优化 & 克鲁斯卡尔算法】

                   本人QQ :2319411771   邮箱 : cyb19950118@163.com

         若您发现本文有什么错误,请联系我,我会及时改正的,谢谢您的合作!

         本文为原创文章,转载请注明出处 

         本文链接   :http://www.cnblogs.com/Yan-C/p/3943940.html

        哎呀,好久了啊,想写这篇博文好久了,但是因为懒的原因 一直迟迟没动手啊。

        今天,终于在长久的懒惰下,突然来了那么一点热度。把这篇博文写一下。

    本文分为以下几个部分 :

    1、  拓扑排序

    2、  并查集

    3、  普利姆算法  &  优先队列优化

    4、  克鲁斯卡尔算法

     前情提要 :  本文的存图方式 只有两种 :  邻接矩阵 or  前向星。

    1、 拓扑排序

      我们起床穿裤子和鞋子时,相信大部分人的顺序是这样的,先穿上内裤,然后再穿上裤子,再穿上袜子,然后才是鞋子。  那么,我们把这些步骤分解:

    (1)穿内裤

    (2)穿裤子

    (3)穿袜子

    (4)穿鞋子

    我们把这四个步骤,按照上述的顺序 给排一下,这就是所谓的拓扑排序

    当然这个排序的顺序是唯一的,如果你先进行(2)然后(1)(3)(4),哦,不,你不是超人,请不要这样做, 又假如你按照(1)(2)(4)(3), 那显然也是不行的。

    拓扑排序 也可以描述一个暑假写作业的过程 : 语文作业,数学作业,英语作业,生物作业,化学作业,物理作业。

    (1)  语文

    (2)  数学

    (3)  英语

    (4)  生物

    (5)  化学

    (6)  物理

    你可以是(1)(2)(3)(4)(5)(6),也可以是(6)(5)(4)(3)(2)(1),再者英语老师比较凶,那么可以是(3)(1)(2)(4)(5)(6)。等等其他的排序方式。

    那么这个排序又是不唯一的。

    因此  拓扑排序可能是唯一的又有可能是不唯一的。

    就像 3个篮球队进行比赛。  编号分别为 1  , 2 , 3。

    1打赢了2

    2打赢了3

    3打赢了1。 问谁是最后的冠军。 各一胜一负你问我谁是冠军 ,这不是扯蛋嘛。 So,这是不能判断谁是冠军的,  因为这个事件存在一个 环,互相牵制,进行排序是不行产生结果的。

    如果这样  :

    1打赢了2

    3打赢了2

    那么最后的冠军可能是不确定的,因为你不知道1和3 谁强。 所以只能是 1,3并列了,你如果喜欢大数在前 那就是3 1 2,反之,就是1 3 2了。

    拓扑排序其实就是这个样子。

    前面大篇幅的扯犊子,主要是介绍什么是拓扑排序。 那么我们要讨论一下,怎么样进行拓扑排序呢?  哎,这个问题好!

    插播 :

    我们再次的从 1  2  3  这三支队伍的冠军争夺赛说起。

    1打赢了2  因为2输了一场比赛,所以要给2做一标记。因此2号的菊花上就出现了一杆长枪。 我们称这个标记为 入度 那么2的入度就是 1了。

    3打赢了2   因为2又输了一场比赛,又是一杆长枪啊。为什么受伤的总是2。  那么2的入度 就++了  变成了2。

    好了  这就是 什么是  入度  了。  如果你还不是很懂入度是什么。那我告诉你,入度 在这里就是2号被打败了几次

    那我们 就要 进入正题了。

    拓扑排序 :

      由AOV网构造拓扑序列的拓扑排序算法主要是循环执行以下两步,直到不存在入度为0的顶点为止。

      (1) 选择一个入度为0的顶点并输出之;
      (2) 从网中删除此顶点及所有出边。

      循环结束后,若输出的顶点数小于网中的顶点数,则输出“有回路”信息,否则输出的顶点序列就是一种拓扑序列。 (摘自 : 百度百科)

     我们继续 以题来进行讲解和理解的加深。

     1 Description
     2 有N个比赛队(1<=N<=500),编号依次为1,23,。。。。,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得每个队的比赛成绩,只知道每场比赛的结果,即P1赢P2,用P1,P2表示,排名时P1在P2之前。现在请你编程序确定排名。
     3  
     4 
     5 Input
     6 输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示队伍的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即P1队赢了P2队。
     7  
     8 
     9 Output
    10 给出一个符合要求的排名。输出时队伍号之间有空格,最后一名后面没有空格。
    11 
    12 其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。
    13  
    14 
    15 Sample Input
    16 
    17 4 3 
    18 1 2 
    19 2 3 
    20 4 3
    21 
    22  
    23 
    24 Sample Output
    25 
    26 1 2 4 3
    题目在这

    题目链接:在这

    因为数据较小,我们可以使用邻接矩阵进行存储。  这是第一种方法。

    题解在这 :

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <iostream>
     4 #include <algorithm>
     5 #include <queue>
     6 #define LL long long
     7 
     8 const int INF = 1e9+7;
     9 const int VM = 503;// 点的个数
    10 
    11 bool G[VM][VM];//
    12 int deg[VM];//各个顶点的入度  计数
    13 
    14 void toposort(int n) {//拓扑排序
    15     int k = 0;
    16 
    17     for (int i = 1; i <= n; i++) {//共进行|G.V|次操作
    18         for (int j = 1; j <= n; j++) {//遍历所有的顶点  找入度为0的
    19             if (deg[j] == 0) {//找到
    20                 printf("%d%c", j, i == n ? '
    ' : ' ');//输出
    21                 deg[j]--;//去掉这个点  让deg[j] = -1;
    22                 k = j;//记录这个点
    23                 break;//跳出循环
    24             }
    25         }
    26         for (int j = 1; j <= n; j++)//遍历所有的点
    27             if (G[k][j] == true) {//找被此点打败过的点
    28                 G[k][j] = false;//标记为找到过
    29                 deg[j]--;//让这个点的入度-1
    30             }
    31     }
    32 }
    33 
    34 int main() {
    35     int n, m;
    36 
    37     while (scanf("%d %d", &n, &m) == 2) {//多组输入, 获取n, m
    38         memset(G, 0, sizeof(G));//初始化
    39         memset(deg, 0, sizeof(deg));//初始化
    40         while (m--) {
    41             int u, v;
    42             scanf("%d %d", &u, &v);//获取 u,v  u打败过v
    43             if (G[u][v] == false) {//防止重边 如果被同一个对手打败多次,也太伤v的心了
    44                 G[u][v] = true;//标记为真
    45                 deg[v]++;//v的入度++   一杆长枪入洞了。
    46             }
    47         }
    48         toposort(n);//调用函数
    49     }
    50     return 0;
    51 }
    我是题解 1 号

      主函数 对数据的获取 和存图。

     1 int main() {
     2     int n, m;
     3 
     4     while (scanf("%d %d", &n, &m) == 2) {//多组输入, 获取n, m
     5         memset(G, 0, sizeof(G));//初始化
     6         memset(deg, 0, sizeof(deg));//初始化
     7         while (m--) {
     8             int u, v;
     9             scanf("%d %d", &u, &v);//获取 u,v  u打败过v
    10             if (G[u][v] == false) {//防止重边 如果被同一个对手打败多次,也太伤v的心了
    11                 G[u][v] = true;//标记为真
    12                 deg[v]++;//v的入度++   一杆长枪入洞了。
    13             }
    14         }
    15         toposort(n);//调用函数
    16     }
    17     return 0;
    18 }

     拓扑排序的函数 :

     1 void toposort(int n) {//拓扑排序
     2     int k = 0;
     3 
     4     for (int i = 1; i <= n; i++) {//共进行|G.V|次操作
     5         for (int j = 1; j <= n; j++) {//遍历所有的顶点  找入度为0的
     6             if (deg[j] == 0) {//找到
     7                 printf("%d%c", j, i == n ? '
    ' : ' ');//输出
     8                 deg[j]--;//去掉这个点  让deg[j] = -1;
     9                 k = j;//记录这个点
    10                 break;//跳出循环
    11             }
    12         }
    13         for (int j = 1; j <= n; j++)//遍历所有的点
    14             if (G[k][j] == true) {//找被此点打败过的点
    15                 G[k][j] = false;//标记为找到过
    16                 deg[j]--;//让这个点的入度-1
    17             }
    18     }
    19 }


    此算法的时间复杂度为 O(n * n)  复杂度挺高的呢。

    那我们要想办法优化啊。

    来了 , 第二种  时间复杂度为 O(V + E)  在这个算法中 我们用到了 前向星  和  优先队列。

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <iostream>
     4 #include <algorithm>
     5 #include <queue>
     6 #define LL long long
     7 
     8 using namespace std;
     9 
    10 const int INF = 1e9+7;
    11 const int VM = 503;// 点的个数
    12 
    13 struct node {//前向星的结构体
    14     int v;//输队编号
    15     int next;
    16 };
    17 node edge[VM * 4];//结构体数组
    18 int head[VM];//头指针数组
    19 int cnt;//下标
    20 int deg[VM];//入度数组
    21 
    22 void toposort(int n) {
    23     priority_queue<int, vector<int>, greater<int> > que;//优先队列
    24 
    25     for (int i = 1; i <= n; i++)//找所有点
    26         if (deg[i] == 0) {//入度为 0
    27             que.push(i);//加入队列
    28             deg[i]--;//入度 变为 -1
    29         }
    30     int k = 1;
    31     while (que.empty() == false) {//队列不为空
    32         int u = que.top();//取出队首的数
    33         que.pop();//删除
    34         printf("%d%c", u, k++ == n ? '
    ' : ' ');//输出
    35         for (int i = head[u]; i != -1; i = edge[i].next) {//与该点相连的
    36             node e = edge[i];//便于书写
    37             deg[e.v]--;//点的入度 -1 
    38             if (deg[e.v] == 0)//若此点的 入度为 0
    39                 que.push(e.v);//放入队列
    40         }
    41     }
    42 }
    43 
    44 int main() {
    45     int n, m;
    46     int i;
    47 
    48     while (scanf("%d %d", &n, &m) == 2) {//多组输入 ,获取n,m
    49         memset(head, -1, sizeof(head));//初始化
    50         memset(deg, 0, sizeof(deg));//初始化
    51         cnt = 0;//初始化
    52         while (m--) {
    53             int u, v;
    54             scanf("%d %d", &u, &v);//获取u,v
    55             for (i = head[u]; i != -1; i = edge[i].next)//查找重边
    56                 if (edge[i].v == v)//输入重复数据
    57                     break;//不再储存
    58             if (i == -1) {//若不是重复数据
    59                 deg[v]++;//加边
    60                 edge[cnt].v = v;
    61                 edge[cnt].next = head[u];
    62                 head[u] = cnt++;
    63             }
    64         }
    65         toposort(n);//调用函数
    66     }
    67     return 0;
    68 }
    我是题解二号


    所用到的数据结构 :

    1 priority_queue<int, vector<int>, greater<int> > que;//优先队列
    2 struct node {//前向星的结构体
    3     int v;//输队编号
    4     int next;
    5 };
    6 node edge[VM * 4];//结构体数组
    7 int head[VM];//头指针数组
    8 int cnt;//下标

    主函数对数据的获取和 图的存储

     1 int main() {
     2     int n, m;
     3     int i;
     4 
     5     while (scanf("%d %d", &n, &m) == 2) {//多组输入 ,获取n,m
     6         memset(head, -1, sizeof(head));//初始化
     7         memset(deg, 0, sizeof(deg));//初始化
     8         cnt = 0;//初始化
     9         while (m--) {
    10             int u, v;
    11             scanf("%d %d", &u, &v);//获取u,v
    12             for (i = head[u]; i != -1; i = edge[i].next)//查找重边
    13                 if (edge[i].v == v)//输入重复数据
    14                     break;//不再储存
    15             if (i == -1) {//若不是重复数据
    16                 deg[v]++;//加边
    17                 edge[cnt].v = v;
    18                 edge[cnt].next = head[u];
    19                 head[u] = cnt++;
    20             }
    21         }
    22         toposort(n);//调用函数
    23     }
    24     return 0;
    25 }


    拓扑排序函数

     1 void toposort(int n) {
     2     priority_queue<int, vector<int>, greater<int> > que;//优先队列
     3 
     4     for (int i = 1; i <= n; i++)//找所有点
     5         if (deg[i] == 0) {//入度为 0
     6             que.push(i);//加入队列
     7             deg[i]--;//入度 变为 -1
     8         }
     9     int k = 1;
    10     while (que.empty() == false) {//队列不为空
    11         int u = que.top();//取出队首的数
    12         que.pop();//删除
    13         printf("%d%c", u, k++ == n ? '
    ' : ' ');//输出
    14         for (int i = head[u]; i != -1; i = edge[i].next) {//与该点相连的
    15             node e = edge[i];//便于书写
    16             deg[e.v]--;//点的入度 -1 
    17             if (deg[e.v] == 0)//若此点的 入度为 0
    18                 que.push(e.v);//放入队列
    19         }
    20     }
    21 }

    拓扑排序 讲解 完毕。

    2、并查集

        并查集从字面上最起码可以看出是一个集合,而且是能并(合并吗?) 能查的集合。集合也就是分组,一组一组的数据,这一组就是一个集合嘛。

       并查集是一种用来管理元素分组情况的数据结构。  并查集,并查集,那么他的功能肯定就是  并  和 查。

       他可以高效的进行 :

     并  合并元素a和元素b所在的组。

     查   查询元素a和元素b是否属于同一组。

       并查集可以进行合并 但是却不能进行分割。

       并查集的结构 是 树形结构,但是他却不是二叉树,因为是树,所以必定有根节点,根节点就是这个集合,这个分组中最大的统领着

       对于并查集呢,主要是有两部分函数构成, 一个是union()函数 也就是我们所说的并(合并),另一个是find()函数  也就是所说的查函数。

       对于并查集不会画图真的是好纠结。

       对于并查集,大家看这个大牛的博客的讲解吧,  如果大家不想看的话,可以直接看下面的代码讲解,注释还是很清晰的。

      http://www.cnblogs.com/cyjb/p/UnionFindSets.html

       看完讲解 大家可以看一下这些题目加深一下。(脑子有点乱乱的,原谅我的“乱来”)。

    我们对并查集的初始化

    1 for (int i = 1; i <= n; i++)
    2             par[i] = i;//这是初始化

    这是find() 函数 

    1 int find(int x) {//查找函数
    2     if (par[x] == x)//若本身就是根节点 ,那么return
    3         return x;
    4     return find(par[x]);//不是的话,继续查找
    5 }

    这是合并函数

    1 void unite(int x, int y) {//合并函数
    2     x = find(x);//查找x的根节点
    3     y = find(y);//查找y的根节点
    4     if (x == y)//若这两个数的结点是一样的,那么他们本来就是一个分组里的了,所以没有操作
    5         return ;
    6     par[x] = y;//不然的话,就让其中的一个点成为另一个点的根节点。
    7 }

    还有一个判断的 same函数

    1 bool same(int x, int y) {
    2     return find(x) == find(y);
    3 }

    same函数 下面的题解中都是 直接判断的,所以就把same这个函数直接放在了里面,就这样 same 函数 被我隐藏了。

    这上面的  查找函数和合并函数 都是未经优化的,是比较原始的,下面我们用它做一道题。

    题目链接在这   我就是题目链接

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <iostream>
     4 #include <algorithm>
     5 #include <queue>
     6 #define LL long long
     7 
     8 using namespace std;
     9 
    10 const int INF = 1e9+7;
    11 const int VM = 100003;
    12 
    13 int par[VM];
    14 
    15 int find(int x) {//查找函数
    16     if (par[x] == x)//若本身就是根节点 ,那么return
    17         return x;
    18     return find(par[x]);//不是的话,继续查找
    19 }
    20 
    21 void unite(int x, int y) {//合并函数
    22     x = find(x);//查找x的根节点
    23     y = find(y);//查找y的根节点
    24     if (x == y)//若这两个数的结点是一样的,那么他们本来就是一个分组里的了,所以没有操作
    25         return ;
    26     par[x] = y;//不然的话,就让其中的一个点成为另一个点的根节点。
    27 }
    28 
    29 int main() {
    30     int n, m;
    31     int t = 0;
    32 
    33     while (scanf("%d %d", &n, &m), n + m) {
    34         for (int i = 1; i <= n; i++)
    35             par[i] = i;//这是初始化
    36         while (m--) {
    37             int u, v;
    38             scanf("%d %d", &u, &v);
    39             unite(u, v);
    40         }
    41         int ans = 0;
    42         for (int i = 1; i <= n; i++)
    43             if (i == par[i])
    44                 ans++;//计数
    45         printf("Case %d: %d
    ", ++t, ans);//输出
    46     }
    47     return 0;
    48 }

    既然说了上面是未优化的,那这儿就要说一下优化的喽。

      我们的代码需要用到路径压缩 和 这课树(也就是分组)的高度。

    若不知道这两个东东的 话  ,还是这位大牛的  http://www.cnblogs.com/cyjb/p/UnionFindSets.html

    find函数的优化

    int find(int x) {//查找函数
        if (par[x] == x)//若本身就是根节点 ,那么return
            return x;
        return par[x] = find(par[x]);//不是的话,继续查找,并且进行路径压缩。
        
        //上面为递归版本
        /*
        int a = x;
        while (a != par[a])//一直找到a的 根节点
            a = par[a];
        while (x != par[x]) {//路径压缩
            int t = par[x];
            par[x] = a;
            x = t;
        }
        return a;
        */
        //上面为非递归版本
    }

    合并函数的优化

     1 void unite(int x, int y) {//合并函数
     2     x = find(x);//查找x的根节点
     3     y = find(y);//查找y的根节点
     4     if (x == y)//若这两个数的结点是一样的,那么他们本来就是一个分组里的了,所以没有操作
     5         return ;
     6     if (rank[x] < rank[y]) //不然的话,  如果x这个分组的高度小于y分组的高度
     7         par[x] = y;//将x并到 y这个分组中,并且是x的父节点是y
     8     else {
     9         par[y] = x;//不然就是y的父节点为x
    10         if (rank[x] == rank[y])//若两个分组的高度相同
    11             rank[x]++;//x 的分组高度++
    12     }
    13 }

      在这给出 这个题目的 优化的代码。

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <iostream>
     4 #include <algorithm>
     5 #include <queue>
     6 #define LL long long
     7 
     8 using namespace std;
     9 
    10 const int INF = 1e9+7;
    11 const int VM = 100003;
    12 
    13 int par[VM];
    14 int rank[VM];
    15 
    16 int find(int x) {//查找函数
    17     if (par[x] == x)//若本身就是根节点 ,那么return
    18         return x;
    19     return par[x] = find(par[x]);//不是的话,继续查找,并且进行路径压缩。
    20     
    21     //上面为递归版本
    22     /*
    23     int a = x;
    24     while (a != par[a])//一直找到a的 根节点
    25         a = par[a];
    26     while (x != par[x]) {//路径压缩
    27         int t = par[x];
    28         par[x] = a;
    29         x = t;
    30     }
    31     return a;
    32     */
    33     //上面为非递归版本
    34 }
    35 
    36 void unite(int x, int y) {//合并函数
    37     x = find(x);//查找x的根节点
    38     y = find(y);//查找y的根节点
    39     if (x == y)//若这两个数的结点是一样的,那么他们本来就是一个分组里的了,所以没有操作
    40         return ;
    41     if (rank[x] < rank[y]) //不然的话,  如果x这个分组的高度小于y分组的高度
    42         par[x] = y;//将x并到 y这个分组中,并且是x的父节点是y
    43     else {
    44         par[y] = x;//不然就是y的父节点为x
    45         if (rank[x] == rank[y])//若两个分组的高度相同
    46             rank[x]++;//x 的分组高度++
    47     }
    48 }
    49 
    50 int main() {
    51     int n, m;
    52     int t = 0;
    53 
    54     while (scanf("%d %d", &n, &m), n + m) {
    55         for (int i = 1; i <= n; i++)
    56             par[i] = i;//这是初始化
    57         memset(rank, 0, sizeof(rank));//树的高度 为 0  初始化
    58         while (m--) {
    59             int u, v;
    60             scanf("%d %d", &u, &v);
    61             unite(u, v);
    62         }
    63         int ans = 0;
    64         for (int i = 1; i <= n; i++)
    65             if (i == par[i])
    66                 ans++;//计数
    67         printf("Case %d: %d
    ", ++t, ans);//输出
    68     }
    69     return 0;
    70 }
    优化后的题解


     再来一个题  : 题目题目

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <iostream>
     4 #include <algorithm>
     5 #include <queue>
     6 #define LL long long
     7 
     8 using namespace std;
     9 
    10 const int INF = 1e9+7;
    11 const int VM = 100003;
    12 
    13 int par[VM];
    14 int rank[VM];
    15 
    16 int find(int x) {//查找函数
    17     if (par[x] == x)//若本身就是根节点 ,那么return
    18         return x;
    19     return par[x] = find(par[x]);//不是的话,继续查找,并且进行路径压缩。
    20 
    21     //上面为递归版本
    22     /*
    23     int a = x;
    24     while (a != par[a])//一直找到a的 根节点
    25         a = par[a];
    26     while (x != par[x]) {//路径压缩
    27         int t = par[x];
    28         par[x] = a;
    29         x = t;
    30     }
    31     return a;
    32     */
    33     //上面为非递归版本
    34 }
    35 
    36 void unite(int x, int y) {//合并函数
    37     x = find(x);//查找x的根节点
    38     y = find(y);//查找y的根节点
    39     if (x == y)//若这两个数的结点是一样的,那么他们本来就是一个分组里的了,所以没有操作
    40         return ;
    41     if (rank[x] < rank[y]) //不然的话,  如果x这个分组的高度小于y分组的高度
    42         par[x] = y;//将x并到 y这个分组中,并且是x的父节点是y
    43     else {
    44         par[y] = x;//不然就是y的父节点为x
    45         if (rank[x] == rank[y])//若两个分组的高度相同
    46             rank[x]++;//x 的分组高度++
    47     }
    48 }
    49 
    50 int main() {
    51     int n, m;
    52 
    53     while (scanf("%d %d", &n, &m) == 2) {
    54         for (int i = 0; i < n; i++)
    55             par[i] = i;//这是初始化
    56         memset(rank, 0, sizeof(rank));//树的高度 为 0  初始化
    57         while (m--) {
    58             int u, v;
    59             scanf("%d %d", &u, &v);
    60             unite(u, v);
    61         }
    62         int ans = 0;
    63         for (int i = 1; i < n; i++)
    64             if (find(par[0]) == find(par[i]))
    65                 ans++;//计数
    66         printf("%d
    ", ans);//输出
    67     }
    68     return 0;
    69 }
    我是题解

    不会画图,就不好讲了。

    并查集 算是马马虎虎的说完了吧。。。。

    插播 :

      什么是  生成树?什么   又是    最小生成树?

      给定一个无向图,如果它的某个子图中的任意两个顶点都互相联通并且是一棵树,那么这棵树就是  生成树  

      也就是说,在一个图中,有 n 个顶点 ,若有 n - 1 条边,能使得所有的顶点相连 ,就是 生成树了。

      如果你给这些边  加上权值  ,那 权值 总和最小的额生成树  就是最小生成树

    再插 :

      最小生成树  有两种方法   一种  : 普利姆算法   另一种 : 克鲁斯卡尔。

    3、  普利姆算法  &  优先队列优化

      prim算法和Dijkstra算法十分相似,都是从某个顶点出发,不断加边的算法。

      1. 假设有一棵树只包含一个顶点的v的树T。

      2.贪心的选取T和其他顶点之间相连的最小权值的边,并将它加入T中。

      3.不断重复1,2  知道所有的点相连生成一棵最小生成树。(此算法的正确性,不给予证明)

      下面开始练题。

      题目    :   我是题目 请点击  

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <iostream>
     4 #include <algorithm>
     5 
     6 using namespace std;
     7 
     8 const int INF = 1e9+7;
     9 const int VM = 103;
    10 
    11 int G[VM][VM];//存图
    12 
    13 void prim(int n) {
    14     int dis[VM];//记录 边的权值
    15     bool vis[VM];//记录为否访问
    16     int ans = 0;//
    17 
    18     memset(vis, 0, sizeof(vis));//初始化
    19     for (int i = 1; i <= n; i++)
    20         dis[i] = G[1][i];//初始化
    21     dis[1] = 0;// 
    22     vis[1] = true;// 1 点标记为已访问
    23     int i;
    24     for (i = 2; i <= n; i++) {//进行 n - 1 次操作
    25         int u = INF;//初始化
    26         int k;
    27         for (int j = 1; j <= n; j++) {//遍历所有顶点
    28             if (!vis[j] && u > dis[j]) {//在所有的未加入的点中  找一个最小的权值
    29                 k = j;//记录下标
    30                 u = dis[j];//更新最小值
    31             }
    32         }
    33         if (u == INF)//若图是不连通的   
    34             break;//提前退出
    35         vis[k] = true;//标记为已加入
    36         ans += u;//加权值
    37         for (int j = 1; j <= n; j++) {//遍历所有的点
    38             if (!vis[j] && dis[j] > G[k][j])//对未加入的点&&能找到与此点相连且的权值最小的边 
    39                 dis[j] = G[k][j];//进行更新
    40         }
    41     }
    42     //输出
    43     if (i - 1 == n)
    44         printf("%d
    ", ans);
    45     else
    46         printf("?
    ");
    47 }
    48 
    49 int main() {
    50     int n, m;
    51 
    52     while (scanf("%d %d", &n, &m), n) {//对边数 和点数的获取
    53         for (int i = 1; i <= m; i++) {//初始化
    54             for (int j = 1; j <= m; j++) {
    55                 G[i][j] = i == j ? 0 : INF;
    56             }
    57         }
    58         while (n--) {
    59             int u, v, w;
    60             scanf("%d %d %d", &u, &v, &w);//获取 数据
    61             if (G[u][v] > w)//防止重边&&存两点之间的最短距离
    62                 G[u][v] = G[v][u] = w;
    63         }
    64         prim(m);//调用函数
    65     }
    66     return 0;
    67 }
    我是 题解一号

    主函数对数据的获取及图的存储

     1 int main() {
     2     int n, m;
     3 
     4     while (scanf("%d %d", &n, &m), n) {//对边数 和点数的获取
     5         for (int i = 1; i <= m; i++) {//初始化
     6             for (int j = 1; j <= m; j++) {
     7                 G[i][j] = i == j ? 0 : INF;
     8             }
     9         }
    10         while (n--) {
    11             int u, v, w;
    12             scanf("%d %d %d", &u, &v, &w);//获取 数据
    13             if (G[u][v] > w)//防止重边&&存两点之间的最短距离
    14                 G[u][v] = G[v][u] = w;
    15         }
    16         prim(m);//调用函数
    17     }
    18     return 0;
    19 }

    普利姆函数

     1 void prim(int n) {
     2     int dis[VM];//记录 边的权值
     3     bool vis[VM];//记录为否访问
     4     int ans = 0;//
     5 
     6     memset(vis, 0, sizeof(vis));//初始化
     7     for (int i = 1; i <= n; i++)
     8         dis[i] = G[1][i];//初始化
     9     dis[1] = 0;// 
    10     vis[1] = true;// 1 点标记为已访问
    11     int i;
    12     for (i = 2; i <= n; i++) {//进行 n - 1 次操作
    13         int u = INF;//初始化
    14         int k;
    15         for (int j = 1; j <= n; j++) {//遍历所有顶点
    16             if (!vis[j] && u > dis[j]) {//在所有的未加入的点中  找一个最小的权值
    17                 k = j;//记录下标
    18                 u = dis[j];//更新最小值
    19             }
    20         }
    21         if (u == INF)//若图是不连通的   
    22             break;//提前退出
    23         vis[k] = true;//标记为已加入
    24         ans += u;//加权值
    25         for (int j = 1; j <= n; j++) {//遍历所有的点
    26             if (!vis[j] && dis[j] > G[k][j])//对未加入的点&&能找到与此点相连且的权值最小的边 
    27                 dis[j] = G[k][j];//进行更新
    28         }
    29     }
    30     //输出
    31     if (i - 1 == n)
    32         printf("%d
    ", ans);
    33     else
    34         printf("?
    ");
    35 }

    上面的算法的时间复杂度为O(V * V),是不是和Dijkstra很相似呢?那么可不可用优化Dijkstra算法的方法来优化这个呢? 当然可以

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <iostream>
     4 #include <algorithm>
     5 #include <queue>
     6 #define LL long long
     7 
     8 using namespace std;
     9 
    10 const int INF = 1e9+7;
    11 const int VM = 103;
    12 
    13 typedef pair<int, int>P;//对组
    14 struct node {//前向星 结构体
    15     int v, w;
    16     int next;
    17 };
    18 node edge[4 * VM];//前向星数组
    19 int head[VM];//头指针数组
    20 int cnt;//计数
    21 
    22 void add(int u, int v, int w) {//加边函数
    23     edge[cnt].v = v;//顶点
    24     edge[cnt].w = w;//权值
    25     edge[cnt].next = head[u];//下一个
    26     head[u] = cnt++;//头指针
    27 }
    28 
    29 void prim(int n) {//普利姆函数
    30     bool vis[VM];//标记是否访问过
    31     int dis[VM];//记录权值
    32     int ans = 0;//最小生成树的总值
    33     int count = 0;//计数
    34     priority_queue<P, vector<P>, greater<P> >que;//权值从小到大的队列
    35 
    36     fill(dis, dis + VM, INF);//初始化
    37     memset(vis, 0, sizeof(vis));//初始化
    38     dis[1] = 0;//初始化
    39     que.push(P(0, 1));//将 1点 和 dis[1] = 0 放入队列
    40     while (que.empty() == false) {//队列不为空时
    41         P p = que.top();//取出队首
    42         que.pop();//删除
    43         int u = p.second;//
    44         if (vis[u] == true)//若此顶点已经加入生成树
    45             continue;//
    46         vis[u] = true;//否则,就标记为加入
    47         ans += dis[u];//
    48         count++;//加入点个数
    49         for (int i = head[u]; i != -1; i = edge[i].next) {//遍历与该点相邻的点
    50             node e = edge[i];
    51             if (dis[e.v] > e.w) {//更新他们的权值
    52                 dis[e.v] = e.w;//
    53                 que.push(P(dis[e.v], e.v));//放入队列
    54             }
    55         }
    56     }
    57     //输出
    58     if (count == n)
    59         printf("%d
    ", ans);
    60     else
    61         printf("?
    ");
    62 }
    63 
    64 int main() {
    65     int n, m;
    66 
    67     while (scanf("%d %d", &n, &m), n) {//边的个数 顶点个数
    68         memset(head, -1, sizeof(head));//初始化
    69         cnt = 0;//初始化
    70         while (n--) {
    71             int u, v, w;
    72             scanf("%d %d %d", &u, &v, &w);//获取数据
    73             add(u, v, w);//加边
    74             add(v, u, w);//无向图
    75         }
    76         prim(m);//普利姆算法
    77     }
    78     return 0;
    79 }
    我是题解二号

    主函数对数据的获取 和 图的存储

     1 int main() {
     2     int n, m;
     3 
     4     while (scanf("%d %d", &n, &m), n) {//边的个数 顶点个数
     5         memset(head, -1, sizeof(head));//初始化
     6         cnt = 0;//初始化
     7         while (n--) {
     8             int u, v, w;
     9             scanf("%d %d %d", &u, &v, &w);//获取数据
    10             add(u, v, w);//加边
    11             add(v, u, w);//无向图
    12         }
    13         prim(m);//普利姆算法
    14     }
    15     return 0;
    16 }

    prim函数

     1 void prim(int n) {//普利姆函数
     2     bool vis[VM];//标记是否访问过
     3     int dis[VM];//记录权值
     4     int ans = 0;//最小生成树的总值
     5     int count = 0;//计数
     6     priority_queue<P, vector<P>, greater<P> >que;//权值从小到大的队列
     7 
     8     fill(dis, dis + VM, INF);//初始化
     9     memset(vis, 0, sizeof(vis));//初始化
    10     dis[1] = 0;//初始化
    11     que.push(P(0, 1));//将 1点 和 dis[1] = 0 放入队列
    12     while (que.empty() == false) {//队列不为空时
    13         P p = que.top();//取出队首
    14         que.pop();//删除
    15         int u = p.second;//
    16         if (vis[u] == true)//若此顶点已经加入生成树
    17             continue;//
    18         vis[u] = true;//否则,就标记为加入
    19         ans += dis[u];//
    20         count++;//加入点个数
    21         for (int i = head[u]; i != -1; i = edge[i].next) {//遍历与该点相邻的点
    22             node e = edge[i];
    23             if (dis[e.v] > e.w) {//更新他们的权值
    24                 dis[e.v] = e.w;//
    25                 que.push(P(dis[e.v], e.v));//放入队列
    26             }
    27         }
    28     }
    29     //输出
    30     if (count == n)
    31         printf("%d
    ", ans);
    32     else
    33         printf("?
    ");
    34 }

    此算法的时间复杂度为O(E*log(V));   是不是很棒!

    次算法结束。

    4、  克鲁斯卡尔算法

      克鲁斯卡尔算法就是利用了并查集这一方法,通过对所有边从小到大排序后,判断这两个顶点是否在一个分组中,若在一个分组中,说明这两个点已经加入到生成树之中,若不在一个分组中

    就可以直接加上这个边了。

       还是以上一题为例

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <iostream>
     4 #include <algorithm>
     5 #include <queue>
     6 #define LL long long
     7 
     8 using namespace std;
     9 
    10 const int INF = 1e9+7;
    11 const int VM = 103;
    12 
    13 struct node {//边的结构体
    14     int u, v, w;
    15 };
    16 node edge[VM * 2];
    17 int rank[VM];//分组的高度
    18 int par[VM];//父节点
    19 
    20 bool cmp(const node &a, const node &b) {
    21     return a.w < b.w;//按w从小到大排序
    22 }
    23 
    24 int find(int x) {
    25     if (par[x] == x)//若根节点为本身
    26         return x;
    27     return par[x] = find(par[x]);//路径压缩
    28 }
    29 
    30 bool same(int x, int y) {//判断为否在同一分组中 
    31     return find(x) == find(y);
    32 }
    33 
    34 void unite(int x, int y) {
    35     x = find(x);//查找根节点
    36     y = find(y);//查找根节点
    37     if (x == y)//若以在同一分组
    38         return ;
    39     if (rank[x] < rank[y])//y所在分组的高度 大于x的
    40         par[x] = y;//将y作x的父节点。
    41     else {
    42         par[y] = x;//将x作为y的父节点
    43         if (rank[x] == rank[y])//若两个分组高度相同
    44             rank[x]++;//x分组所在高度++
    45     }
    46 }
    47 
    48 int main() {
    49     int n, m;
    50 
    51     while (scanf("%d %d", &n, &m), n) {//获取边的个数 和顶点个数
    52         int cnt = 0;//
    53         for (int i = 1; i <= m; i++)//初始化
    54             par[i] = i;
    55         memset(rank, 0, sizeof(rank));//初始化
    56         while (n--) {
    57             scanf("%d %d %d", &edge[cnt].u, &edge[cnt].v, &edge[cnt].w);//获取数据
    58             cnt++;
    59         }
    60         sort(edge, edge + cnt, cmp);//按权值从小到大排序
    61         int ans = 0;//最小生成树 权值
    62         int count = 0;//计数
    63         for (int i = 0; i < cnt; i++) {//对所有的边
    64             node e = edge[i];
    65             if (!same(e.u, e.v)) {//若两点不属于一个分组
    66                 ans += e.w;//权值总和
    67                 unite(e.u, e.v);//合并两点
    68                 count++;//计数
    69             }
    70         }
    71         //输出
    72         if (count == m - 1)
    73             printf("%d
    ", ans);
    74         else
    75             printf("?
    ");
    76     }
    77     return 0;
    78 }

    自己感觉这个专题写的好糟糕啊,哎,深夜了,睡觉吧。明天又是新的一天啊。因为明天,不,今天8点就要开始新学期第一堂课了啊。

      


    作者:blueppo
    出处:http://www.cnblogs.com/Yan-C/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    10种 分布式ID生成方式(新增MongoDB的ObjectId)
    Spring核心接口Ordered的实现及应用 (动态切换数据源时候用到)
    No module named 'Crypto' 解决方案
    使用Anaconda管理多个版本的Python环境
    深入浅出Blazor webassembly 之API服务端保护
    [转载]HTTPS 是如何保护你的安全的
    [转载]api接口token的生成和应用
    深入浅出Blazor webassembly之HttpClient使用
    深入浅出Blazor webassembly之自定义Input组件
    深入浅出Blazor webassembly之EditForm
  • 原文地址:https://www.cnblogs.com/Yan-C/p/3943940.html
Copyright © 2011-2022 走看看