大家好,我叫Prim,是一名电工,有一天我老板接了个给一个小村子通电线的活,这个村子一共有八户人家,老板说,就把你扔在这穷乡僻壤的干吧,我对你干活的进度,没啥要求,电线材料啥的管够,只要你每天保证一户人家通上电就行,每天保证一户人家通上电你就可以下班了,每天早干完早休息早抠手机。
老板给了我一张村子图纸:
为了使用数据描述图中的信息,我们使用矩阵(也就是二位数组来描述这个图)
v0-v8是村中的九户人家,中间连线是他们之间可以行的通的路,数字是通路的距离,但是你看v0到v6家因为山啊河啊的没法通路在小山村中也不奇怪,表示为∞
厂家最初给v0家通了电(谁知道为什么,可能他家天时地利人和吧),于是我就从v0开始干,开始往外扯电线:
刚来那天晚上(初始化),我住在v0家,对工作稍微计划了一下,于是我草草看了一下图,做了一个表格叫lowcost(最小工作量),这表长这样:
表0-0
对表的解释:
1、∞-在图中没有可以直接相通的路径,0-表示这家已经通电了肯定不用再干了。
2、针对每一家“没通电的户”依托“已经通电的户”中选工作量小的作为lowcost[i]的值。(今天只有v0家可以依托,因为v0家有电)
3、在lowcost[i]中选一个最小的工作量作为第二天的工作去干活,好早干完早收工休息。
另外以后万一电线坏了不通电了,为了方便维修,及时得知通电邻居(最近邻居的信息),我又制作了一个表示最近邻居的表叫adjvex,这表长这样:
表0-1
对表的解释:adjvex[i]表示i最近的邻居,也是i的通电邻居(表示V i家的电从V adjvex[i]中通来),反正还没开始时干活,我既然从v0家开始往外扯电线,所以明天不管谁家通电,他的通电的邻居(为了尽快干完活,肯定也是距离最近的邻居)肯定是v0,所已先把每家每户的通电邻居都设置为v0:
//初始化
lowcost[0] = 0
adjvex[0] = 0
for i := 1; i < G.NumVex; i++ {
lowcost[i] = G.Arc[0][i]
adjvex[i] = 0
}
(adjvex数组初始化)
总共九户人家,v0家已经通了电,还剩8户人家,所以我8天之内肯定能干完:
//整个大循环是构造最小生成树的过程(八天肯定能干完)
for i := 1; i < G.NumVex; i++ {......}
开工:
第一天(i=1):
拿出昨晚画的lowcost表一看(参看表0-0),比着表从左到右边扫面一遍:
min, j, k := INF, 1, 0 //min初始化为∞,实际值为999(用一个图中不可能出现的大数表示)
for j = 1; j < G.NumVex; j++ {
//lowcost[j]=0已经是最小生成树中的顶点,不参与权值的查找
if lowcost[j] != 0 && lowcost[j] < min {
min = lowcost[j]
//找到权值最小的顶点赋值给k
k = j
}
}
发现v0家已经通电(废话),还发现v1家离v0家最近,所以今天就从v0到v1家扯电线,10米的距离很轻松就干完了,干完今天的活以后,在图纸上做一下标记:
v1的最近的邻居是v0(adjvex[1]=0),然后告诉乡亲们,今天,v0家到v1家通电了:
fmt.Printf("(%d,%d)", adjvex[k], k) //k是代码中表示通电人家的临时变量
接下来,修改一下表格,计划一下明天的工作,今天就算完事了,那怎么修改呢,既然v1家已经通电,那到v1家就不用再有工作量了,所以lowcost[1]=0,而且,而且,现在可以依托v0,v1重新规划到其他每家每户(v2-v8)的工作量了,所以得看v1加入有电户以后,工作量计划lowcost[]的变化:
至此,如果要往v2、v6、v8家通电,必须要从v1家拉电线才会更近,因为到v2、v6、v8通电的工作量,都因V1家已经通电而变得更小,所以adjex[2]=1、adjex[8]=1、adjex[6]=1:
代码体现:
for j = 1; j < G.NumVex; j++ {
if lowcost[j] != 0 && G.Arc[k][j] < lowcost[j] {
lowcost[j] = G.Arc[k][j]
adjvex[j] = k
}
}
但是,但是,通过观察lowcost[]发现,最少的工作量,居然是往v5家拉电线,而且往v5家拉电线,并没有因为V1家的通电而变小,所以还是得从V0往V5家拉电线,所以第二天
第二天(i=2)
观察第一天的lowcost工作量表,11最小,所以往v5家拉电线:
干干干,干完画图纸:
用电户有v0、v1、v5,依托v0、v1、v5,更新工作量修改表格:
第三天(i=3)
观察第二天的lowcost工作量表12最小,所以往v8家拉电线:
干完画图纸:
用电户有v0、v1、v5,v8,依托v0、v1、v5,v8,更新工作量修改表格:
算了,不想画了,反正就是这个逻辑
第四五六七八天:
源代码:
类型定义:
package weightedgraph
const INF = 999
type VexType string
// 定义边
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
}
}
// VexIndex Index 返回数组下标
func VexIndex(strs []VexType, str VexType) int {
index := -1
for key, value := range strs {
if value == str {
index = key
}
}
return index
}
算法:
func MiniSpanTreePrim(G *Graph) {
adjvex := make([]int, G.NumVex) //保存相关顶点间边的权值点的下标
lowcost := make([]int, G.NumVex) //保存相关顶点间边的权值
lowcost[0] = 0 //初始化第一个权值为0,即V0加入生成树,谁下标等于0谁加入生成树
adjvex[0] = 0 //初始化第一个顶点下标为0
//初始化
for i := 1; i < G.NumVex; i++ {
lowcost[i] = G.Arc[0][i]
adjvex[i] = 0 //初始化都为v0的下标
}
//整个大循环是构造最小生成树的过程(八天肯定能干完)
for i := 1; i < G.NumVex; i++ {
min, j, k := INF, 1, 0
for j = 1; j < G.NumVex; j++ {
//lowcost[j]=0已经是最小生成树中的顶点,不参与权值的查找
if lowcost[j] != 0 && lowcost[j] < min {
min = lowcost[j]
//找到权值最小的顶点赋值给k
k = j
}
}
//每次输出一个最小生成树的边
fmt.Printf("(%d,%d)", adjvex[k], k)
lowcost[k] = 0
for j = 1; j < G.NumVex; j++ {
if lowcost[j] != 0 && G.Arc[k][j] < lowcost[j] {
lowcost[j] = G.Arc[k][j]
adjvex[j] = k
}
}
}
}
测试代码:
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)
//最小生成树pirm算法
MiniSpanTreePrim(&graph)
}
输出结果:
(0,1)(0,5)(1,8)(8,2)(1,6)(6,7)(7,4)(7,3)