岛屿问题——DFS、BFS
重刷了一遍之前在代码随想录中的每日打卡时的岛屿问题,发现之前写的好多💩山代码,感兴趣的可以看看D56、D57、D58这几篇,不过可以看出写dfs与bfs确实是有进步与理解加深了,一大幸事。
有什么建议或者错误欢迎随时提出。
也欢迎你们学习下面的七道岛屿问题,若有问题或者需要交流,随时可评论区或私信。
岛屿数量
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而成,并且四周都是水域。你可以假设矩阵外均被水包围。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。
后续 N 行,每行包含 M 个数字,数字为 1 或者 0。
输出描述
输出一个整数,表示岛屿的数量。如果不存在岛屿,则输出 0。
输入示例
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例
3
提示信息
根据测试案例中所展示,岛屿数量共有 3 个,所以输出 3。
数据范围:
1 <= N, M <= 50
深搜版
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
# 记录访问过的陆地
visited=[[False for _ in range(m)] for _ in range(n)]
directions=[[-1,0],[0,-1],[0,1],[1,0]] # dfs中上下左右四个方向
def dfs(i,j):
# 递归终止条件
if grid[i][j]=='0' or visited[i][j]==True:
return
visited[i][j]=True #设置为已访问
# 遍历四个方向
for dire in directions:
next_x,next_y=dire[0]+i,dire[1]+j # 下一个方向的下标
if 0<=next_x<n and 0<=next_y<m:
dfs(next_x,next_y)
ans=0
for i in range(n):
for j in range(m):
if grid[i][j]=='1' and visited[i][j]==False:
ans+=1
dfs(i,j)
print(ans)
广搜版
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
# 记录访问过的陆地
visited=[[False for _ in range(m)] for _ in range(n)]
directions=[[-1,0],[0,-1],[0,1],[1,0]] # bfs中上下左右四个方向
from collections import deque
def bfs(i,j):
visited[i][j]=True
while que:
x,y=que.popleft()
for dire in directions:
next_x,next_y=x+dire[0],y+dire[1]
if 0<=next_x<n and 0<=next_y<m and grid[next_x][next_y]=='1' and visited[next_x][next_y]==False:
que.append([next_x,next_y])
# 加入队列就标记走过
visited[next_x][next_y]=True
ans=0
for i in range(n):
for j in range(m):
if grid[i][j]=='1' and visited[i][j]==False:
que=deque([[i,j]])
bfs(i,j)
ans+=1
print(ans)
岛屿的最大面积
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,计算岛屿的最大面积。岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿由水平方向或垂直方向上相邻的陆地连接而成,并且四周都是水域。你可以假设矩阵外均被水包围。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。后续 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。
输出描述
输出一个整数,表示岛屿的最大面积。如果不存在岛屿,则输出 0。
输入示例
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例
4
提示信息
样例输入中,岛屿的最大面积为 4。
数据范围:
1 <= M, N <= 50。
深搜版
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
# 记录访问过的陆地
visited=[[False for _ in range(m)] for _ in range(n)]
directions=[[-1,0],[0,-1],[0,1],[1,0]]
def dfs(i,j):
global area
# 只要遇到水或者访问过的陆地就终止
if grid[i][j]=='0' or visited[i][j]==True:
return
visited[i][j]=True
area+=1
for dire in directions:
next_x,next_y=i+dire[0],j+dire[1]
if 0<=next_x<n and 0<=next_y<m:
dfs(next_x,next_y)
ans=0
for i in range(n):
for j in range(m):
if grid[i][j]=='1' and visited[i][j]==False:
area=0
dfs(i,j)
ans=max(ans,area)
print(ans)
广搜版
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
# 记录访问过的陆地
visited=[[False for _ in range(m)] for _ in range(n)]
directions=[[-1,0],[0,-1],[0,1],[1,0]]
from collections import deque
def bfs(i,j):
global area
visited[i][j]=True
while que:
x,y=que.popleft()
area+=1
for dire in directions:
next_x,next_y=x+dire[0],y+dire[1]
if 0<=next_x<n and 0<=next_y<m and grid[next_x][next_y]=='1'and visited[next_x][next_y]==False:
que.append([next_x,next_y])
visited[next_x][next_y]=True
ans=0
for i in range(n):
for j in range(m):
if grid[i][j]=='1' and visited[i][j]==False:
area=0
que=deque([[i,j]])
bfs(i,j)
ans=max(ans,area)
print(ans)
孤岛的总面积
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿指的是由水平或垂直方向上相邻的陆地单元格组成的区域,且完全被水域单元格包围。孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。
现在你需要计算所有孤岛的总面积,岛屿面积的计算方式为组成岛屿的陆地的总数。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0。
输出描述
输出一个整数,表示所有孤岛的总面积,如果不存在孤岛,则输出 0。
输入示例
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例
1
提示信息
在矩阵中心部分的岛屿,因为没有任何一个单元格接触到矩阵边缘,所以该岛屿属于孤岛,总面积为 1。
数据范围:
1 <= M, N <= 50。
深搜版
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
# 记录访问过的陆地
visited=[[False for _ in range(m)] for _ in range(n)]
directions=[[-1,0],[0,-1],[0,1],[1,0]]
def dfs(i,j):
global flag,area
if visited[i][j]==True or grid[i][j]=='0':
return
area+=1
visited[i][j]=True
for dire in directions:
next_x,next_y=i+dire[0],j+dire[1]
if next_x<0 or next_y<0 or next_x>=n or next_y>=m:
flag=False
continue
dfs(next_x,next_y)
ans=0
for i in range(n):
for j in range(m):
if grid[i][j]=='1' and visited[i][j]==False:
area=0
flag=True # 标记是不是孤岛
dfs(i,j)
if flag:
ans+=area
print(ans)
广搜版
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
# 记录访问过的陆地
visited=[[False for _ in range(m)] for _ in range(n)]
directions=[[-1,0],[0,-1],[0,1],[1,0]]
from collections import deque
def bfs(i,j):
global flag,area
visited[i][j]=True
while que:
x,y=que.popleft()
area+=1
for dire in directions:
next_x,next_y=x+dire[0],y+dire[1]
if next_x<0 or next_y<0 or next_x>=n or next_y>=m:
flag=False
continue
if grid[next_x][next_y]=='1' and visited[next_x][next_y]==False:
que.append([next_x,next_y])
visited[next_x][next_y]=True
ans=0
for i in range(n):
for j in range(m):
if grid[i][j]=='1' and visited[i][j]==False:
area=0
flag=True # 标记是不是孤岛
que=deque([[i,j]])
bfs(i,j)
if flag:
ans+=area
print(ans)
沉没孤岛
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿指的是由水平或垂直方向上相邻的陆地单元格组成的区域,且完全被水域单元格包围。孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。
现在你需要将所有孤岛“沉没”,即将孤岛中的所有陆地单元格(1)转变为水域单元格(0)。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。
之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。
输出描述
输出将孤岛“沉没”之后的岛屿矩阵。 注意:每个元素后面都有一个空格
输入示例
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例
1 1 0 0 0
1 1 0 0 0
0 0 0 0 0
0 0 0 1 1
提示信息
将孤岛沉没。
数据范围:
1 <= M, N <= 50。
深搜版
# 将边缘的陆地标记为2,再重新遍历一遍岛屿.为1的就是孤岛,改成0,再把2改为1
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
visited=[[False for _ in range(m)] for _ in range(n)]
directions=[[-1,0],[0,-1],[0,1],[1,0]]
# 将边缘的岛屿标记为2
def dfs(i,j):
if visited[i][j]==True or grid[i][j]!='1':
return
visited[i][j]=True
grid[i][j]='2'
for dire in directions:
next_x,next_y=i+dire[0],j+dire[1]
if 0<=next_x<n and 0<=next_y<m:
dfs(next_x,next_y)
# 标记左边缘和右边缘的岛屿
for i in range(n):
dfs(i,0)
dfs(i,m-1)
# 标记上边缘和下边缘的岛屿
for j in range(m):
dfs(0,j)
dfs(n-1,j)
# 将标记为2的岛屿改为1,标记为1的岛屿改为0
for i in range(n):
for j in range(m):
if grid[i][j]=='2':
grid[i][j]='1'
elif grid[i][j]=='1':
grid[i][j]='0'
# 打印
for i in grid:
print(' '.join(i))
广搜版
# 将边缘的陆地标记为2,再重新遍历一遍岛屿.为1的就是孤岛,改成0,再把2改为1
from collections import deque
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
visited=[[False for _ in range(m)] for _ in range(n)]
directions=[[-1,0],[0,-1],[0,1],[1,0]]
# 将边缘的岛屿标记为2
def bfs(i,j):
que=deque([[i,j]])
visited[i][j]=True
grid[i][j]='2'
while que:
x,y=que.popleft()
for dire in directions:
next_x,next_y=x+dire[0],y+dire[1]
if 0<=next_x<n and 0<=next_y<m and grid[next_x][next_y]=='1' and visited[next_x][next_y]==False:
que.append([next_x,next_y])
visited[next_x][next_y]=True
grid[next_x][next_y]='2'
# 标记左边缘和右边缘的岛屿
for i in range(n):
if grid[i][0]=='1' and visited[i][0]==False:
bfs(i,0)
if grid[i][m-1]=='1' and visited[i][m-1]==False:
bfs(i,m-1)
# 标记上边缘和下边缘的岛屿
for j in range(m):
if grid[0][j]=='1' and visited[0][j]==False:
bfs(0,j)
if grid[n-1][j]=='1' and visited[n-1][j]==False:
bfs(n-1,j)
# 将标记为2的岛屿改为1,标记为1的岛屿改为0
for i in range(n):
for j in range(m):
if grid[i][j]=='2':
grid[i][j]='1'
elif grid[i][j]=='1':
grid[i][j]='0'
# 打印
for i in grid:
print(' '.join(i))
水流问题
题目描述
现有一个 N × M 的矩阵,每个单元格包含一个数值,这个数值代表该位置的相对高度。矩阵的左边界和上边界被认为是第一组边界,而矩阵的右边界和下边界被视为第二组边界。
矩阵模拟了一个地形,当雨水落在上面时,水会根据地形的倾斜向低处流动,但只能从较高或等高的地点流向较低或等高并且相邻(上下左右方向)的地点。我们的目标是确定那些单元格,从这些单元格出发的水可以达到第一组边界和第二组边界。
输入描述
第一行包含两个整数 N 和 M,分别表示矩阵的行数和列数。
后续 N 行,每行包含 M 个整数,表示矩阵中的每个单元格的高度。
输出描述
输出共有多行,每行输出两个整数,用一个空格隔开,表示可达第一组边界和第二组边界的单元格的坐标,输出顺序任意。
输入示例
5 5
1 3 1 2 4
1 2 1 3 2
2 4 7 2 1
4 5 6 1 1
1 4 1 2 1
输出示例
0 4
1 3
2 2
3 0
3 1
3 2
4 0
4 1
提示信息
图中的蓝色方块上的雨水既能流向第一组边界,也能流向第二组边界。所以最终答案为所有蓝色方块的坐标。
数据范围:
1 <= M, N <= 100。
深搜版
n,m=map(int,input().split())
grid=[list(map(int,input().split())) for _ in range(n)]
directions=[[-1,0],[0,1],[1,0],[0,-1]]
def dfs(i,j):
global flag1,flag2
# 剪枝,相当于如果遍历到的节点如果在答案中,说明一定可以流向两组边界
if [i,j] in ans:
flag1,flag2=True,True
return
# 访问过,返回
if visited[i][j]:
return
visited[i][j]=True
# 这两个节点是两组边界的交叉点
if i==0 and j==m-1 or i==n-1 and j==0:
flag1,flag2=True,True
return
if i==0 or j==0:
flag1=True
if i==n-1 or j==m-1:
flag2=True
for dx,dy in directions:
next_x,next_y=i+dx,j+dy
if 0<=next_x<n and 0<=next_y<m and grid[next_x][next_y]<=grid[i][j]:
dfs(next_x,next_y)
ans=[]
for i in range(n):
for j in range(m):
visited=[[False]*m for _ in range(n)]
flag1,flag2=False,False # 能不能留到两组边界
dfs(i,j)
if flag1 and flag2:
ans.append([i,j])
for i,j in ans:
print(i,j)
广搜版
n,m=map(int,input().split())
grid=[list(map(int,input().split())) for _ in range(n)]
directions=[[-1,0],[0,1],[1,0],[0,-1]]
from collections import deque
def bfs(i,j):
global flag1,flag2
visited[i][j]=True
que=deque([[i,j]])
while que:
x,y=que.popleft()
if [x,y] in ans:
flag1,flag2=True,True
break
if x==0 or y==0:
flag1=True
if x==n-1 or y==m-1:
flag2=True
for a,b in directions:
next_x,next_y=a+x,b+y
if 0<=next_x<n and 0<=next_y<m and visited[next_x][next_y]==False and grid[next_x][next_y]<=grid[x][y]:
que.append([next_x,next_y])
visited[next_x][next_y]=True
ans=[]
for i in range(n):
for j in range(m):
visited=[[False]*m for _ in range(n)]
flag1,flag2=False,False # 能不能流到两组边界
bfs(i,j)
if flag1 and flag2:
ans.append([i,j])
for i,j in ans:
print(i,j)
建造最大岛屿
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,你最多可以将矩阵中的一格水变为一块陆地,在执行了此操作之后,矩阵中最大的岛屿面积是多少。
岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿是被水包围,并且通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设矩阵外均被水包围。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。
输出描述
输出一个整数,表示最大的岛屿面积。
输入示例
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例
6
提示信息
对于上面的案例,有两个位置可将 0 变成 1,使得岛屿的面积最大,即 6。
数据范围:
1 <= M, N <= 50。
深搜版
# 每次将每一块水域标记成陆地,再计算以这块陆地延申的最大面积
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
directions=[[-1,0],[1,0],[0,1],[0,-1]]
def dfs(i,j):
global area
if visited[i][j]==True or grid[i][j]=='0':
return
visited[i][j]=True
area+=1
for x,y in directions:
nx,ny=i+x,j+y
if 0<=nx<n and 0<=ny<m:
dfs(nx,ny)
ans=0
# 标记有没有水域,如果没有,则进不入循环,最大面积会被计算为零,所以需要标记如果没有进入循环,则最大面积则是n*m
flag=False
for i in range(n):
for j in range(m):
area=0
visited=[[False]*m for _ in range(n)]
if grid[i][j]=='0':
flag=True
grid[i][j]='1' # 改为陆地
dfs(i,j)
ans=max(area,ans)
grid[i][j]='0' # 计算完还原回来
# 如果这个地图没有一块水域
if flag==False:
ans=n*m
print(ans)
广搜版
from collections import deque
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
directions=[[-1,0],[1,0],[0,1],[0,-1]]
def bfs(i,j):
global area
que=deque([[i,j]])
visited[i][j]=True
while que:
cx,cy=que.popleft()
area+=1
for x,y in directions:
nx,ny=cx+x,cy+y
if 0<=nx<n and 0<=ny<m and visited[nx][ny]==False and grid[nx][ny]=='1':
que.append([nx,ny])
visited[nx][ny]=True
ans=0
flag=False
for i in range(n):
for j in range(m):
area=0
visited=[[False]*m for _ in range(n)]
if grid[i][j]=='0':
flag=True
grid[i][j]='1'
bfs(i,j)
ans=max(area,ans)
grid[i][j]='0'
if flag==False:
ans=n*m
print(ans)
岛屿的周长
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿是被水包围,并且通过水平方向或垂直方向上相邻的陆地连接而成的。
你可以假设矩阵外均被水包围。在矩阵中恰好拥有一个岛屿,假设组成岛屿的陆地边长都为 1,请计算岛屿的周长。岛屿内部没有水域。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。
输出描述
输出一个整数,表示岛屿的周长。
输入示例
5 5
0 0 0 0 0
0 1 0 1 0
0 1 1 1 0
0 1 1 1 0
0 0 0 0 0
输出示例
14
提示信息
岛屿的周长为 14。
数据范围:
1 <= M, N <= 50。
深搜版
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
directions=[[-1,0],[1,0],[0,1],[0,-1]]
visited=[[False]*m for _ in range(n)]
def dfs(i,j):
global ans
# 边界外或者遇到水了周长就加一
if i<0 or j<0 or i>=n or j>=m or grid[i][j]=='0':
ans+=1
return
if grid[i][j]=='0' or visited[i][j]==True:
return
visited[i][j]=True
for x,y in directions:
nx,ny=i+x,j+y
dfs(nx,ny)
ans=0
for i in range(n):
for j in range(m):
if grid[i][j]=='1' and visited[i][j]==False:
dfs(i,j)
print(ans)
广搜版
from collections import deque
n,m=map(int,input().split())
grid=[input().split() for _ in range(n)]
directions=[[-1,0],[1,0],[0,1],[0,-1]]
visited=[[False]*m for _ in range(n)]
def bfs(i,j):
global ans
que=deque([[i,j]])
visited[i][j]=True
while que:
cx,cy=que.popleft()
for x,y in directions:
nx,ny=cx+x,cy+y
if nx<0 or ny<0 or nx>=n or ny>=m or grid[nx][ny]=='0':
ans+=1
continue
elif visited[nx][ny]==False:
que.append([nx,ny])
visited[nx][ny]=True
ans=0
for i in range(n):
for j in range(m):
if grid[i][j]=='1' and visited[i][j]==False:
bfs(i,j)
print(ans)