双向BFS及优化

单向BFS只从起点一端开始搜索,双向BFS则是从起点和终点两边扩展节点,当节点发生重合时即找到最优解。

假设起点到终点深度为d,每个节点平均有n个分支,那么单向BFS需要扩展的节点个数为《双向BFS及优化》。而从起点终点同时扩展,则只需《双向BFS及优化》

实现方法为:维护两个队列,分别保存从起点和终点扩展到的下一层,这样保证了两个队列中的节点处于相同的深度(即:距离起点或者终点深度相同)。则当拓展到《双向BFS及优化》时一定发生重合,得到最优解。

LeetcodeWord Ladder 代码如下:

class Solution {
public:
    int BFS(bool dir, queue<string>& qu, unordered_set<string>& wordList){
        queue<string> next;
        while(!qu.empty()){
            string cur = qu.front();
			qu.pop();
            int step = dis[dir][cur];
            ++step;
            for(int i = 0; i < len; ++i){
                char c = cur[i];
                for(char j = 'a'; j <= 'z'; ++j){
                    if(j == c)
                        continue;
					cur[i] = j;
                    if(wordList.find(cur) != wordList.end() && dis[dir][cur] == INT_MAX){
                        if(dis[!dir][cur] != INT_MAX)
                            return dis[!dir][cur] + step + 1;
                        next.push(cur);
                        dis[dir][cur] = step;
                    }
                }
				cur[i] = c;
            }
        }
        qu = next;
        return 0;
    }
    int ladderLength(string beginWord, string endWord, unordered_set<string>& wordList) {
        len = beginWord.size();
        queue<string> qu1, qu2;
        for(unordered_set<string>::iterator it = wordList.begin(); it != wordList.end(); ++it){
            dis[0].insert(make_pair(*it, INT_MAX));
            dis[1].insert(make_pair(*it, INT_MAX));
        }
        dis[0][beginWord] = 0, dis[1][endWord] = 0;
        qu1.push(beginWord), qu2.push(endWord);
        bool dir = false;
        while(!qu1.empty() && !qu2.empty()){
            int t = BFS(dir, qu1, wordList);
            if(t)
                return t;
            t = BFS(!dir, qu2, wordList);
            if(t)
                return t;
        }
        return 0;
    }
private:
    unordered_map<string, int> dis[2];
    int len;
};

与单向bfs运行时间1200ms相比,以上朴素的双向BFS时间为212ms,差别还是很明显的。

当然,针对扩展方式还可以进行优化——交替逐层扩展。由于两端扩展节点个数可能差别很大,所以每次选择节点个数少的队列进行扩展。return的条件仍为有节点重叠。

正确性证明如下(证明不会因为提前break而错过最优解):

假设当前得到最优解为X,不失一般性,假设X现在是从起点搜索到的,则X已经被终点搜索过了,所以才终止搜索。使用反证法证明,假设存在最优解Y。

1.  若X和Y在同一层,则当且仅当从终点搜索时Y位于X前面层,Y才为最优解。设从起点搜索Y的前一个节点为z,由于终点方向从Y所在层,扩展到X所在层,这个过程是连续,所以在这个过程中会首先扩展到z,则z被当做最优解,程序return。如下图所示:

《双向BFS及优化》

2.  Y位于X的后面,令X和Y相隔i层,则Y为最优解的充分条件为:从终点方向,Y与X的层数之差大于i,记为j。令起点方向上,z是y的前j个节点。那么从终点方向搜索时,z应该不晚于x被搜到。因此也不会错过最优解。如下图所示:

《双向BFS及优化》

所以使用交替逐层能保证算法正确性。代码如下:

class Solution {
public:
    int ladderLength(string beginWord, string endWord, unordered_set<string>& wordList) {
        queue<string> qu1, qu2;
        qu1.push(beginWord);
        qu2.push(endWord);
        int len = beginWord.size();
        unordered_map<string, int> dis[2];
        for(unordered_set<string>::iterator it = wordList.begin(); it != wordList.end(); ++it){
            dis[0].insert(make_pair(*it, INT_MAX));
            dis[1].insert(make_pair(*it, INT_MAX));
        }
        dis[0][beginWord] = 0, dis[1][endWord] = 0;
        bool dir = false;
        while(!qu1.empty()){
            queue<string> layer;
            while(!qu1.empty()){
                string cur = qu1.front();
                qu1.pop();
                int step = dis[dir][cur];
                ++step;
                for(int i = 0; i < len; ++i){
                    char c = cur[i];
                    for(char j = 'a'; j <= 'z'; ++j){
                        if(j == c)
                            continue;
                        cur[i] = j;
                        if(wordList.find(cur) != wordList.end() && dis[dir][cur] == INT_MAX){
                            if(dis[!dir][cur] != INT_MAX)
                                return step + dis[!dir][cur] + 1;
                            layer.push(cur);
                            dis[dir][cur] = step;
                        }
                    }
                    cur[i] = c;
                }
            }
            if(layer.size() > qu2.size()){
                qu1 = qu2;
                qu2 = layer;
				dir = !dir;
            }
            else
                qu1 = layer;
        }
        return 0;
    }
};

时间比较如下:单向BFS1200ms,朴素双向BFS212ms,交替逐层优化为64ms。

    原文作者:BFS
    原文地址: https://blog.csdn.net/ww32zz/article/details/50755225
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞