给定一个由0,1组成的二维数组,1为通,0不通,考虑上下左右为通的可以连通在一起,问连通块个数。
三种解法,bfs, dfs, union_find。
bfs用队列实现,为1的入队。队头出队,它上下左右如果有为1的并且没出界的依次入队,直到队列为空。进入过队列的原地置0来防止再次进队。m*n的二维数组每一个点,依次遍历,如果为1就进行bfs。
进行bfs的次数是连通块的个数,未遍历到的点可能在之前的bfs中被置0,所以此法可行。
dfs用递归实现,如果是1,原地置0,对它的上下左右是1并没出界的点进行递归调用,递归函数内对当前的点置0,递归出口是递归函数最后一行的return语句。m*n的二维数组每个点遍历一遍,1的个数是联通块的个数,此前dfs可能对未遍历的点置0,所以此法可行。
union_find,union是连接两个点,find是找根。union先分别找两个点的根,如果两个点的根不同,将第一个点的根的父亲置为第二个点的根,如果两个点的根相同不做处理。find用递归实现,如果当前点的父亲是本身直接返回当前的点,如果不是,将当前点的父亲置为当前点的父亲的根(这里递归调用find),返回当前点的父亲。总的1的个数在union时如果两个点的根不同就减1,最后的数就是联通块个数。
时间复杂度:
bfs和dfs:
使用邻接表时,BFS/DFS的时间复杂度为O(V + E);使用邻接矩阵时,BFS/DFS的时间复杂度为O(V^2),其中V代表顶点,E代表边。在本题,输入是由01组成的邻接矩阵,所以时间复杂度是顶点个数mn。
union_find:
当既没有路径压缩也没union_by_rank时是O(n),n是节点个数。
当仅有路径压缩时,O(n+f(1+log_(2+f/n) n)。
当仅有union_by_rank时, O(mlog n),m是查找次数。
当路径压缩和union_by_rank同时使用时,O(m*alpha(n)),m是查找次数,alpha(n)<=4。
class Solution:
"""
@param grid: a boolean 2D matrix
@return: an integer
"""
def in_bound(self, grid, i, j):
return 0<=i<len(grid) and 0<=j<len(grid[0])
def bfs(self, grid, i, j):
direction = [(0,1), (1,0), (0,-1), (-1,0)]
grid[i][j] = 0
queue_ = []
queue_.append((i,j))
while(queue_):
x, y = queue_.pop(0)
for dx, dy in direction:
x_ = x+dx
y_ = y+dy
if self.in_bound(grid, x_, y_) and grid[x_][y_]:
grid[x_][y_] = 0
queue_.append((x_, y_))
def numIslands(self, grid): # bfs
# write your code here
# union find solution
ans = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j]:
self.bfs(grid, i, j)
ans += 1
return ans
class Solution:
"""
@param grid: a boolean 2D matrix
@return: an integer
"""
def in_bound(self, grid, i, j):
return 0<=i<len(grid) and 0<=j<len(grid[0])
def dfs(self, grid, i, j):
grid[i][j] = 0
direction = [(0, 1), (1, 0), (0, -1), (-1, 0)]
for dx, dy in direction:
x_ = i+dx
y_ = j+dy
if self.in_bound(grid, x_, y_) and grid[x_][y_]:
self.dfs(grid, x_, y_)
def numIslands(self, grid): # dfs
# write your code here
# union find solution
ans = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j]:
self.dfs(grid, i, j)
ans += 1
return ans
union_find:只有路径压缩。
class Solution:
"""
@param grid: a boolean 2D matrix
@return: an integer
"""
def numIslands(self, grid):
# write your code here
# union find solution
if not grid or not grid[0]:
return 0
self.parent = {}
self.size = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j]:
self.parent[(i, j)] = (i, j)
self.size += 1
for x in range(len(grid)):
for y in range(len(grid[0])):
if grid[x][y]:
for delta_x, delta_y in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
x_ = x + delta_x
y_ = y + delta_y
if self.is_inbound(grid, x_, y_) and grid[x_][y_]:
self.union((x, y), (x_, y_))
return self.size
def union(self, a, b):
root_a = self.find(a)
root_b = self.find(b)
if root_a != root_b:
self.parent[root_a] = root_b
self.size -= 1
def find(self, point):
if point == self.parent[point]:
return point
self.parent[point] = self.find(self.parent[point])
return self.parent[point]
def is_inbound(self, grid, x_, y_):
return 0 <= x_ < len(grid) and 0 <= y_ < len(grid[0])
union_find 的改进, 因为只要没漏掉任意两对节点的关系就可以,遍历的时候,若当前节点为1,那么只用右下走就行了,当然要考虑右下分别是否为1和分别是否越界。
class Solution:
"""
@param grid: a boolean 2D matrix
@return: an integer
"""
parent = {}
def in_bound(self, grid, i, j):
return 0<=i<len(grid) and 0<=j<len(grid[0])
def root(self, a):
if a == self.parent[a]:
return a
self.parent[a] = self.root(self.parent[a])
return self.parent[a]
def union(self, a, b):
root_a = self.root(a)
root_b = self.root(b)
if root_a != root_b:
self.parent[root_b] = root_a
self.size -= 1
def numIslands(self, grid): # union_find
# write your code here
# union find solution
if not grid:
return 0
self.size = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j]:
self.size += 1
self.parent[(i, j)] = (i, j)
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j]:
for dx, dy in [(0, 1), (1, 0)]:
x_ = i+dx
y_ = j+dy
if self.in_bound(grid, x_, y_) and grid[x_][y_]:
self.union((i, j), (x_, y_))
return self.size
union_find 路径压缩和union_by_rank同时使用。union_by_rank操作的地方是初始化为0,union时确定父子关系的两个节点的根的rank的比较,以及确定父子关系后父亲的rank加1,共三个地方。
class Solution:
"""
@param grid: a boolean 2D matrix
@return: an integer
"""
def numIslands(self, grid):
# write your code here
# union find solution
if not grid or not grid[0]:
return 0
self.parent = {}
self.rank = {}
self.size = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j]:
self.parent[(i, j)] = (i, j)
self.rank[(i, j)] = 0
self.size += 1
for x in range(len(grid)):
for y in range(len(grid[0])):
if grid[x][y]:
for delta_x, delta_y in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
x_ = x + delta_x
y_ = y + delta_y
if self.is_inbound(grid, x_, y_) and grid[x_][y_]:
self.union((x, y), (x_, y_))
return self.size
def union(self, a, b):
root_a = self.find(a)
root_b = self.find(b)
if root_a != root_b:
if self.rank[root_a]>self.rank[root_b]:
self.parent[root_b] = root_a
elif self.rank[root_b]>self.rank[root_a]:
self.parent[root_a] = root_b
else:
self.parent[root_b] = root_a
self.rank[root_a] += 1
self.size -= 1
def find(self, point):
if point == self.parent[point]:
return point
self.parent[point] = self.find(self.parent[point])
return self.parent[point]
def is_inbound(self, grid, x_, y_):
return 0 <= x_ < len(grid) and 0 <= y_ < len(grid[0])
# if __name__ == "__main__":
# m = 5
# a = [[1,0,1,0]for i in range(3)]
# b = [[0,1,0,0]for i in range(m-3)]
# a.extend(b) # a.extend(b)返回None,a 原地extend。 或者a+=b或者a=a+b
# # print(a)
# # a[0][0]=0
# # print(a)
# s = Solution()
# print(s.numIslands(a))