最小生成树-Prim算法

前言

书接上回,我们写了个无向图的广度优先遍历的代码

用邻接矩阵表示无向图,并做广度优先遍历(golang)-CSDN博客文章浏览阅读557次,点赞8次,收藏8次。上学没好好学,只能问问度娘,然后度娘给出的答案全是文字。。。我在这里画个图描述一下吧。简单点来说就是用二维数组来表达 一个图 上 各个点之间的连接情况,这里我们用无向图来举例可以看到图中有ABCDEF六个点,我们先忽略其中的连线,假设六个点互不相连,那么我们就用如下二维数组来表示0就代表两个点之间没有连线,本文不考虑A点和A点自己连线这种情况先。https://blog.csdn.net/woaixuejinglzq/article/details/142831038?spm=1001.2014.3001.5502然后今天,去写一下Prim算法,我们用的图结构,还是上面文章的,继续用邻接矩阵来表达图,但是对于不存在的线,前文用0来表达,这里我们使用mat.MaxInt,具体为啥,我会在代码注释中表达呢

package main
 
 
type Graph struct {
	dots   []string //	点
	matrix [][]int  //	邻接矩阵
}
 

最小生成树 

 什么是生成树?

图论:一个连通无向图的生成树是通过移除一些边得到的树形结构,它包含图中的所有顶点,但没有回路。

什么是最小生成树?

是指在所有可能的生成树中,边的权值和最小的那棵树

注意点:

对于有n个点的连通图来说,它的生成树有n-1条边,大于n-1必然有回路,小于n - 1 必然不连通

Prim算法

原理

将图中的点分为两类,一类是已经遍历的点(A类),一类是未被遍历的点(B类),我们每一次都找和A类中的点   相连的B类点中  边权值最小的点,然后将该点加入A类,直到所有点都被遍历一次,每一次都找新的点必然不会有回路出现,每一次找权值最小的线,必然是总权值最小

画图解释

先画一个连通图,我们从A点开始遍历

 1、与A相邻的点有B、C、F三个点,其中最小的是AB线段,AB线段成为树中的一条边

2、和A、B两点相连的所有点中 F、C、D,最短的是 AC、AF,我们取哪个都可以的,因为如果AF比后面的线短,早晚还得加进最小生成树,取AC先

3、和ABC三点相连的所有点中F、D、E,权重最小的是AF

4、下一个是CD

5、下一个是DE

至此,最小生成树完成如下

代码

package main

import (
	"container/list"
	"fmt"
	"math"
)

type Graph struct {
	dots   []string //	点
	matrix [][]int  //	邻接矩阵
}

func InitGraph(graph *Graph) error {
	dotsNum := len(graph.dots)
	if dotsNum == 0 {
		return fmt.Errorf("no dots")
	}
	// 建立矩阵,初始值都为0,意为,现在各个点互相不连接
	graph.matrix = make([][]int, dotsNum)
	for i := 0; i <= dotsNum-1; i++ {
		graph.matrix[i] = make([]int, dotsNum)
		for j := 0; j <= dotsNum-1; j++ {
			graph.matrix[i][j] = math.MaxInt
		}
	}
	// AB
	graph.matrix[0][1] = 1
	graph.matrix[1][0] = 1

	// BD
	graph.matrix[1][3] = 5
	graph.matrix[3][1] = 5

	// AC
	graph.matrix[0][2] = 2
	graph.matrix[2][0] = 2

	// AF
	graph.matrix[0][5] = 2
	graph.matrix[5][0] = 2

	// CD
	graph.matrix[2][3] = 3
	graph.matrix[3][2] = 3

	// CE
	graph.matrix[2][4] = 4
	graph.matrix[4][2] = 4

	// CF
	graph.matrix[2][5] = 5
	graph.matrix[5][2] = 5

	// EF
	graph.matrix[4][5] = 6
	graph.matrix[5][4] = 6

	// DE
	graph.matrix[3][4] = 3
	graph.matrix[4][3] = 3


	return nil
}

func ShowGraph(graph *Graph) {
	for i := 0; i <= len(graph.dots)-1; i++ {
		fmt.Println(graph.matrix[i])
	}
}

func prim(graph *Graph) {
	// 总点数
	dotCount := len(graph.dots)
	// graph.dots中的下标
	startPointIndex := 0
	// 最小生成树总权重
	sumWeight := 0
	// 最小生成树线集合
	lines := []string{}
	// 值:代表当前已遍历点 所连接 未被遍历点 的线 的权重,
	// 数组下标:就代表着graph.dots中的下标,代表着未被访问且与已被相连的点的下标 和 不相连的点(weightRecord中为0)
	weightRecord := make([]int, dotCount)
	// 值:已经被遍历的点,数组的值就是graph.dots中的下标,代表着已经被访问到的点的下标
	// 下标:数组的下标也代表 graph.dots中的下标,代表着未被访问且与已被相连的点的下标 和 不相连的点(visitList中为-1)
	visitList := make([]int, dotCount)

	//先从下标为0的开始,这里也就是A点
	for i := 0; i <= dotCount-1; i++ {
		// 将所有与A相连的点赋值,如果不相连就是 -1
		weightRecord[i] = graph.matrix[startPointIndex][i]
		// 全是0,代表A点
		visitList[i] = 0
	}
	weightRecord[0] = -1
	// 每个点都遍历一次,A已经被遍历过了,所以做 dotCount - 1次循环
	// 如果要走到dotCount次,weightRecord都变成-1 结果包是 *A,多一条线会
	for i := 1; i <= dotCount-1; i++ {
		minWeightNow := math.MaxInt
		nextDotIndex := 0
		for j := 0; j <= dotCount-1; j++ {
			// weightRecord为0 代表着没有连线
			if weightRecord[j] != -1 && weightRecord[j] < minWeightNow {
				minWeightNow = weightRecord[j]
				nextDotIndex = j
			}
		}
		sumWeight += weightRecord[nextDotIndex]

		lines = append(lines, graph.dots[visitList[nextDotIndex]]+graph.dots[nextDotIndex])
		// fmt.Println(nextDotIndex)
		weightRecord[nextDotIndex] = -1
		// 更新weightRecord 和 visitList
		for j := 0; j < dotCount; j++ {
			// 这里解释一下
			// 我们假设与nextDotIndex相连的已被遍历的节点为A
			// 假设nextDotIndex为B点
			// 假设与nextDotIndex相连未被遍历的节点为C
			// 这里有三种情况
			// 1、A和C不相连,那么好说,直接越过就好了,本身不相连的点在矩阵中也是math.MaxInt,也不需要多余判断,
			// 我们就不改weightRecord 和 visitList
			// 2、如果A和C相连,且AC权重大于BC,那么我们舍弃AC就好了,记录下来BC,更改weightRecord 和 visitList
			// 这里我们将visitList中的C的下标的值 改为 B的下标值(dots数组的下标)
			// 3、如果A和B相连,且AC权重小于BC,我们就不改weightRecord 和 visitList
			if weightRecord[j] != -1 && graph.matrix[nextDotIndex][j] < weightRecord[j] {
				weightRecord[j] = graph.matrix[nextDotIndex][j]
				visitList[j] = nextDotIndex
			}

		}
		// fmt.Println(weightRecord)
		// fmt.Println(visitList)
		// break
	}

	fmt.Println("sum:", sumWeight)
	fmt.Println(lines)
}

func main() {
	graph := &Graph{}
	graph.dots = []string{"A", "B", "C", "D", "E", "F"}

	InitGraph(graph)
	ShowGraph(graph)
	prim(graph)
}

最后

小弟太菜,写的可能又臭又长,还求大佬们多多指导QAQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值