广度优先搜索

BFS算法思想

  • 二叉树的层次遍历是一种特殊的广度优先遍历.
  • 它的基本思想是:
    首先访问起始顶点v,接着由v的节点出发,访问v的各个未访问过的邻接顶点w1,w2…wi…,再从这些访问过的节点访问…访问他们未访问过的邻接顶点.直到所有节点都被访问.
  • 算法必须借助一个辅助队列

应用

使用场景

  • 输入数据: 没有什么特征. 不像深度搜索.有递归的特性.若是树和图.使用BFS概率更高
  • 状态转移: 树或有向无环DAG图
  • 求解目标: 最短路径

思考步骤

一. 是求路径长度,还是路径本身(或者动作序列)?

  • 若是求路径长度,则状态里面要存路径的长度.
  • 若是要求路径或者动作序列
  1. 要用一棵树存储宽搜过程中的路径
  2. 是否可以预估状态的个数的上限?
    能预估状态总数,则开一个大数组,用树的双亲表示法;
    若不能预估,则要使用一棵通用的树.

二. 如何表示状态?

  • 即一个状态需要存储哪些必要的数据.才能完整地提供扩展下一步状态的所有信息

三. 如何扩展状态?

  • 这一步与第二步有关.
  • 对于隐式图! 要在第一步想清楚状态所带的数据.

四. 如何判断重复?

  • 如果状态转移是一颗树! 不会出现回路, 不需要判重!
  • 若是一个图. 需要判重!
  1. 若果是求最短路径或一条路径. 只需要让点不重复!!! (如删除这些状态)?
  2. 若是求所有路径.注意,此时状态转移图是 有向无环图 .即允许两个父节点指向同一个子节点. 具体实现时,每个节点需要 延迟 加入已访问集合 visited ,要等一层全部访问后,再加入到 visited 集合
  3. 具体实现:
  1. 状态是否存在完美的哈希方案? 即将状态一一映射到整数,互相之间不会冲突.
  2. 如不存在,则需要使用通用的 哈希表来判重.

五. 目标状态是否已知?

  • 若给出目标状态.可以带来很大遍历.

LeetCode 127 Word Ladder

给定你两个单词,要你从begin那个变换成end那个
要求变换过去改变最小的次数.
每次只能变换一次.
而且变换过程出现的单词,必须出现在给定的字典中
Return 0 if there is no such transformation sequence.

方法1:

/*
		广度优先搜索
		1. 从头开始遍历,尝试改变i位置的值
		2. 若存在于worldlist则进行判断.
			a) 若为endWord,则可以返回了
			b) 不是endWord,将中间过程加入Map中.
		3. 不存在于Wordlist...

*/
class Solution {
    public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {

        set<string> WordSet(wordList.begin(), wordList.end()); // 存储字典,防止重复
        if (!WordSet.count(endWord)) return 0;
        unordered_map<string, int> WordMap;	     // 用来存储过程的单词以及路径的长度
        WordMap.insert(make_pair(beginWord, 1)); 
        queue<string> q;
        q.push(beginWord);

        while (!q.empty()) {
            string word = q.front();	q.pop();
            // 从0开始,BFS
            for (int i = 0; i < word.size(); ++i) {
                string newWord = word;
                // 尝试更换字符
                for (char c = 'a'; c <= 'z'; ++c) {
                    newWord[i] = c;
                    if (WordSet.count(newWord) && newWord == endWord) {
                        return WordMap[word] + 1;
                    }
                    // 存在于字典中,但是不在路径中
                    if (WordSet.count(newWord) && !WordMap.count(newWord)) {
                        q.push(newWord);
                        WordMap[newWord] = WordMap[word] + 1;
                    }
                }
            }

        }
        return 0;
    }
};

方法2:

  • 同样是广度优先搜索
class Solution {
	public:
		int ladderLength(string beginWord, string endWord, vector<string>& wordList) {

			set<string> WordSet(wordList.begin(), wordList.end()); // 存储字典,防止重复
			if (!WordSet.count(endWord)) return 0;
			int res = 0;
			queue<string> q;
			q.push(beginWord);

			while (!q.empty()) {
				// size指的是相同层的,从后面遍历,减少时间
				for (int i = q.size(); i > 0; --i) {
					string word = q.front(); q.pop();
					if (word == endWord) {
						return res + 1;
					}
					for (int j = 0; j < word.size(); ++j) {
						string newWord = word;
						for (char c = 'a'; c <= 'z'; ++c) {
							newWord[j] = c;
							// 若字典中存在
							if (WordSet.count(newWord)/*&&newWord!=word*/) {
								q.push(newWord);
								WordSet.erase(newWord);
							}
						}
					}
				}
				res++;
			}
			return 0;
		}
	};

LeetCode 126 Word Ladder II

找到从start到end的所有最短路径

方法1:

  • 广度优先搜索+深度优先搜索
  • 超时!
  • 使用BFS,找到最短的路径,记录从start到end的节点,保存每个节点的下一层节点到MAP中
  • 使用DFS,从MAP中输出和最短路径相同距离的距离.
class Solution {
    public:
    vector < vector<string>> res;
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {

        unordered_map<string, vector<string>> next; // 记录下一层
        unordered_map<string, int> distance; // 用于记录距离
        unordered_set<string> dict(wordList.begin(), wordList.end());

        queue<string> q; // 遍历bfs使用

        vector<string> path;

        // bfs
        q.push(beginWord);
        distance[beginWord] = 0;

        while (!q.empty()) {
            string cur = q.front(); q.pop();
            if (cur == endWord) {
                break;
            }
            int cur_dis = distance[cur];
            vector<string> cur_next;
            for (int i = 0; i < cur.size(); ++i) {
                string newWord = cur;
                for (auto c = 'a'; c <= 'z'; ++c) {
                    newWord[i] = c;
                    // 找不到就下一轮
                    if (c == cur[i] || dict.find(newWord) == dict.end()) continue;
                    auto it = distance.find(newWord);
                    if (it == distance.end()) {
                        // 找不到就要将这个带加入cur_next
                        q.push(newWord);
                        distance[newWord] = cur_dis + 1;
                    }
                    cur_next.push_back(newWord);
                }
            }
            next[cur] = cur_next;
        }

        path.push_back(beginWord);
        dfs(path, next, distance, beginWord, endWord);
        return res;
    }

    void dfs(vector<string> &path, unordered_map<string, vector<string>> &next,
             unordered_map<string, int> distance, string cur, string end) {
        if (cur == end) {
            res.push_back(path);
        }
        else {
            auto vec = next[cur];
            int cur_dis = distance[cur];
            for (int i = 0; i < vec.size(); i++) {
                if (distance[vec[i]] != cur_dis + 1)continue;
                path.push_back(vec[i]);
                dfs(path, next, distance, vec[i], end);
                path.pop_back();
            }
        }
    }
};

方法2:

class Solution {
    public:
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        vector<vector<string>> res;

        // 查询字典
        unordered_set<string> dict(wordList.begin(), wordList.end());
        vector<string> p = { beginWord };
        queue<vector<string>> paths;
        paths.push(p); // 插入以开始词为节点的路径

        //level是记录循环中当前路径的长度
        //minLevel记录的最短的记录
        int level = 1, minLevel = INT_MAX;

        // 定义一个set变量words,用来记录已经出现过的路径中的词
        unordered_set<string> words;

        while (!paths.empty()) {
            auto t = paths.front(); paths.pop(); // 取出一条路径

            // 剪枝
            if (t.size() > level) {
                // 将已经在路径中出现过的字符串在字典中删除
                for (string w : words) {
                    dict.erase(w);
                }
                words.clear();
                // 更新当前路径的长度
                level = t.size();
                // 如果当前路径的长度大于最小的距离说明遍历结束
                if (level > minLevel) break;
            }
            string last = t.back();
            for (int i = 0; i < last.size(); ++i) {
                string newLast = last; // 对last进行扩展
                for (char c = 'a'; c <= 'z'; ++c) {
                    newLast[i] = c;
                    if(!dict.count(newLast)) continue; // 若在字典中找不到,继续扩展
                    // words加入新词,说明这个词遍历过
                    words.insert(newLast); 
                    // 原有路径的基础上加上这个新词生成一条新路径
                    vector<string> newPath = t;
                    newPath.push_back(newLast);
                    if (newLast == endWord) {
                        res.push_back(newPath);
                        minLevel = level; // 为什么不需要比较呢?因为上面有剪枝
                    } else {
                        // 还没有到最后一个词,就将这条路径继续加入paths
                        paths.push(newPath);
                    }
                }
            }
        }
        return res;
    }
};