算法解释:
克鲁斯卡尔(kruskal)算法,建立最小生成树的算法,观察这个图,
步骤是依次选择权值最小的边,构成最小生成树,注意是树,所以依次选择的各边不能构成环。
实现
针对这个图,我们定义一个集合IntEdges,用来存储未被添加到生成树中的边,并根据各边的按权值的大小进行排序,对这个集合进行排序,
但这里有一个问题,如何再添加一条边之后不让当前的树变成环呢。回想一下我们完过的贪吃蛇的游戏,如果因为吃一个果子,咬到了自己的身体,是不是就死掉了呢:
假如当前有一个生成树(暂且不要在乎权值大小,只是举个例子):顶点是
v7、v6、v0、v5(蓝色连线部分),这时候如果有边(v6,v5)添加进来,会构成闭环,所以不能添加进来,通过观察发现,条件总结起来,就一句话:
新添加的边的两个顶点,不能出现在同一个生成树中
那么如何判断两个顶点不出现在同一个生成树中呢,这时候我们就想到了树,**每个树都有一个树根节点,如果两个顶点有相同的树根节点,那么他们两个肯定是在同一个生成树中喽,**就像如果你能和某个人找到相同的祖宗,是不是就可以认为是一家人了呢(在同一颗树中)(对树不熟悉的同学可以复习一下树的知识),例如图中举例的生成树:
v7—>v6—>v1—>v5
接下来,就是选择一种数据存储结构,来表示这个树,目的是,每次输入一个顶点,就输出其树根节点,还记得静态链表吗,我们利用数组下标的特点,使用数组存储链式的树结构
定义一个数组 var parent []int,针对上面的例子,有:
parent[5]=1 //v5的双亲是v1
parent[1]=6 //v1的双亲是v6
parent[6]=7 //v6的双亲是v7
在设计一个函数,寻找树根节点,
func FindRoot(parent *[]int, f int) int {
for (*parent)[f] != f {
f = (*parent)[f]
}
return f
}
大家对代码中的 *parent)[f] != f 可能有疑惑,没关系,我来解释:
既然我们定义了parent数组,但是如果不初始化,那么是没法使用的,但是如和初始化呢,看图,从下图中找出已经存在的生成树:
你看这图里面什么也没有,搞不好你说我耍你!别急,往下看:
图中的每个小自环都是一棵生成树,每棵树只有一个节点,而且还是树根节点,自己给自己当祖宗,parent[0]=0,parent[1]=1,parent[2]=2…
所以我们的初始化代码就来了:
for i := 0; i < G.NumVex; i++ {
//自己是自己的前驱
parent[i] = i
}
Now,我们把树也讲清楚了,存储的数组也初始化了,都能找自己的祖宗了,下面就开始找生成树把,首先,不能忘了我们按权值大小排序好的边集,
他是长这样滴:
拿出最小的第一个边IntEdges[0](4,7,7),找4和7树根,通过FindRoot函数
n := FindRoot(&parent, IntEdges[0].begin)
m := FindRoot(&parent, IntEdges[0].end)
发现n=4,m=7 n!=m 说明他们两个之前不在一个生成树中,那么就把这条边添加进来吧,注意不要忘了,parent[n]=m或者parent[m]=n,任选一种方向即可,于是乎最初的图变成了这样:
接下来的每次循环,小生成树会越长越大,而且他们还会合并,像小泡泡变成大泡泡一样,最后这些树,变成了一颗树,而且还包括图中所有的顶点,这样我们的最小生成树就长成啦😊。
源代码:
数据结构定义:
// 定义边
type Edge struct {
//顶点
Vex1, Vex2 VexType
//权值
Weight int
}
// Graph 定义网数据结构
type Graph struct {
Vexes []VexType
Arc [][]int
NumVex, NumEdge int
}
// WGInit 初始化
func WGInit(G *Graph, vexs []VexType) {
G.Vexes = make([]VexType, len(vexs))
G.Arc = make([][]int, len(vexs))
for i := 0; i < len(vexs); i++ {
G.Arc[i] = make([]int, len(vexs))
}
for i := 0; i < len(vexs); i++ {
for j := 0; j < len(vexs); j++ {
if i == j {
G.Arc[i][j] = 0
} else {
G.Arc[i][j] = INF
}
}
}
G.NumVex = 0
G.NumEdge = 0
}
func WGCreat(G *Graph, vexs []VexType, edges []Edge) {
G.NumEdge = len(edges)
G.NumVex = len(vexs)
//顶点集
for i := 0; i < G.NumVex; i++ {
G.Vexes[i] = vexs[i]
}
//边集合
for _, edge := range edges {
v1index := VexIndex(vexs, edge.Vex1)
v2index := VexIndex(vexs, edge.Vex2)
G.Arc[v1index][v2index] = edge.Weight
G.Arc[v2index][v1index] = edge.Weight
}
}
算法实现:
//排序边
type IntEdge struct {
begin, end int
weight int
}
func MiniSpanTreekruskal(G *Graph) {
var IntEdges IntEdgeList
IntEdges = make([]IntEdge, 0)
parent := make([]int, G.NumVex)
//创建边集并排序
CreatEdges(G, &IntEdges)
for i := 0; i < G.NumVex; i++ {
//自己是自己的前驱
parent[i] = i
}
//循环遍历每一条边
for i := 0; i < G.NumEdge; i++ {
n := FindRoot(&parent, IntEdges[i].begin)
m := FindRoot(&parent, IntEdges[i].end)
//如果边的两端返回的都是尾部且相等,则说明当前边会把生成树闭环
if m != n {
//将此边的节点放入下标为起点的parent中,表示此顶点加入生成树集合
parent[n] = m
fmt.Printf("(%d,%d) %d\n", IntEdges[i].begin, IntEdges[i].end, IntEdges[i].weight)
}
}
//给IntEdges排序
}
// FindRoot 查找最小生成树的连线的尾部顶点下标
func FindRoot(parent *[]int, f int) int {
for (*parent)[f] != f {
f = (*parent)[f]
}
return f
}
func CreatEdges(G *Graph, intEdges *IntEdgeList) {
//给IntEdges赋值
for i := 0; i < G.NumVex; i++ {
for j := i; j < G.NumVex; j++ {
if G.Arc[i][j] != 0 && G.Arc[i][j] != INF {
edg := IntEdge{begin: i, end: j, weight: G.Arc[i][j]}
*intEdges = append(*intEdges, edg)
}
}
}
sort.Sort(*intEdges)
}
测试代码:
func MiniTree() {
//顶点集合
vexs := []VexType{"v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8"}
//边集合
edges := []Edge{
{"v0", "v1", 10},
{"v0", "v5", 11},
{"v1", "v2", 18},
{"v1", "v6", 16},
{"v1", "v8", 12},
{"v2", "v3", 22},
{"v2", "v8", 8},
{"v3", "v8", 21},
{"v3", "v6", 24},
{"v3", "v7", 16},
{"v3", "v4", 20},
{"v4", "v5", 26},
{"v4", "v7", 7},
{"v5", "v6", 17},
{"v6", "v7", 19},
}
var graph Graph
WGInit(&graph, vexs)
WGCreat(&graph, vexs, edges)
//最小生成树kruskal算法
MiniSpanTreekruskal(&graph)
}
输出:
(4,7) 7
(2,8) 8
(0,1) 10
(0,5) 11
(1,8) 12
(1,6) 16
(3,7) 16
(6,7) 19