优选算法——BFS

💁‍♂️个人主页:进击的荆棘

👇作者其它专栏:

《数据结构与算法》《算法》《C++起始之路》


目录

1.BFS解决FloodFill算法

2.BFS解决最短路问题

3.多源BFS

4.BFS解决拓扑排序


1.BFS解决FloodFill算法

1.1图像渲染

算法思路:

可以利用【深搜】或【宽搜】,遍历到与该点相连的所有【像素值相同的点】,然后将其修改成指定的像素。

cpp 复制代码
class Solution {
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
public:
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {
        //将需要修改的像素值提前保存
        int prev=image[sr][sc];
        //若起始位置值已经与color相同,直接返回
        if(image[sr][sc]==color) return image;

        int m=image.size(),n=image[0].size();
        queue<pair<int,int>> q;
        q.push({sr,sc});
        image[sr][sc]=color;
        while(q.size()){
            auto [a,b]=q.front();
            q.pop();
            for(int i=0;i<4;i++){
                int x=a+dx[i],y=b+dy[i];
                if(x>=0&&x<m&&y>=0&&y<n&&image[x][y]==prev){
                    image[x][y]=color;
                    q.push({x,y});
                }
            }
        }
        return image;
    }
};

1.2岛屿数量

算法思路:

遍历整个矩阵,每次找到【一块陆地】时:

●说明找到【一个岛屿】,记录到最终结果ret里;

●并且将这个陆地相连的所有陆地,即这块【岛屿】,全部变成【海洋】。这样的话,下次遍历到这块岛屿时,它【已经是海洋】了,不会影响最终结果。(有时不能修改原数组,也可以用一个bool数组标记遍历过的位置)

●其中【变成海洋】的操作,可以利用【深搜】和【宽搜】解决,其实就是 1.1图像渲染

这样,当遍历完全部矩阵式,ret存的就是最终结果。

cpp 复制代码
class Solution {
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    bool check[301][301];
    int m,n;
public:
    int numIslands(vector<vector<char>>& grid) {
        m=grid.size(),n=grid[0].size();
        int ret=0;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]=='1'&&!check[i][j]){
                    ret++;
                    bfs(grid,i,j);
                }
            }
        }
        return ret;
    }
    void bfs(vector<vector<char>>& grid,int i,int j){
        queue<pair<int,int>> q;
        q.push({i,j});
        check[i][j]=true;
        while(q.size()){
            auto [a,b]=q.front();
            q.pop();
            for(int k=0;k<4;k++){
                int x=a+dx[k],y=b+dy[k];
                if(x>=0&&x<m&&y>=0&&y<n&&!check[x][y]&&grid[x][y]=='1'){
                    q.push({x,y});
                    check[x][y]=true;
                }
            }
        }
    }
};

1.3岛屿的最大面积

算法思路:

●遍历整个矩阵,每当遇到一块土地时,用【深搜】或【宽搜】将与这块土地相连的【整个岛屿】的面积计算出来。

●然后再搜索得到的【所有岛屿面积】求一个【最大值】。

●在搜索过程中,为【防止搜到重复的土地】:

▢可以开一个同等规模的【布尔数组】,标记一下这个位置是否已经被访问过;

▢也可以将原始矩阵的1修改为0,但这样操作会修改原始矩阵。

cpp 复制代码
class Solution {
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    bool check[51][51];
    int m,n;
public:
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        m=grid.size(),n=grid[0].size();
        int ret=0;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]==1&&!check[i][j])
                    ret=max(bfs(grid,i,j),ret);
            }
        }
        return ret;
    }
    int bfs(vector<vector<int>>& grid,int i,int j){
        queue<pair<int,int>> q;
        int ret=1;
        q.push({i,j});
        check[i][j]=true;
        while(q.size()){
            auto [a,b]=q.front();
            q.pop();
            for(int k=0;k<4;k++){
                int x=a+dx[k],y=b+dy[k];
                if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==1&&!check[x][y]){
                    q.push({x,y});
                    check[x][y]=true;
                    ret++;
                }
            }
        }
        return ret;
    }
};

1.4被围绕的区域

算法思路:

正难则反。

可以先利用bfs将与边缘相连的'O'区域做标记,然后重新遍历矩阵,将没有标记过的'O'修改为'X'

cpp 复制代码
class Solution {
    int m,n;
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
public:
    void solve(vector<vector<char>>& board) {
        m=board.size(),n=board[0].size();
        for(int i=0;i<n;i++){
            if(board[0][i]=='O') bfs(board,0,i);
            if(board[m-1][i]=='O') bfs(board,m-1,i);
        }
        for(int i=0;i<m;i++){
            if(board[i][0]=='O') bfs(board,i,0);
            if(board[i][n-1]=='O') bfs(board,i,n-1); 
        }
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(board[i][j]=='O') board[i][j]='X';
                else if(board[i][j]=='.') board[i][j]='O';
            }
        }
    }
    void bfs(vector<vector<char>>& board,int i,int j){
        queue<pair<int,int>> q;
        q.push({i,j});
        board[i][j]='.';
        while(q.size()){
            auto [a,b]=q.front();
            q.pop();
            for(int k=0;k<4;k++){
                int x=a+dx[k],y=b+dy[k];
                if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]=='O'){
                    q.push({x,y});
                    board[x][y]='.';
                }
            }
        }
    }
};

2.BFS解决最短路问题

2.1迷宫中离入口最近的出口

算法思路:

利用层序遍历来解决迷宫问题,是最经典的做法。

可以从起点开始层序遍历,并在遍历的过程中记录当前遍历的层数。这样就能在找出出口的时候,得到起点到出口的最短距离。

cpp 复制代码
class Solution {
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
public:
    int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {
        int ret=0;
        int m=maze.size(),n=maze[0].size();
        bool check[m][n];
        memset(check,0,sizeof check);
        queue<pair<int,int>> q;
        q.push({entrance[0],entrance[1]});
        check[entrance[0]][entrance[1]]=true;
        while(q.size()){
            int cnt=q.size();
            ret++;
            while(cnt--){
                auto [a,b]=q.front();
                q.pop();
                for(int i=0;i<4;i++){
                    int x=a+dx[i],y=b+dy[i];
                    if(x>=0&&x<m&&y>=0&&y<n&&maze[x][y]=='.'&&!check[x][y]){
                        if(x==0||x==m-1||y==0||y==n-1) return ret;
                        q.push({x,y});
                        check[x][y]=true;
                    }
                }
            }
        }
        return -1;        
    }
};

2.2最小基因变化

算法思路:

若将【每次字符串的变化】抽象为图中的【两个顶点和一条边】的话,问题就变成了【边权为1的最短路问题】。

从起始的字符串开始,来一次bfs即可。

cpp 复制代码
class Solution {
public:
    int minMutation(string startGene, string endGene, vector<string>& bank) {
        unordered_set<string> vis;//标记已经存在的状态
        unordered_set<string> hash(bank.begin(),bank.end());//存储bank中的字符串
        string change="ACGT";

        if(startGene==endGene) return 0;
        if(!hash.count(endGene)) return -1;

        queue<string> q;
        q.push(startGene);
        int ret=0;
        while(q.size()){
            ret++;
            int n=q.size();
            while(n--){
                string t=q.front();
                q.pop();
                for(int i=0;i<8;i++){
                    string tmp=t;
                    for(int j=0;j<4;j++){
                        tmp[i]=change[j];
                        if(tmp==endGene) return ret;
                        if(hash.count(tmp)&&!vis.count(tmp)){
                            vis.insert(tmp);
                            q.push(tmp);
                        }
                    }
                }
            }
        }
        return -1;
    }
};

2.3单词接龙

算法思路:

与 2.2最小基因变化 相似。

cpp 复制代码
class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        unordered_set<string> vis;
        unordered_set<string> hash(wordList.begin(),wordList.end());

        if(!hash.count(endWord)) return 0;
        queue<string> q;
        q.push(beginWord);
        int ret=1;
        while(q.size()){
            ret++;
            int n=q.size();
            while(n--){
                string t=q.front();
                q.pop();
                for(int i=0;i<t.size();i++){
                    string tmp=t;
                    for(char j='a';j<='z';j++){
                        tmp[i]=j;
                        if(tmp==endWord) return ret;
                        if(hash.count(tmp)&&!vis.count(tmp)){
                            q.push(tmp);
                            vis.insert(tmp);
                        }
                    }
                }
            }
        }
        return 0;
    }
};

2.4为高尔夫比赛砍树

算法思路:

a.先找出砍树的顺序;

b.然后按照砍树的顺序,一个一个的用bfs求出最短路即可。

cpp 复制代码
class Solution {
    int m,n;
public:
    //相当于若干个迷宫问题,从某点走的某点的最短路径
    int cutOffTree(vector<vector<int>>& forest) {
        m=forest.size(),n=forest[0].size();
        vector<pair<int,int>> trees;
        for(int i=0;i<m;i++)
            for(int j=0;j<n;j++)
                if(forest[i][j]>1) trees.push_back({i,j});
       
        sort(trees.begin(),trees.end(),[&](const pair<int,int>& p1,const pair<int,int>& p2){
            return forest[p1.first][p1.second]<forest[p2.first][p2.second];
        });

        //记录每次的起始位置
        int bx=0,by=0;
        int ret=0;
        for(auto& [a,b]:trees){
            int step=bfs(forest,bx,by,a,b);
            if(step==-1) return -1;
            ret+=step;
            bx=a,by=b;
        }
        return ret;
    }
    bool vis[51][51];
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    int bfs(vector<vector<int>>& forest,int bx,int by,int ex,int ey){
        if(bx==ex&&by==ey) return 0;
        //每次将之前的干扰清除
        memset(vis,0,sizeof vis);
        queue<pair<int,int>> q;
        q.push({bx,by});
        vis[bx][by]=true;

        int step=0;
        while(q.size()){
            step++;
            int sz=q.size();
            while(sz--){
                auto [a,b]=q.front();
                q.pop();
                for(int i=0;i<4;i++){
                    int x=a+dx[i],y=b+dy[i];
                    if(x>=0&&x<m&&y>=0&&y<n&&forest[x][y]&&!vis[x][y]){
                        if(x==ex&&y==ey) return step;
                        q.push({x,y});
                        vis[x][y]=true;
                    }
                }
            }
        }
        return -1;
    }
};

3.多源BFS

3.101 矩阵

算法思路:

对于求的结果,有两种方式:

●第一种方式:从每一个1开始,然后通过层序遍历找到离它最近的0。

这一种方式,会以所有的1起点,来一次层序遍历,势必会遍历到很多重复的点。并且若矩阵中只有一个0的话,每次层序遍历需遍历很多层,时间复杂度会很高。

●换一种方式:从0开始层序遍历,并且记录遍历的层数。当第一次碰到1的时候,当前的层数就是这个1离0的最短距离。

这一种方式,我们在遍历的时候标记一下处理过的1,能够做到只用遍历整个矩阵一次,就能得到最终结果。

但是这里有一个问题,0是有很多个的,怎么才能保证遇到的1距离这一个0是最近的呢?

其实很简单,我们可以先把所有的0都放在队列中,把它们当成一个整体(一个超级源点),每次把当前队列里面的所有元素向外扩展一次。

cpp 复制代码
class Solution {
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
public:
    //寻找多源起点,正难则反,寻找0再去更新数组中的值更容易
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
        int m=mat.size(),n=mat[0].size();
        //dist[i][j]==-1,表示该位置未遍历过
        //dist[i][j]!=-1,最短距离
        vector<vector<int>> dist(m,vector<int>(n,-1));
        queue<pair<int,int>> q;
        //寻找所有的0,并添加进队列+数组
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(mat[i][j]==0){
                    dist[i][j]=0;
                    q.push({i,j});
                }
            }
        }
        //开始bfs
        while(q.size()){
            auto [a,b]=q.front();
            q.pop();
            for(int i=0;i<4;i++){
                int x=a+dx[i],y=b+dy[i];
                if(x>=0&&x<m&&y>=0&&y<n&&dist[x][y]==-1){
                    dist[x][y]=dist[a][b]+1;
                    q.push({x,y});
                }
            }
        }
        return dist;
    }
};

3.2飞地的数量

算法思路:

正难则反:

从边上的1开始搜索,把与边上1相连的连通区域全部标记一下;

然后再遍历一般矩阵,看看哪些位置的1没有被标记即可。

标记的时候,可以用【多源bfs】解决。

cpp 复制代码
class Solution {
    int m,n;
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
public:
    int numEnclaves(vector<vector<int>>& grid) {
        m=grid.size(),n=grid[0].size();
        vector<vector<bool>> vis(m,vector<bool>(n));
        queue<pair<int,int>> q;
        //1.将边界上的1加入队列
        for(int i=0;i<m;i++){
            if(grid[i][0]==1) q.push({i,0});
            if(grid[i][n-1]==1) q.push({i,n-1});
        }
        for(int j=0;j<n;j++){
            if(grid[0][j]==1) q.push({0,j});
            if(grid[m-1][j]==1) q.push({m-1,j});
        }
        //2.bfs
        while(q.size()){
            auto [a,b]=q.front();
            q.pop();
            vis[a][b]=true;
            for(int i=0;i<4;i++){
                int x=a+dx[i],y=b+dy[i];
                if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==1&&!vis[x][y]){
                    q.push({x,y});
                    vis[x][y]=true;
                }
            }
        }
        //3.统计结果
        int ret=0;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]==1&&!vis[i][j]) ret++;
            }
        }
        return ret;
    }
};

3.3地图中的最高点

算法思路:

与 3.101 矩阵 思路相同,直接用多源bfs解决即可。

cpp 复制代码
class Solution {
    int m,n;
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
public:
    vector<vector<int>> highestPeak(vector<vector<int>>& isWater) {
        m=isWater.size(),n=isWater[0].size();        
        vector<vector<int>> dist(m,vector<int>(n,-1));
        queue<pair<int,int>> q;
        //寻找所有水域
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(isWater[i][j]==1){
                    q.push({i,j});
                    dist[i][j]=0;
                }
            }
        }
        while(q.size()){
            auto [a,b]=q.front();q.pop();
            for(int i=0;i<4;i++){
                int x=a+dx[i],y=b+dy[i];
                if(x>=0&&x<m&&y>=0&&y<n&&dist[x][y]==-1){
                    dist[x][y]=dist[a][b]+1;
                    q.push({x,y});
                }
            }            
        }
        return dist;
    }
};

3.4地图分析

算法思路:

01矩阵的变形题,直接用多源bfs解决即可。

cpp 复制代码
class Solution {
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
public:
    //把1当作超级源点,向0扩展
    int maxDistance(vector<vector<int>>& grid) {
        int m=grid.size(),n=grid[0].size();
        vector<vector<int>> dist(m,vector<int>(n,-1));
        queue<pair<int,int>> q;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]==1){
                    dist[i][j]=0;
                    q.push({i,j});
                }
            }
        }
        int ret=-1;
        while(q.size()){
            auto [a,b]=q.front();q.pop();
            for(int i=0;i<4;i++){
                int x=a+dx[i],y=b+dy[i];
                if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==0&&dist[x][y]==-1){
                    dist[x][y]=dist[a][b]+1;
                    ret=max(ret,dist[x][y]);
                    q.push({x,y});
                }
            }
        }
        return ret;
    }
};

4.BFS解决拓扑排序

4.1课程表

算法思路:

原问题可以转化成一个拓扑排序问题。

用BFS解决拓扑排序即可。

拓扑排序流程:

a.将所有入度位0的点加入到队列中;

b.当队列不空时,一直循环:

i.取出队头元素;

ii.将与队头元素相连的顶点的入度-1;

iii.然后判断是否减成0,若为0,就加入到队列中。

cpp 复制代码
class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        //1.准备工作
        unordered_map<int,vector<int>>  edges;//用邻接表建图
        vector<int> in(numCourses);//记录每个点的入度
        //2.建图
        for(auto e:prerequisites){
            int a=e[0],b=e[1];//b -> a
            edges[b].push_back(a);
            in[a]++;
        }
        
        queue<int> q;
        //将所有入度为0的点加入队列
        for(int i=0;i<numCourses;i++){
            if(in[i]==0){
                q.push(i);
            }
        }
        //bfs
        while(q.size()){
            int t=q.front();q.pop();
            //修改相连的边
            for(auto a:edges[t]){
                in[a]--;
                if(in[a]==0){
                    q.push(a);
                }
            }
        }
        //判断是否有环
        for(int i=0;i<numCourses;i++){
            if(in[i])   return false;
        }
        return true;
    }
};

4.2课程表 II

算法思路:

与 4.1课程表 思路相同

cpp 复制代码
class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        //unordered_map<int,vector<int>> edges;
        //当数据稀疏时,可以用二维数组来建邻接表
        //1.准备工作
        vector<vector<int>> edges(numCourses);//用二维数组来建邻接表
        vector<int> in(numCourses);//记录每个点的入度
        //2.建图
        for(auto e:prerequisites){
            int a=e[0],b=e[1];
            edges[b].push_back(a);
            in[a]++;
        }
        //拓扑排序
        queue<int> q;
        for(int i=0;i<numCourses;i++){
            if(in[i]==0) q.push(i);
        }
        vector<int> ret;
        while(q.size()){
            int t=q.front();q.pop();
            ret.push_back(t);
            for(auto a:edges[t]){
                in[a]--;
                if(in[a]==0){
                    q.push(a);
                }
            }
        }
        if(ret.size()==numCourses) return ret;
        else return {};
    }
};

4.3火星词典

算法思路:

将题意搞清楚后,这道题就变成了判断有向图是否有环,可以用拓扑排序解决。

如何搜集信息(如何建图):

a.两层for循环枚举出所有的两个字符串的组合;

b.然后利用指针,根据字典序规则找出信息。

cpp 复制代码
class Solution {
    unordered_map<char,unordered_set<char>> edges;//创建邻接表
    unordered_map<char,int> in;//记录每个点的入度
    bool check;
public:
    string alienOrder(vector<string>& words) {
        //将入度初始化
        for(auto& s:words){
            for(auto ch:s){
                in[ch]=0;
            }
        }
        //建图
        int n=words.size();
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
               add(words[i],words[j]);
               if(check) return "";
            }
        }
        //将入度为0的加入队列
        queue<char> q;
        for(auto& [a,b]:in){
            if(b==0) q.push(a);
        }
        //拓扑排序
        string ret;
        while(q.size()){
            char t=q.front();q.pop();
            ret+=t;
            for(auto ch:edges[t]){
                if(--in[ch]==0){
                    q.push(ch);
                }
            }
        }
        //判断
        for(auto& [a,b]:in){
            if(b!=0) return "";
        }
        return ret;
    }
    void add(string& s1,string& s2){
        int n=min(s1.size(),s2.size());
        int i=0;
        while(i<n){
            if(s1[i]!=s2[i]){
                char a=s1[i],b=s2[i];//a -> b
                if(!edges.count(a)||!edges[a].count(b)){
                    edges[a].insert(b);
                    in[b]++;
                }
                break;
            }
            i++;
        }
        //特例
        if(i==s2.size()&&i<s1.size()) check=true;
    }
};
相关推荐
.千余3 小时前
【C++】C++ set 与 multiset 完全指南:关联式容器入门
开发语言·c++·笔记·学习·其他
c++之路6 小时前
CMake 系列教程(二):基础命令详解
开发语言·c++
南境十里·墨染春水10 小时前
C++ 工厂模式:从入门到进阶,彻底掌握对象创建的艺术
开发语言·c++·算法
@insist12310 小时前
系统架构设计师-实时性评价、调度算法与内核架构选型
算法·架构·系统架构·软考·系统架构设计师·软件水平考试
一拳一个呆瓜13 小时前
【STL】_SCL_SECURE_NO_WARNINGS
c++·stl
小小编程路13 小时前
C++ 异常 完整讲解
开发语言·c++
一只齐刘海的猫15 小时前
【Leetcode】找到字符串中所有字母异位词
算法·leetcode·职场和发展
海清河晏11116 小时前
数据结构 | 八大排序
数据结构·算法·排序算法