【问题标题】:What is the difference between BFS and DFS algorithms?BFS 和 DFS 算法有什么区别?
【发布时间】:2019-04-05 05:12:40
【问题描述】:

使用 BFS 解决算法问题时发生超时。但是,有一个问题可以用 DFS 解决。为什么会出现这种差异?

问题是通过水平、垂直或对角线移动来计算从(1,1)到(N,N)的到达次数。

如果用 BFS 解决问题(最坏情况)需要 1331.0ms,而用 DFS 解决问题需要 62.0ms。 (我已经创建并测试了 16 * 16 的数组。)

附上问题网址。 (但请理解这是韩语。) 网址>https://www.acmicpc.net/problem/17070

我想听到的答案是……

  1. 我以为 BFS 算法会更快,但为什么 DFS 更快?
  2. BFS 是否因为队列中有很多元素而变慢?我想知道确切的原因。

实现代码>

上课地点{

int x;
int y;
int dir;

public Location(int x, int y, int dir) {
    super();
    this.x = x;
    this.y = y;
    this.dir = dir;
}

}

公共类主{

static int[][] map;
static int Answer;
static int N;

public static void main(String args[]) throws IOException {

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st;

    N = Integer.parseInt(br.readLine());

    map = new int[N + 1][N + 1];
    for (int i = 1; i <= N; i++) {

        st = new StringTokenizer(br.readLine());

        for (int j = 1; j <= N; j++)
            map[i][j] = Integer.parseInt(st.nextToken());
    }
    DFS(1, 2, 0);
    System.out.println(Answer);
    Answer = 0;
    BFS(1, 2, 0);
    System.out.println(Answer);
    br.close();
}

static void DFS(int x, int y, int pre) {

    if (x == N && y == N) {

        Answer++;
        return;
    }

    if (pre == 0) {

        if (y + 1 <= N && map[x][y + 1] == 0)
            DFS(x, y + 1, 0);

        if (y + 1 <= N && map[x][y + 1] == 0 && x + 1 <= N && map[x + 1][y] == 0 && map[x + 1][y + 1] == 0)
            DFS(x + 1, y + 1, 1);
    } else if (pre == 1) {

        if (y + 1 <= N && map[x][y + 1] == 0)
            DFS(x, y + 1, 0);

        if (x + 1 <= N && map[x + 1][y] == 0)
            DFS(x + 1, y, 2);

        if (y + 1 <= N && map[x][y + 1] == 0 && x + 1 <= N && map[x + 1][y] == 0 && map[x + 1][y + 1] == 0)
            DFS(x + 1, y + 1, 1);
    } else {

        if (x + 1 <= N && map[x + 1][y] == 0)
            DFS(x + 1, y, 2);

        if (y + 1 <= N && map[x][y + 1] == 0 && x + 1 <= N && map[x + 1][y] == 0 && map[x + 1][y + 1] == 0)
            DFS(x + 1, y + 1, 1);
    }
}

static void BFS(int startX, int startY, int dir) {

    ArrayDeque<Location> arrayDeque = new ArrayDeque<>();
    arrayDeque.add(new Location(startX, startY, dir));

    Location location;
    int x, y, pre;

    while(!arrayDeque.isEmpty()) {

        location = arrayDeque.remove();
        x = location.x;
        y = location.y;
        pre = location.dir;

            if(x == N-1 && y == N-1) {
               Answer++; continue;
            }

        if (pre == 0) {

            if (y + 1 <= N && map[x][y + 1] == 0)
                arrayDeque.add(new Location(x, y + 1, 0));

            if (y + 1 <= N && map[x][y + 1] == 0 && x + 1 <= N && map[x + 1][y] == 0 && map[x + 1][y + 1] == 0)
                arrayDeque.add(new Location(x + 1, y + 1, 1));
        } else if (pre == 1) {

            if (y + 1 <= N && map[x][y + 1] == 0)
                arrayDeque.add(new Location(x, y + 1, 0));

            if (x + 1 <= N && map[x + 1][y] == 0)
                arrayDeque.add(new Location(x + 1, y, 2));

            if (y + 1 <= N && map[x][y + 1] == 0 && x + 1 <= N && map[x + 1][y] == 0 && map[x + 1][y + 1] == 0)
                arrayDeque.add(new Location(x + 1, y + 1, 1));
        } else {

            if (x + 1 <= N && map[x + 1][y] == 0)
                arrayDeque.add(new Location(x + 1, y, 2));

            if (y + 1 <= N && map[x][y + 1] == 0 && x + 1 <= N && map[x + 1][y] == 0 && map[x + 1][y + 1] == 0)
                arrayDeque.add(new Location(x + 1, y + 1, 1));
        }
    }
}

}

【问题讨论】:

  • 一般来说,BFS 具有一致的运行时间,取决于答案的深度(如果问题空间是一棵树,则保证如果存在答案,则可以找到答案)。 DFS 更加灵活(基于运气),这取决于您是否选择了正确的分支,并且可能比 BFS 快得多,或者慢得多(如果问题空间具有无限深度,甚至永无止境)。

标签: algorithm depth-first-search breadth-first-search


【解决方案1】:

BFS 和 DFS 都具有 O(|V| + |E|) 时间复杂度,而您遇到的时间差异很可能源于 BFS 实现中的错误,该错误打破了循环不变量。 p>

实现 BFS 时最常见的错误之一是多次将相同的元素添加到队列中。一个人应该只将一个顶点v 添加到队列中一次,这样您就可以确保它被删除一次。除非你这样做,否则渐近运行时间(即它的复杂性)将不再是线性的。您可以查看相关的 CLRS 章节来证明这一点,基于他们引入的 循环不变 概念。

换句话说,BFS 是一种遍历算法。它找出哪些 顶点是可到达的,而不是到达每个顶点v 的路线数。如果您尝试计算从 (1, 1) 通过 BFS 到达每个 v 的方式 K<sub>v</sub> 的数量,则复杂性变得大于线性。 如果问题要求您找到K<sub>v</sub>,那么您的方法应该是使用记忆化和动态编程,而不是 BFS。

具体来说,根据您提供的代码,您的算法似乎无法跟踪先前是否探索过顶点(即网格中的单元格)。这会导致对顶点进行多次探索,这优于使用 BFS 和 DFS 等图遍历算法的要点。使用我上面提到的术语,您将违背 BFS 的循环不变量,它指出 每个顶点仅从队列中弹出一次。这会导致代码的复杂性远高于线性代码。

您应该研究 memoization 一词并找出一种方法来找到 (N, N) 的解决方案,使用您为 (N-1, N-1) 计算一次的解决方案, (N-1, N)(N, N-1)

【讨论】:

  • @Taylous 我根据您提供的代码示例更新了我的答案。
  • 请注意,为了根据 (N-1, N-1)、(N-1, N) 和 (N, N-1) 的解来找到 (N,N) 的解) - 或者通常使用每个单元格的解决方案数量更新数组 - 您需要根据最后一次移动是水平、垂直还是对角线将所有可能的解决方案分成三个解决方案。 (否则对角线移动将被计算三次:对角线,水平->垂直,垂直->水平。)
  • 是的,还有第三个方向参数。但我只是想暗示一下 memoization 和 DP 方法的工作原理,而不是提供一个的所有细节。
【解决方案2】:

您的 BFS 实现使用动态内存分配和 ArrayDeque;您的 DFS 避免了这种情况。这将增加 BFS 的每个节点的成本,尽管它会这么多很奇怪。

您可以尝试在每次 DFS 调用中分配一个新位置(并可能在 ArrayDeque 中添加和删除它),看看这是否会导致相同的性能损失。

此外,当 x=y=N; 时,您的 BFS 不会直接停止;但我认为它不会显着增加运行时间。

【讨论】:

  • 我先写了文章,把代码擦掉了。但回答者要求提供代码。所以好像被匆匆跳过了。谢谢你的回答。
猜你喜欢
  • 1970-01-01
  • 2018-09-29
  • 2014-10-16
  • 1970-01-01
  • 1970-01-01
  • 2010-12-26
  • 2022-12-11
  • 2017-07-11
  • 2010-10-27
相关资源
最近更新 更多