广度优先搜索(BFS)的一个(重要!)细节(可能因此卡题)

本文针对LeetCode上的200.岛屿数量题目,深入探讨了一种广度优先搜索算法的实现细节。通过对两种不同代码实现的对比,揭示了一个关键的效率瓶颈——节点重复入队的问题,并解释了其对算法性能的影响。

引言:

为了一个广度优先搜索的细节有必要水一整篇文章?有必要。

这个细节非常重要,以至于我在切Leetcode某一题的时候,明明和答案的高效率通过的代码相差无几,逻辑毫无错误,STL使用相同,但仍然有几个测试点卡不过去。

题目来源:200.岛屿数量


我原来的代码:

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
       // vector<vector<bool>> visited(grid.size(),vector<bool>(grid[0].size(),false));
        //int dx[4] = {1,0,-1,0};
        //int dy[4] ={0,-1,0,1};
        int res = 0;
        int r= grid.size();
        int c = grid[0].size();
        for(int i = 0;i<r;++i){
            for(int j  =0;j<c;++j){
                if(grid[i][j] == '0')continue;
                queue<pair<int,int>> q;
                q.push({i,j});
                res ++;
                grid[i][j] = '1';
                while(!q.empty()){
                    int x = q.front().first;
                    int y = q.front().second;
                    q.pop();
                    grid[x][y] = '0';
                    /*
                    for(int k = 0;k<4;++k){
                        int xx = x+dx[k];
                        int yy = y+dy[k];
                        if(xx<0||xx>=grid.size() ||yy<0||yy>=grid[0].size()||grid[xx][yy]=='0'){
                            continue;
                        }
                        q.push(make_pair(xx,yy));
                    }*/
                    if(x-1>=0 && grid[x-1][y]=='1')q.push({x-1,y});
                    if(y-1>=0 &&grid[x][y-1]=='1')q.push({x,y-1});
                    if(x+1<r&&grid[x+1][y]=='1')q.push({x+1,y});
                    if(y+1<c&&grid[x][y+1]=='1')q.push({x,y+1});
                }
            }
        }
        return res;
    }
};

代码运行后,总是超时。

我做了许多猜测与尝试,排除了  另外开visited数组make_pairdx[]与dy[]、甚至访问vector的size()等可能导致超时的因素。

但是都没有太大的效果。

于是排除所有可能,真相就只有一个了——

先来看看答案代码:

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        int nr = grid.size();
        if (!nr) return 0;
        int nc = grid[0].size();

        int num_islands = 0;
        for (int r = 0; r < nr; ++r) {
            for (int c = 0; c < nc; ++c) {
                if (grid[r][c] == '1') {
                    ++num_islands;
                    grid[r][c] = '0';
                    queue<pair<int, int>> neighbors;
                    neighbors.push({r, c});
                    while (!neighbors.empty()) {
                        auto rc = neighbors.front();
                        neighbors.pop();
                        int row = rc.first, col = rc.second;
                        if (row - 1 >= 0 && grid[row-1][col] == '1') {
                            neighbors.push({row-1, col});
                            grid[row-1][col] = '0';
                        }
                        if (row + 1 < nr && grid[row+1][col] == '1') {
                            neighbors.push({row+1, col});
                            grid[row+1][col] = '0';
                        }
                        if (col - 1 >= 0 && grid[row][col-1] == '1') {
                            neighbors.push({row, col-1});
                            grid[row][col-1] = '0';
                        }
                        if (col + 1 < nc && grid[row][col+1] == '1') {
                            neighbors.push({row, col+1});
                            grid[row][col+1] = '0';
                        }
                    }
                }
            }
        }

        return num_islands;
    }
};
作者:LeetCode
链接:https://leetcode-cn.com/problems/number-of-islands/solution/dao-yu-shu-liang-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

对比可以发现,答案与我的代码里面一个看似没什么影响的差别在于:

我的代码是从队列中弹出节点时才把这个节点标记为<已访问>,而答案代码里是在push进队列的时候就标记其为<未访问>

看似没有什么逻辑问题啊。

但是,

我仔细

仔细

想了想。

这实际上导致的效率差别是巨大的!

比如,在访问cur节点的时候,把一个B节点push了进队列(也就是说B节点是cur节点的邻居节点),

如果我此刻没有标记其为<已访问>的话,

那么我在访问队列中排在B前面的节点cur2时(即在cur之后,B之前,被push进队列的节点),

如果我在cur2的邻居节点里看到了B,此刻由于B还没有出队列,所以它目前未被标记为<已访问>

那么,此时B又会再一次被push进队列里面!!!

也就是说,一个节点会被访问多次,那运行时间固然就翻倍了。

由此可见这个细节的重要性之大。

(如果比赛的时候因为这个细节卡题了的话,我想我就不会痛失倒一了吧。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖啡咖_CoffCa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值