拓扑排序 详解 + 并查集 详解 + 最小生成树详解

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

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

本文链接   : 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 输入有若干组,每组中的第一行为二个数N1<=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 ? '\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 ? '\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 ? '\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 ? '\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\n", ++t, ans);//输出
46     }
47     return 0;
48 }

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

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

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

find函数的优化

int find(int x) {//查找函数if (par[x] == x)//若本身就是根节点 ,那么returnreturn 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\n", ++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\n", 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\n", ans);
45     else
46         printf("?\n");
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\n", ans);
33     else
34         printf("?\n");
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\n", ans);
60     else
61         printf("?\n");
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\n", ans);
32     else
33         printf("?\n");
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\n", ans);
74         else
75             printf("?\n");
76     }
77     return 0;
78 }

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/510169.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Impala Shell 简单命令

目录 1.更新元数据 2.对查询结果去格式化 3.查询结果存储到文件 4.去格式化后指定分隔符 5.-p或者--show-profiles:显示查询的执行计划(与EXPLAIN语句输出相同)和每个查询语句底层的执行步骤的详细信息. 6.指定主机名连接 (-i) 7.执行查询语句 8.指定脚本文件执行SQL …

CDH邮件预警

转载自&#xff1a;http://blog.51cto.com/feature09/2055835 在CDH的7180页面找到Cloudera Managerment Service 如图所示&#xff1a; 在Configuration中&#xff0c;搜索alert 设置接收信息的邮箱。 设置内容都在图片上&#xff1a; 添加邮件页眉说明&#xff0c;第一行显…

结构光双目视觉测距原理

结构光视觉技术是一种主动投影式的三维测量技术&#xff0c;通过使用投影仪和相机组成的系统来对物体进行三维测量 系统结构&#xff1a; 硬件系统&#xff1a; 常见编码方法&#xff1a; 结构光图案编码 常用的是格雷码和传统的二进制码相比&#xff0c;格雷码的编码数中任意…

c++静态成员函数为什么不能为虚函数?

知乎用户 three passions govern my life2 人赞同可以把静态成员函数看作是“命名空间受限的普通函数”&#xff0c;所以它不能有运行时多态发布于 2015-06-23 添加评论 感谢 分享收藏 • 没有帮助 • 举报 • 作者保留权利Elvis Wang C程序员转型Java中6 人赞同其实我觉得这个…

如何成为一个优秀的高级C++程序员

C这门语言从诞生到今天已经经历了将近30个年头。不可否认&#xff0c;它的学习难度都比其它 语言较高。而它的学习难度&#xff0c;主要来自于它的复杂性。现在C的使用范围比以前已经少了很多&#xff0c;java、C#、python等语言在很多方面已经可以代替 C。但是也有很多地方是其…

c++标准库 及 命名空间std

1、命名空间std   C标准中引入命名空间的概念&#xff0c;是为了解决不同模块或者函数库中相同标识符冲突的问题。有了命名空间的概念&#xff0c;标识符就被限制在特定的范围(函数)内&#xff0c;不会引起命名冲突。最典型的例子就是std命名空间&#xff0c;C标准库中所有标…

相机标定原理和opencv代码解析

1.单目标定 单应矩阵 设三维空间点的齐次坐标,对应的图像坐标为 他们满足一下关系&#xff1a; s为尺度因子&#xff0c;K为内参矩阵 R和T旋转平移矩阵统称为外参 假设我们提供K个棋盘图像&#xff0c;每个棋盘有N个角点&#xff0c;于是我们拥有2KN个约束方程。与此同时&am…

HUE 提交Schedule 时区问题

1.配置如下&#xff0c;每天定时 14:02 启动 2.运行后&#xff0c;任务状态提示&#xff0c;时间变成了06:02:00 3.修改HUE的时区配置 修改后 3.重启 4.然后执行发现&#xff0c;还是不起作用&#xff0c;后排查了一下oozie问题&#xff0c;发现hue的时区配置对oozie无法生效…

cuda的global memory介绍

CUDA Memory Model 对于程序员来说&#xff0c;memory可以分为下面两类&#xff1a; Programmable&#xff1a;我们可以灵活操作的部分。Non-programmable&#xff1a;不能操作&#xff0c;由一套自动机制来达到很好的性能。 在CPU的存储结构中&#xff0c;L1和L2 cache都是n…

Mybase到期 破解

1.找到mybase安装目录 C:\Users\Zsh\AppData\Local\wjjsoft\nyfedit6 2.打开nyfedit.ini 3. 内容中查找 App.UserLic.FirstUseOn 删除 4.保存打开mybase即可

Android中给按钮同时设置背景和圆角示例代码

前言 最近在做按钮的时候遇到在给按钮设置一张图片作为背景的同时还要自己定义圆角&#xff0c;最简单的做法就是直接切张圆角图作为按钮就可以了&#xff0c;但是如果不这样该怎么办呢&#xff0c;看代码&#xff1a; 下面来看效果图 一、先建一个圆角的shape文件&#xff1a;…

awk使用方法

awk是行处理器: 相比较屏幕处理的优点&#xff0c;在处理庞大文件时不会出现内存溢出或是处理缓慢的问题&#xff0c;通常用来格式化文本信息 awk处理过程: 依次对每一行进行处理&#xff0c;然后输出 awk命令形式: awk [-F|-f|-v] ‘BEGIN{} //{command1; command2} END{}’…

cude的__ldg使用

一般使用__ldg是更好的选择。通过constant缓存存储的数据必须相对较小而且必须获取同一个地址以便获取最佳性能&#xff0c;相反&#xff0c;只读缓存则可以存放较大的数据&#xff0c;且不必地址一致。 __global__ void transpose2(const real* A, real* B, const int N) {co…

cuda的shared momery

CUDA SHARED MEMORY 在global Memory部分&#xff0c;数据对齐和连续是很重要的话题&#xff0c;当使用L1的时候&#xff0c;对齐问题可以忽略&#xff0c;但是非连续的获取内存依然会降低性能。依赖于算法本质&#xff0c;某些情况下&#xff0c;非连续访问是不可避免的。使用…

图的表示方法和C++实现

图的表示最长用的两种方法是&#xff1a; 1&#xff09;、邻接矩阵表示法 2&#xff09;、邻接表表示 下面是两种构造图的方法 1&#xff09;邻接矩阵&#xff1a; [cpp] view plaincopy#include <iostream> #include <vector> using namespace std; //枚…

成为专业程序员的 6 个技巧

1.在你责怪别人之前&#xff0c;先检查自己的代码 先想一想自己的假设和其他人的假设。来自不同供应商的工具可能内置不同的假设&#xff0c;即便是相同的供应商对于不同的工具&#xff0c;其假设也可能不同。 当其他人正在报告一个你不能重复的问题的时候&#xff0c;去看看他…

HUE集成Hbase

目录 一、Hbase开启代理用户相关配置 二、代理用户授权认证 三、检查HUE在hue.ini文件中指定的HBASE的本地配置目录 一、Hbase开启代理用户相关配置 Cloudera Manager修改Hbase配置或Hbase配置文件-hbase-site.xml <property><name>hbase.thrift.support.proxyu…

OpenCV中cornerSubPixel()亚像素求精原理

采用的方法为最小二乘法&#xff1a; 首先我们要构建以下方程&#xff1a; 我们讨论角点的情况&#xff1a; q是我们要求的角点 p0和p1为q周围的点 &#xff08;q-pi&#xff09;为一个向量 Gi为pi处的梯度 所以满足一下公式 Gi*(q-pi)0 有以下两种情况&#xff1a; &a…

HBase 2.0 之修复工具 HBCK2 运维指南

HBase 2.0 之修复工具 HBCK2 运维指南 转载自&#xff1a;https://mp.weixin.qq.com/s/GVMWwB1WsKcdvZGfvX1lcA?spma2c4e.11153940.blogcont683107.11.49d762a815MegW 概述 目前社区已经发布了 HBase 的 2.0 版本&#xff0c;很多公司都希望去尝试新版本上的新功能&#xff0c…

html中article、section、aside的区别与联系

首先看看我做的图(PS:有点丑)&#xff0c;通俗易懂