拓扑排序 详解 + 并查集 详解 + 最小生成树详解
来源:互联网 发布:软件服务外包 编辑:程序博客网 时间:2024/05/22 06:30
若您发现本文有什么错误,请联系我,我会及时改正的,谢谢您的合作!
本文为原创文章,转载请注明出处
本文链接 : 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,2,3,。。。。,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 Output10 给出一个符合要求的排名。输出时队伍号之间有空格,最后一名后面没有空格。11 12 其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。13 14 15 Sample Input16 17 4 3 18 1 2 19 2 3 20 4 321 22 23 24 Sample Output25 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]--;//让这个点的入度-130 }31 }32 }33 34 int main() {35 int n, m;36 37 while (scanf("%d %d", &n, &m) == 2) {//多组输入, 获取n, m38 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打败过v43 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打败过v10 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]--;//让这个点的入度-117 }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) {//入度为 027 que.push(i);//加入队列28 deg[i]--;//入度 变为 -129 }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)//若此点的 入度为 039 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,m49 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,v55 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,v12 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)//若此点的 入度为 018 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)//若本身就是根节点 ,那么return3 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)//若本身就是根节点 ,那么return17 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的父节点为x10 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)//若本身就是根节点 ,那么return18 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的父节点是y43 else {44 par[y] = x;//不然就是y的父节点为x45 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)//若本身就是根节点 ,那么return18 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的父节点是y43 else {44 par[y] = x;//不然就是y的父节点为x45 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 else46 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 else34 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 else61 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 else33 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 else75 printf("?\n");76 }77 return 0;78 }
自己感觉这个专题写的好糟糕啊,哎,深夜了,睡觉吧。明天又是新的一天啊。因为明天,不,今天8点就要开始新学期第一堂课了啊。
- 拓扑排序 详解 + 并查集 详解 + 最小生成树(MST)详解
- 拓扑排序 详解 + 并查集 详解 + 最小生成树详解
- 拓扑排序 详解 + 并查集 详解 + 最小生成树(MST)详解 【普利姆算法 + 优先队列优化 & 克鲁斯卡尔算法】
- 拓扑排序 详解 + 并查集 详解 + 最小生成树(MST)详解 【普利姆算法 + 优先队列优化 & 克鲁斯卡尔算法】
- 并查集和拓扑排序加最小生成树
- 2017.4.3 机房测试 (并查集,最短路,拓扑排序,最小生成树)
- 拓扑排序 详解+最小生成树(MST)详解 【普利姆算法 + 优先队列优化 & 克鲁斯卡尔算法】
- 并查集详解
- 并查集详解
- 并查集详解
- 并查集详解
- 并查集详解
- 并查集详解
- 并查集详解
- 并查集详解
- 并查集详解
- 并查集详解
- 并查集详解
- eclipse导入android项目报错 MainActivity 和values文件出错
- 初入密码学世界
- android 关于发起不同网络请求与服务器session不一致问题
- 请问诸位大神,Android怎么实现图片转动
- linq查询的结果怎么在视图用
- 拓扑排序 详解 + 并查集 详解 + 最小生成树详解
- Android简易实战教程--第五十话《动画扫描》
- JniHelper的再说明
- 制作U盘启动盘并用Ghost备份系统
- ZCMU-1722-围栅栏
- 请问一下Android Studio如何配置JAVACV 0.8Javacv+2.4.9Opencv 万分感谢
- 增删改查-反射技术通用的方法
- Http与https区别(转发)
- 双层LIstView现在只显示一层