算法题:最小生成树(Kruskal算法,Prim算法)

并查集

思想:用集合中的一个元素代表集合
操作:
合并:把两个不想交的集合合并为一个集合。
查询:查询两个元素是否在同一集合中。

题目:
冗余连接(leetcode 684)
输入一个图,该图由一个有着N个节点(节点不重复1,2,…,N)的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组。每一个边的元素是一对[u,v],满足u<v,表示连接顶点u和v的无向图的边。

返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边[u,v]应满足相同的格式u < v。

分析:
如果两个节点有相同的根,则连接这两个节点会导致环的出现,所以我们必须删除这条边。所以我们只需要找到两个根相同的节点,删除它们的边及可。

def findRedundantConnection(self,edges):
	#随机生成多个0,越多越好保存边
	pre = [0] * 1001
	for edge in edges:
		x = self.find(edge[0],pre)
		y = self.find(edge[1],pre)
		#如果它们的根节点不相等,就合并,如果相等就是要删除的边,因为本身x就要指向y
		#然后它们又有相同的节点,所以它们就形成了环
		if x != y:
			pre[y] = x
		else:
			return edge

def find(self,node,pre):
	while pre[node] != 0:
		node = pre[node]
	return node

最小生成树问题

所谓最小生成树,就是一个图的极小连通子图,它包含原图的所有顶点,并且所有边的权值之和尽可能小。(不需要回路的)

例子:
下面绿色加粗的边可以把所有顶点连接起来,又保证了边的权值之和最小保留绿色的边就是一颗最小生成树。
注意:
1.包含所有节点
2.所有边权值和最小
3.图的极小连通子图不需要回路,而是一个树形结构,所以人们才把它叫做最小生成树。图的最小生成树不唯一,可能一个图对应多个最小生成树。在这里插入图片描述
kruskal算法(采用了并查集的思想):
以图的边为基础
1.新建图G,G中拥有原图中相同的结点,但是没有边。
2.将原图中所有边按照权值从小到大排序。
3.从权值最小的边开始,如果这条边连接的两个节点不在一个连通分量中(也就是不形成环,就跟上面那个一样,如果这条边的两个节点的父节点相同,就会形成环。),则添加这条边到图G中。
4.重复3,直至图G中所有的节点都在同一个连通分量中。

拿上面那个图打比方,先把权值最小的边1拿下来,让4指向2(这个无所谓,只要不在同一个连通分量即可)。然后把权值第二小的边3拿下来,让0指向2。然后把权值第三小的边4拿下来,让1指向0.然后把第四小的边7拿下来,让3指向1。

题目(leetcode 1135,最低成本联通所有城市):
想象一下你是个城市基建规划者,地图上有 N 座城市,它们按以 1 到 N 的次序编号。
给你一些可连接的选项 conections,其中每个选项 conections[i] = [city1, city2, cost] 表示将城市 city1 和城市 city2 连接所要的成本。(连接是双向的,也就是说城市 city1 和城市 city2 相连也同样意味着城市 city2 和城市 city1 相连)。
返回使得每对城市间都存在将它们连接在一起的连通路径(可能长度为 1 的)最小成本。该最小成本应该是所用全部连接代价的综合。如果根据已知条件无法完成该项任务,则请你返回 -1。

输入:N = 3, conections = [[1,2,5],[1,3,6],[2,3,1]]
输出:6

题解:这就是一个最小生成树的问题。

def minimumCost(self,N,connections):
	if len(connections) < N - 1:
		return -1
	connections.sort(key = lambda a : a[2])
	parent = [i for i in range(N)]
	#找节点的父节点
	def find(x):
		if x != parent[x]:
			parent[x] = find(parent[x])
		return parent[x]
	res,e,k = 0,0,0
	while e < N -1:
		u,v,w = connections[k]
		#k就可以控制舍去在同一个连通分量中
		k += 1
		#因为parent数组是从0开始的
		x,y = find(u-1),find(v-1)
		if x != y:
			e += 1
			res += w
			parent[x] = y
	return res

Prim算法

这个算法是以图的顶点为基础
从一个初始顶点开始,寻找触达其他顶点权值最小的边,并把该顶点加入到已触达顶点的集合中。当全部顶点都加入到集合时,算法的工作就完成了。Prim算法的本质是基于贪心算法。

V表示节点集合,E表示边集合
V1表示最小生成树所包含的节点,V2表示未包含节点
初始化时V1为空,V2 = V(节点集合)
当V2为空,V1 = V时算法结束

步骤:
1.随机选择一个节点v加入到V1,并从V2中删除v
2.选择边权最小的<u,v>,其中u属于V1,v属于V2,将v从V2移动到V1
3.重复2步骤直到V2为空

同一题的解答:

def minimumCost(self,N,connections):
	if len(connections) < N - 1:
		return -1
	graph = {}
	for u,v,w in connections:
		graph[(u,v)] = w
	#随机选了1节点加入到V1(now)
	now = [1]
	res = 0
	#需要把N-1个节点加入到V1(now)中,所以要执行N-1次
	for i in range(N-1):
		minNode = 0
		minDis = float('inf')
		#j就是想要加入到V1中的节点
		for j in range(N+1):
			#在已经加入V1的节点中选出权值最小的那个边,所以要遍历完整个now数组
			for x in now:
				if j not in now and (x,y) in graph.key():
					if graph[(x,j)] < minDis:
						minDis = graph[(x,y)]
						minNode = j
		res += minDis
		now.append(minNode)
	return res
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值