Prim的故事(一文搞懂Prim算法)

大家好,我叫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)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值