并查集
思想:用集合中的一个元素代表集合
操作:
合并:把两个不想交的集合合并为一个集合。
查询:查询两个元素是否在同一集合中。
题目:
冗余连接(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