使用回溯(而不是DFS)背后的直觉

时间:2018-03-16 00:56:36

标签: c++ algorithm depth-first-search backtracking

我正在LeetCode.com上解决Word Search问题:

  

给定2D板和单词,找出网格中是否存在该单词。

     

该单词可以由顺序相邻的单元的字母构成,其中“相邻”单元是水平或垂直相邻的单元。相同的字母单元格不得多次使用。

我用在线帮助写的解决方案如下:

class Solution {
public:

    //compare this with Max Area of Island:
    //they 'look' similar, but this one uses a backtracking approach since we retract when we don't find a solution
    //In case of Max Area Of Island, we are not 'coming back' - technically we do come back due to recursion, but we don't track     
    //that since we don't acutally intend to do anything - we are just counting the 1s.

    bool exist(vector<vector<char>>& board, string& word) {
        if(board.empty()) return false;

        for(int i=0; i<board.size(); i++) {
            for(int j=0; j<board[0].size(); j++) {
                //if(word[0] == board[i][j])
                    if(existUtil(board, word, i, j, 0))    //matching the word[i][j] with 0th character of word
                        return true;
            }
        }

        return false;
    }

    bool existUtil(vector<vector<char>>& board, string& word, int i, int j, int match) {
        if(match==word.size()) return true;
        if(i<0 || i>=board.size() || j<0 || j>=board[0].size()) return false;
        if(board[i][j]!=word[match]) return false;

        board[i][j] = '*';
        bool ans = existUtil(board, word, i+1, j, match+1) || //[i+1,j]
               existUtil(board, word, i-1, j, match+1) || // [i,j+1]
               existUtil(board, word, i, j+1, match+1) || // [i-1,j]
               existUtil(board, word, i, j-1, match+1);   // [i,j-1]
        board[i][j] = word[match];

        return ans;
    }
};

我的问题很简单 - 为什么我们使用backtracking方法而不仅仅是传统的DFS?与我们所做的非常相似,我们可以从每个角色开始并执行DFS以确定我们是否可以找到目标词。但是我们没有这样做,为什么?

我考虑了很多,并提出了以下推理,但我不确定 - 我们使用回溯方法,因为相同的字母单元格可能不会被多次使用。所以,当我们做回溯时,我们用'*'替换原始字符,然后在我们回来时重新替换它。但这在某种程度上感觉不对,因为我们可以使用visited矩阵代替。

有人可以解释为什么我们使用回溯而不是DFS吗?

1 个答案:

答案 0 :(得分:0)

  

问:我的问题很简单 - 为什么我们使用回溯方法而不仅仅是传统的DFS?

因为回溯比解决这类问题远比普通DFS更有效。

DFS和回溯之间的区别是微妙的,但我们可以总结如下:DFS是一种用于搜索图形的技术,而回溯是一种问题解决技术(它由 DFS +修剪组成,此类程序称为反向攻击者。因此,DFS访问每个节点,直到找到所需的值(在您的情况下是目标词),而回溯更聪明 - 当确定在那里找不到目标词时,它甚至不访问特定分支。

想象一下,你有一本包含所有可能单词的词典,并在棋盘上搜索,找到棋盘上存在的所有单词(Boggle游戏)。你开始穿过董事会,偶然发现了这些字母,“A&#39; A&#39;&#39; C&#39;按此顺序,所以当前前缀是&#34; JAC&#34;。大。让我们看看字母C&#39;的邻居,例如他们是&#39; A&#39;&#39; Q&#39;,&#39; D&#39;&#39; F&#39;。普通DFS会做什么?它会跳过A&#39;因为它来自那个节点到C&#39;但它会盲目地访问每个剩余的节点,希望找到一些单词,即使我们知道没有单词以#34; JACQ&#34开头;,&#34; JACD&#34;和&#34; JACF&#34;。 Backtracker会立即用#J; JACQ&#34;,&#34; JACD&#34;和&#34; JACF&#34;通过例如咨询从字典构建的辅助trie数据结构。在某些时候甚至DFS会回溯,但只有当它没有去哪里时 - 即所有周围的信件都已被访问过。

总而言之 - 在您的示例中,传统的DFS会为每个节点盲目地检查所有邻居节点,直到找到目标字或者直到所有邻居都被访问 - 它才会回溯。另一方面,Backtracker会不断检查我们是否在#34;正确的轨道上#34;并且代码中执行此操作的关键行是:

if (board[i][j] != word[match]) return false;