您的位置:首页 > 其它

算法基础 - 最小生成树(Kruscal算法)

2016-05-27 17:02 197 查看

最小生成树

就是让一个图里,生成一个树状图,任意两个节点只有唯一的路径到达,并且让这个距离最短。

Karuscal算法

这个算法是相对于Prim算法( 传送门:Prim算法 )的另外一个算法,这个算法主要的应用场景是对于稀疏图非常好用,因为Karuscal算法是根据边来计算的,每次添加一个最短的边。

而Prim的每步是添加一个最短的点,类似于Dijkstra算法的步骤。所以Prim的算法是对于稠密的图效果更好。

算法思想

首先对所有边进行一次排序,然后每次找到最短的边,假如这个边的两个点不属于同一个子树,就加入进来,如果属于同一棵子树,就找下一个边。

这里引用一下hihocoder上面的解释:

小Hi的温馨提示:边的费用在某些时候可以被理解成为边的长度、边的权值等等!所以在后文中各种可能会用上述各种称呼来描述费用一事。

小Ho听到了这个问题,发表了感慨:“这不就是之前最短路问题的时候针对点集变大,但是边集很小的稀疏图么?和SPFA算法当时遇到的问题很像诶!”

小Hi严肃道:“是的!在现实生活中,大部分的图其实都是稀疏图,哪怕不是,也可以像我这种通过筛选的方式转化为稀疏图!所以稀疏图上的问题是非常重要的!”

“是的!”小Ho连连称是,继续道:“那难道这题也像SPFA那样子来做么?但是最小生成树似乎是不可以用宽度优先搜索来解决的啊?”

“倒也没有那么复杂。”小Hi道:“还记的我们在Prim算法中得出的结论——对于城市i(i≠1),如果i与城市1的距离不超过其他任何城市j(j≠1)与城市1的距离,那么(1,

i)这一条边一定存在于某一棵最小生成树中么?”

“自然记得。”

“我们来把这个结论稍微改一下:图中最短的边一定属于某棵最小生成树。”小Hi说道:“证明是简单的——因为城市1的标号是随意的,也就是无论给哪个城市标1都会有之前的结论,那么对于任意节点来说它连接的所有边中最短的边一定存在于某一棵最小生成树中,而整个图中最短的一条边一定是这样的一条边。”

小Ho道:“是的,那么也就是说我只需要不断的找到当前图中最短的一条边(一开始就将所有的边排序然后从小到大)——这样的一条边一定属于某棵最小生成树!然后我只需要将连接的2个节点合并成为一个新的节点,那么这个问题就变成了一个规模-1的问题了!只需要不断重复这样的操作,就能够使得问题最后变成1的规模,这个时候只需要之前找到的所有一定存在于最小生成树中的边的费用加起来就是答案了就可以了!”

小Hi点了点头,说道:“那么还剩一个问题,在Prim问题中,由于合并都是和1号城市合并,所以只需要简单的记录一个节点是否已经合并进了1号城市就可以了,但是在这里却会复杂很多,你有什么想法么?”

“你这就太小看我的记忆力了!”小Ho抱怨道:“在当年遇到黑叔叔的时候,我们不是学会了一种并查集的方法么?在这里只需要用并查集维护哪些节点被合并到了一起,不就行了?”

“嗯~ o( ̄▽ ̄)o,算你聪明,赶紧去实现程序吧!”

代码实现

#include <iostream>
#include <vector>
#include <cstring>
#include <string>
#include <unordered_map>

using namespace std;

#define MAXNUM 100005 //定义最大顶点个数
#define MAXPATH 1000005 //定义最多边的条数

unordered_map<int, int> parent;//利用并查集来判断两个点在不在同一棵子树上

struct Path{ //定义边的结构体
int st;
int en;
int length;
Path(int s, int e, int len): st(s), en(e), length(len){}
};

Path *path[MAXPATH];
int cur = 0;

int findParent(int n){ //并查集查找方法
if (parent
== n) {
return n;
}
parent
= findParent(parent
); //状态压缩
return parent
;
}

void quickSort(int left, int right){ //快排方便找最小的边
if (left >= right) {
return;
}
int j = left;
for (int i = left; i < right; i++) {
if (path[i]->length < path[right]->length) {
swap(path[i], path[j]);
j++;
}
}
swap(path[j], path[right]);
quickSort(left, j-1);
quickSort(j+1, right);
}

int findMin(int M){ //找最小边
int st,en, pst, pen;
for (int i = cur; i < M; i++) {
st = path[i]->st;
en = path[i]->en;
pst = findParent(st);
pen = findParent(en);
if (pst != pen) {
parent[pst] = parent[pen];
cur = i+1;
return path[i]->length;
}
}
return -1;
}

long long Kruscal(int M){
long long result = 0;
quickSort(0, M-1);
while (1) {
int len = findMin(M);
if (len == -1) {
break;
}
result += len;
}
return result;
}

int main(){
int M,N;
cin>>M>>N; //M是顶点个数,N是边的数量
int st,en,len;
for (int i = 0; i < N; i++) {
scanf("%d%d%d", &st,&en,&len);
path[i] = new Path(st,en,len); //每次输入起点,终点,边长
parent[st] = st;
parent[en] = en;
}
cout<<Kruscal(N)<<endl;
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 Kruscal