今天晚上终于处理好了上周一直留下的问题,把Kreskal算法给解决了,主要利用的是并查集的思想,再辅助一些代码就可以实现。
不得不佩服自己思路的跨越,周五到周日三天去监考四六级,周四写的代码,周一晚上再回来看,居然还能一直想着思路,脑子里可能只有数据结构吧,现在要好好学离散数学。
基本思想
- 建立一个无向连通图 G=( V , E ),这里字母就完全按照上课时候的课件来标注了。设最小生成树为T = { U ,TE },其初态为 U = V , TE={ } 。
- 按照图中,两点之间的边权进行从小到大的排序
2.1. 如果被考察的两条边属于两个不同的连通分量,则把此边纳入最小生成树之中,成为最小生成树中的一条边(加入T中),与此同时把这两个不同的连通分量合并成一个连通分量。
2.2. 如果被考察边属于同一个连通分支,舍去此条边。(离散数学中讲到过,树中两点间加入一条边,则会产生回路。) - 循环进行操作,直到T中连通分量数为1,此时树之中所有的点都是连通的,且由于每次都是纳入的最小边,所以边权最小,成为最小生成树。
并查集
parent数组的含义是一个节点如果与另外一个结点连接后,我们默认一个结点的序号是另一个结点的parent数组值,这里是顺序存储的双亲表示法中相关知识。
这里处理的是判断一条边的两个点是否属于同一个连通分支,利用while循环不断地找最终指向的父亲,这样我们找的每个结点都进行并查集的搜索,就可以判断两结点是否是同一个连通分支。
int Find(int parent[], int node)
{
int f = node;
while (parent[f] > -1)
{
f = parent[f];
}
return f;
}
上面的两张就是第一个图的并查集搜索过程,这里都是用的序号作为的顶点值,这里顶点值与并查集的顺序存储序号相同,比较方便查找。
Kruskal算法
- 对边进行从小到大排序,使用sort函数。先从最小边开始查找判断,并且为所有的parent数组赋初值-1,表示所有的顶点都没有父亲,都是一个独立的连通分支。
- 进行arcNum次循环,分别判断边两个顶点是否是同一个连通分支后,如果不是输出该边的两个顶点,以及这条边的边权,并且为其中赋值parent,使这两个顶点成为同一个连通分支。
- 如果次数等于顶点数-1,直接跳出循环,遍历完成。
void Kruskal()
{
sort(edge + 1, edge + arcNum + 1, cmp);
for (int i = 1; i <= vertexNum; ++i)
{
parent[i] = -1;
}
int count = 0;
int start, end;
for (int i = 1; i <= arcNum; ++i)
{
start = edge[i].start;
end = edge[i].end;
int m = Find(parent, start);
int n = Find(parent, end);
if (m != n)
{
cout << start << " " << end << " " << edge[i].value << endl;
parent[n] = m;
count++; // 执行到n-1次后直接结束 n是顶点个数
if (count == vertexNum - 1)
break;
}
}
}
全部代码
这里与Prime不同的地方是,Prime使用的邻接矩阵存储,这里用了struct结构体,因为如果使用结构体在sort函数中能够方便排序,时间减少。
#include<iostream>
#include<algorithm>
using namespace std;
int Find(int parent[], int node)
{
int f = node;
while (parent[f] > -1)
{
f = parent[f];
}
return f;
}
struct EdgeNode
{
int start;
int end;
int value;
};
bool cmp(EdgeNode& a, EdgeNode& b)
{
return a.value < b.value;
}
class MGraph
{
int arcNum;
int vertexNum;
int *parent;
EdgeNode *edge;
public:
MGraph(int n, int e)
{
arcNum = e;
vertexNum = n;
parent = new int[vertexNum + 1];
edge = new EdgeNode[arcNum + 1];
for (int i = 1; i <= arcNum; ++i)
{
cin >> edge[i].start >> edge[i].end >> edge[i].value;
}
}
void Kruskal()
{
sort(edge + 1, edge + arcNum + 1, cmp);
for (int i = 1; i <= vertexNum; ++i)
{
parent[i] = -1;
}
int count = 0;
int start, end;
for (int i = 1; i <= arcNum; ++i)
{
start = edge[i].start;
end = edge[i].end;
int m = Find(parent, start);
int n = Find(parent, end);
if (m != n)
{
cout << start << " " << end << " " << edge[i].value << endl;
parent[n] = m;
count++; // 执行到n-1次后直接结束 n是顶点个数
if (count == vertexNum - 1)
break;
}
}
}
};
int main()
{
int x, y;
cin >> x >> y;
MGraph G(x, y);
G.Kruskal();
return 0;
}
时间复杂度是O(E log(E)),时间花费在排序上。