DFS、BFS
回顾(C语言代码)
map[i][j]里记录的是i点和j点的连接关系
基本DFS:
cpp
int vis[101],n,map[101][101];
void dfs(int t)
{
int i;
vis[t]=1;
for(i=0;i<n;i++)//找对t点所有有关联的点------"找路"
{
if(vis[i]!=0&&map[t][i]==1)//有连接边的点且没访问过
{
printf(" %d",i);
dfs(i);
}
}
}
dfs(0);
通过BFS找从起点到终点的最短步数
cpp
int n;
int map[1001][1001],vis[1001];
int quee[1001];//用个队列存着与当前点有连接边的点
int num[1001];//bfs下起点到各个点的步数
void bfs(int t)
{
memset(vis,0,sizeof(vis));
memset(num,1061109567,sizeof(num));
memset(quee,0,sizeof(quee));
int k,kk,i;
k=0;
kk=0;
num[t]=0;//n到n 为0步
quee[k++]=t;//在队列中放入起点
vis[t]=1;
while(kk<k)//队列不为空
{
int now=quee[kk++];//依次拿出队列中的节点
for(i=1; i<=n; i++)//找路
{
if(vis[i]==0&&map[now][i]==1)//当前结点与i结点连通 && 没有遍历过
{
quee[k++]=i;
vis[i]=1;
if(num[now]<num[i])//当前now点到i点距离更短,那就在num[now]的基础上+1
num[i]=num[now]+1;
}
}
}
if(num[n]==1061109567)//起点到终点距离是inf,说明到不了
printf("NO\n");
else printf("%d\n",num[n]);
}
在二维图迷宫里从起点到终点的路径数(dfs)
(这题需要注意的是,每次移动并不总是向着接近终点的放心走,可以在不重复的前提下乱窜,所以导致有很多条路)
cpp
int n,m,num;
int map[11][11],vis[11][11];
void dfs(int x,int y)
{
int i,tx,ty;
int next[4][2]={{1,0},{-1,0},{0,1},{0,-1}};//下上右左 4种移动方式
vis[x][y]=1;
if(x==n&&y==m)
{
num++;//每次到终点计数
return ;
}
for(i=0;i<4;i++)//与for(0,n)一样都是去判断相连的点即"找路"
{
tx=x+next[i][0];//每次移动
ty=y+next[i][1];
if(tx<1||tx>n||ty<1||ty>m)//是否越界
{
continue;
}
if(vis[tx][ty]==0&&map[tx][ty]==0)//当前点可以走且没走过
{
vis[tx][ty]=1;
dfs(tx,ty);
vis[tx][ty]=0;//释放
}
}
}
dfs(1,1);//起点(1,1) 终点(n,m)
在二维图里从起点到终点的最短步数(bfs)
cpp
//C写法
struct node
{
int x,y,time;
} quee[101],now,t;//队列要记录xy坐标和到这点的步数
char map[16][16];
int n,m,vis[16][16];
void bfs(int x0,int y0)
{
int i,k,kk;
int next[4][2]= {{0,1},{0,-1},{1,0},{-1,0}};
memset(quee,0,sizeof(quee));
vis[x0][y0]=1;
t.x=x0;t.y=y0;t.time=0;
quee[0]=t;//起点信息放队列
k=1;kk=0;
while(kk<k)
{
now=quee[kk++];
if(map[now.x][now.y]=='Y')//到终点
{
printf("%d\n",now.time);
return ;
}
for(i=0; i<4; i++)//二维找路
{
t.x=now.x+next[i][0];
t.y=now.y+next[i][1];
if(t.x<0||t.x>=n||t.y<0||t.y>=m)
{
continue;
}
if(vis[t.x][t.y]==0&&map[t.x][t.y]!='#')//可以通行
{
t.time=now.time+1;
quee[k++]=t;
vis[t.x][t.y]=1;
}
}
}
printf("-1\n");
}
bfs(i1,j1);//起点传进去
不定义结构体存节点的x,y坐标,而是将坐标转化为m*i+j的形式(java其实可以将结点定义成类,类里有x,y坐标)
java
int n = grid.length, m = grid[0].length;
int next[][] = {{1,0},{0,1},{-1,0},{0,-1}};
ArrayDeque<Integer> que = new ArrayDeque<Integer>();
int[] nums = new int[n*m+10];//记录从起点到当前点的最短距离
while(!que.isEmpty()){
int t=que.poll();
int x=t/m,y=t%m;//得到点的坐标
int tx,ty;//下一步的坐标
System.out.println(x+","+y);
for(int i=0;i<4;i++){//四个方向
tx=x+next[i][0];
ty=y+next[i][1];
if(tx<0||tx>=n||ty<0||ty>=m) continue;//超出边界
if(grid[tx][ty]==1){//新鲜橙子
grid[tx][ty]=2;
System.out.println(x+","+y+":"+tx+","+ty);
que.offer(tx*m+ty);
nums[tx*m+ty]=nums[x*m+y]+1;
maxx=Math.max(maxx,nums[tx*m+ty]);
}
}
}
java-简单并查集类
单维点
java
class UnionFind {
private int[] parent;//记录祖先节点是谁
private int[] rank;//统计此节点做了几次祖先了
public UnionFind(int n) {//初始化
parent = new int[n];//new空间
for (int i = 0; i < n; i++) {
parent[i] = i;//先让所有点的祖先是自己
}
rank = new int[n];
}
public void union(int x, int y) {//合并
int rootx = find(x);
int rooty = find(y);
if (rootx != rooty) {//谁rank大谁做祖先
if (rank[rootx] > rank[rooty]) {
parent[rooty] = rootx;
} else if (rank[rootx] < rank[rooty]) {
parent[rootx] = rooty;
} else {
parent[rooty] = rootx;
rank[rootx]++;
}
}
}
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);//找到并和祖先直接相连
}
return parent[x];//返回祖先
}
}
二维图,点由(x,y)坐标组成
java
class UnionFind {
int[] parent;
int[] rank;
public UnionFind(char[][] grid) {//初始化
count = 0;
int m = grid.length;
int n = grid[0].length;
parent = new int[m * n];
rank = new int[m * n];
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == '1') {
parent[i * n + j] = i * n + j;//将坐标(i,j)以数组下标的形式
}
rank[i * n + j] = 0;
}
}
}
public int find(int i) {
if (parent[i] != i)
parent[i] = find(parent[i]);
return parent[i];
}
public void union(int x, int y) {
int rootx = find(x);
int rooty = find(y);
if (rootx != rooty) {
if (rank[rootx] > rank[rooty]) {
parent[rooty] = rootx;
} else if (rank[rootx] < rank[rooty]) {
parent[rootx] = rooty;
} else {
parent[rooty] = rootx;
rank[rootx]++;
}
}
}
}
leetcode:
200. 岛屿数量
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 :
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
关键:通过基础二维图dfs遍历图
java
class Solution {
int[][] vis=new int[303][303];
int n,m;
void dfs(char[][] map,int x,int y){
int i,tx,ty;
int[][] next={{1,0},{-1,0},{0,1},{0,-1}};//下上右左 4种移动方式
vis[x][y]=1;
for(i=0;i<4;i++){
tx=x+next[i][0];
ty=y+next[i][1];
if(tx<0||tx>=n||ty<0||ty>=m){
continue;
}
if(vis[tx][ty]==0&&map[tx][ty]=='1'){
vis[tx][ty]=1;
dfs(map,tx,ty);
}
}
}
public int numIslands(char[][] grid) {
n=grid.length;
m=grid[0].length;
int cnt=0;
//对图中所有没访问过的点做dfs
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(vis[i][j]==0&&grid[i][j]=='1'){
dfs(grid,i,j);
cnt++;//每次从新起点开始dfs就是一个独立的岛屿
}
}
}
return cnt;
}
}
还可以并查集,将相邻且为1的在并查集里合并,最后统计由几个祖先节点。
994. 腐烂的橘子
在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。
返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。
输入:grid = [[2,1,1],[1,1,0],[0,1,1]]
输出:4
java
class Solution {
public int orangesRotting(int[][] grid) {
int n = grid.length, m = grid[0].length;
int next[][] = {{1,0},{0,1},{-1,0},{0,-1}};
ArrayDeque<Integer> que = new ArrayDeque<Integer>();
int[] nums = new int[n*m+10];//记录从起点到当前点的最短距离
//找腐烂橙子放队列里
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(grid[i][j]==2){
que.offer(i*m+j);//将这个点的坐标信息放进去
nums[i*m+j]=0;
}
}
}
int maxx=0;
while(!que.isEmpty()){
int t=que.poll();
int x=t/m,y=t%m;//得到点的坐标
int tx,ty;//下一步的坐标
System.out.println(x+","+y);
for(int i=0;i<4;i++){//四个方向
tx=x+next[i][0];
ty=y+next[i][1];
if(tx<0||tx>=n||ty<0||ty>=m) continue;//超出边界
if(grid[tx][ty]==1){//新鲜橙子
grid[tx][ty]=2;
System.out.println(x+","+y+":"+tx+","+ty);
que.offer(tx*m+ty);
nums[tx*m+ty]=nums[x*m+y]+1;
maxx=Math.max(maxx,nums[tx*m+ty]);
}
}
}
//看看图里还有新鲜橙子吗
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(grid[i][j]==1) return -1;
}
}
return maxx;
}
}
拓扑排序问题
一个图是否存在合法拓扑序列的充要条件: 有向,无环。
找是否存在拓扑序列:找入度为0的顶点,删除出边(与它相连的点入度-1)
C简洁形式
cpp
for(i=1;i<=n;i++)//计找n次入度为0的点
{ //n次全有入度为0的点则无环
f=0;
for(j=1;j<=n;j++)
{
if(in[j]==0&&!vis[j])
{
vis[j]=1;
f=1;//有 入度为0的点
for(k=1;k<=n;k++)
{
if(map[j][k]&&!vis[k])
{
in[k]--;
}
}
break;//找到1个入度为0的点,i+1进入下一循环
}
}
if(f==0) break;
}
if(f==0) printf("NO\n");
else printf("YES\n");
207. 课程表
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
numCourses =2 prerequisites =[[1,0],[0,1]]
输出:false
numCourses =5 prerequisites =[[1,4],[2,4],[3,1],[3,2]]
输出:true
numCourses =5 prerequisites =[[1,2],[2,3],[3,1]]
输出:false
本质就是上述拓扑排序问题,要求有向无环
java
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
//有向无环图
int n=prerequisites.length;
int[] in=new int[numCourses];
for(int i=0;i<n;i++){
in[prerequisites[i][1]]++;//记录结点入度
}
int f=0;
int[] vis=new int[numCourses];
for(int i=0;i<numCourses;i++)//计找n次入度为0的点
{ //n次全有入度为0的点则无环
f=0;
for(int j=0;j<numCourses;j++)
{
if(in[j]==0&&vis[j]==0)
{
vis[j]=1;
f=1;//有 入度为0的点
for(int k=0;k<n;k++)
{
if(prerequisites[k][0]==j&&vis[prerequisites[k][1]]==0)//没访问过且与j有链接
{
in[prerequisites[k][1]]--;
}
}
break;//找到1个入度为0的点,i+1进入下一循环
}
}
if(f==0) break;
}
if(f==0) return false;
else return true;
}
}
1559. 二维网格图中探测环
给你一个二维字符网格数组 grid ,大小为 m x n ,你需要检查 grid 中是否存在 相同值 形成的环。
一个环是一条开始和结束于同一个格子的长度 大于等于 4 的路径。对于一个给定的格子,你可以移动到它上、下、左、右四个方向相邻的格子之一,可以移动的前提是这两个格子有 相同的值 。
同时,你也不能回到上一次移动时所在的格子。比方说,环 (1, 1) -> (1, 2) -> (1, 1) 是不合法的,因为从 (1, 2) 移动到 (1, 1) 回到了上一次移动时的格子。
如果 grid 中有相同值形成的环,请你返回 true ,否则返回 false 。
示例 :
输入:grid = [["c","c","c","a"],["c","d","c","c"],["c","c","e","c"],["f","c","c","c"]]
输出:true
java
class Solution {
int[][] vis = new int[505][505];
int flag;
int n,m;
void dfs(char[][] map,int x,int y,int xx,int yy)//x,y是当前点坐标
{ //xx,yy是x,y,前一步点的坐标
vis[x][y]=1;
if(flag==1) return ;
/* (x,y)向上下左右移动 */
if(x-1>=0&&map[x-1][y]==map[x][y]) //向上存在且同色
{
if(vis[x-1][y]==1&&(x-1!=xx||y!=yy)) 通过下一步(x-1,y)不是上一步(xx,yy),但这个下一步曾经走过,就说明成环了
{
flag=1;
}
else if(vis[x-1][y]==0) //没形成环就继续遍历
dfs(map,x-1,y,x,y);
}
if(y+1<m&&map[x][y+1]==map[x][y])//向右存在且同色
{
if(vis[x][y+1]==1&&(x!=xx||y+1!=yy))
{
flag=1;
}
else if(vis[x][y+1]==0)
dfs(map,x,y+1,x,y);
}
if(x+1<n&&map[x+1][y]==map[x][y])//向下存在且同色
{
if(vis[x+1][y]==1&&(x+1!=xx||y!=yy))
{
flag=1;
}
else if(vis[x+1][y]==0)
dfs(map,x+1,y,x,y);
}
if(y-1>=0&&map[x][y-1]==map[x][y])//向左存在且同色
{
if(vis[x][y-1]==1&&(x!=xx||y-1!=yy))
{
flag=1;
}
else if(vis[x][y-1]==0)
dfs(map,x,y-1,x,y);
}
}
public boolean containsCycle(char[][] grid) {
flag=0;
n=grid.length;
m=grid[0].length;
for(int i=0; i<n; i++)
{
for(int j=0; j<m; j++)
{
if(vis[i][j]==0)//对每个没访问过的点都进行成环dfs测试
{
dfs(grid,i,j,i,j);
}
if(flag==1)
break;
}
if(flag==1) break;
}
if(flag==1)return true;
else return false;
}
}
官方解,时间复杂度更低的使用并查集,通过连通性 判断网格中是否有相同值形成的环,连通性问题可以使用并查集解决 。
(没看明白)