宽度优先搜索练习

• 二叉树上的宽搜 BFS in Binary Tree
• 图上的宽搜 BFS in Graph 
• 拓扑排序 Topological Sorting
• 棋盘上的宽搜 BFS

若是求最短路径,则很大可能是使用宽度优先搜索,也可能是使用DP

若是求最长路径,则很大可能是使用深度优先搜索,也可能是使用DP

 

1.二叉树的层次遍历

2.二叉树的锯齿形层次遍历

3.图的节点,边的数据结构表示以及BFS

4.拓扑排序

5.课程表I,II

6.单词接龙

题目一:二叉树的层次遍历

https://leetcode-cn.com/problems/binary-tree-level-order-traversal/

给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。


class Solution {
public:
    /**
     * @param root: A Tree
     * @return: Level order a list of lists of integer
     */
    vector<vector<int>> levelOrder(TreeNode * root) {
        // write your code here
        vector<vector<int>> res;
        if(root==NULL) return res;
        queue<TreeNode*> m_queue;
        m_queue.push(root);
        while(!m_queue.empty()){
            int size = m_queue.size();
            vector<int> tmp;
            for(int i = 0 ; i < size; i++){
                TreeNode* curNode = m_queue.front();
                m_queue.pop();
                int curValue = curNode->val;
                tmp.push_back(curValue);

                if(curNode->left!=NULL){
                    m_queue.push(curNode->left);
                }
                if(curNode->right!=NULL){
                    m_queue.push(curNode->right);
                }
                
            }
            res.push_back(tmp);
        }
        return res;
    }
};

题目2:  二叉树的锯齿形层次遍历

https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/

class Solution {
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if(root==NULL) return res;
        queue<TreeNode*> m_queue;
        m_queue.push(root);
        int curLevel = 0;
        while(!m_queue.empty()){
            int size = m_queue.size();
            vector<int> tmp;
            for(int i = 0 ; i < size; i++){     
                TreeNode* curNode = m_queue.front();
                m_queue.pop();
                int curValue = curNode->val;

                if(curNode->left!=NULL){
                    m_queue.push(curNode->left);
                } 

                if(curNode->right!=NULL){
                    m_queue.push(curNode->right);
                }

                tmp.push_back(curValue);
            }
            res.push_back(tmp);
        }
        


        //改变的部分
            int level = 1;
            for(int i = 0 ; i < res.size(); i++){
                level++;
                if(level%2){
                    reverse(res[i].begin(),res[i].end());
                }
            }
            return res;
        }
    
};

 

图的BFS和树的BFS有什么区别呢?图可能回到原来的节点,而树则不会,树是一直往下的。可使用hashmap或者数组来进行标记。这里我们写一个图的模板,以后任何关于图的结构就都使用这个模板。

#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include<queue>

using namespace std;

// To execute C++, please define "int main()"

class GraphNode;
class GraphEdge;


class GraphNode{
public:
  int value;
  int in ; //入度
  int out;//出度
  vector<GraphNode*> nexts;//相邻的顶点
  vector<GraphEdge*> edges;//相邻的边
  GraphNode(int value){
    this->value = value;
    this->in    = 0;
    this->out   = 0;
  }
};
class GraphEdge{
public:
  int weight;      //边的权重
  GraphNode* from; 
  GraphNode* to;
  GraphEdge(int weight,GraphNode* from, GraphNode* to){
    this->weight = weight;
    this->from = from;
    this->to   = to;
  }
};

class Graph{
public:
  unordered_map<int,GraphNode*> NodeMap;
  unordered_set<GraphEdge*> Edges;
};

Graph* GenerateGraph(vector<vector<int>>& edges){
  Graph* G = new Graph();
  for(int i = 0 ; i < edges.size();i++){
      int weight = edges[i][0];
      int from   = edges[i][1];
      int to     = edges[i][2];
      if(!G->NodeMap.count(from)){
        //如果图中第一次出现这个点
        G->NodeMap.insert({from,new GraphNode(from)});
      }
      if(!G->NodeMap.count(to)){
        G->NodeMap.insert({to,new GraphNode(to)});
      }
      GraphNode* fromNode = G->NodeMap.find(from)->second;//这样的写法实际上有点危险,因为可能find不到东西,这个时候->就可能报错了 取决你的输入能都正确了,比如1 []
      GraphNode* toNode   = G->NodeMap.find(to)->second;
      
    
      fromNode->out++;
      fromNode->nexts.push_back(toNode);//有向图
      toNode->in++;
      GraphEdge* newEdge = new GraphEdge(weight,fromNode,toNode); 
      fromNode->edges.push_back(newEdge);
      G->Edges.insert(newEdge);
  }
  return G;
}



/*
    测试代码:使用BFS进行测试是否正确

*/

class BFS{
public:
  void bfs(GraphNode* root){
    if(root == NULL) return;
    queue<GraphNode*> m_queue;
    unordered_set<GraphNode*> m_set;
    m_queue.push(root);
    while(!m_queue.empty()){
      GraphNode* curNode = m_queue.front();

       /*
            这里的输出是为了验证代码是否正确
        */
      for(auto edge: curNode->edges){
        cout << edge->weight << " " 
             << edge->from->value << " "
             << edge->to->value   << endl;
      }

      m_queue.pop();
      for(auto next : curNode->nexts){/*图的宽度优先算法会判断该点之前是否进入过*/
        if(!m_set.count(next)){
            m_set.insert(next);
            m_queue.push(next);
        }
      }
    }
  }
};


int main() {
  
  //weight from to
  vector<vector<int>> edges{
    {1,0,1},
    {2,0,3},
    {3,1,3},
    {4,0,5}};
  Graph* G =  GenerateGraph(edges);  
  BFS m_bfs;
  m_bfs.bfs(G->NodeMap.find(0)->second); 
  
  return 0;
}

最终的输出为,与输入一致....

1 0 1
2 0 3
4 0 5
3 1 3

 

题目3:验证一个图是都是树

https://www.lintcode.com/problem/graph-valid-tree/description







class Solution {
public:
    /**
     * @param n: An integer
     * @param edges: a list of undirected edges
     * @return: true if it's a valid tree, or false
     */
    bool validTree(int n, vector<vector<int>> &edges) {
        // write your code here
    
        if(n == 0 ) return false;
        if(n != edges.size()+1) return false;
        if(n == 1 && edges.size() == 0) return true;//为了处理 1 []的情况
        
        unordered_map<int,unordered_set<int>> Graph;
        for(int i  = 0 ; i < edges.size(); i++){
            int from = edges[i][0];
            int to   = edges[i][1];
            
            if(!Graph.count(from)){
                Graph.insert({from,unordered_set<int>()});
            }
            
            if(!Graph.count(to)){
                Graph.insert({to,unordered_set<int>()});
            }
            
            Graph.find(from)->second.insert(to);
            Graph.find(to)->second.insert(from);
        }
        
        queue<int> m_queue;
        unordered_set<int> visited;
        m_queue.push(0);//因为标号从0~n-1开始
        visited.insert(0);
        while(!m_queue.empty()){
            int curNode = m_queue.front();
            m_queue.pop();
          //  if(Graph.find(curNode) == Graph.end()) brea;//这句非常关键,可能会出现 1 []这种情况,但是如果一开始就截段了就不需要加了
            unordered_set<int> adj = Graph.find(curNode)->second;
            for(auto item : adj){
                if(!visited.count(item)){//因为是无向图,所以需要加一个visit来验证之前是否看过
                    visited.insert(item);
                    m_queue.push(item);
                }
            }
        }
        return visited.size() == n;
        
    }
};

官方的解答则更为优雅,主要是由于官方的图结构使用vector<unordered_Set<int>>来表示.

// bfs version
class Solution {
public:
    bool validTree(int n, vector<pair<int, int>>& edges) {
        vector<unordered_set<int>> g(n, unordered_set<int>());
        unordered_set<int> v;
        queue<int> q;
        q.push(0);
        v.insert(0);
        for (auto a : edges) {
            g[a.first].insert(a.second);
            g[a.second].insert(a.first);
        }
        while (!q.empty()) {
            int t = q.front(); q.pop();
            for (auto a : g[t]) {
                if (v.find(a) != v.end()) return false;
                v.insert(a);
                q.push(a);
                g[a].erase(t);
            }
        }
        return v.size() == n;
    }
};

也可以使用并查集进行解决

// union find version
class Solution {
public:
    /**
     * @param n an integer
     * @param edges a list of undirected edges
     * @return true if it's a valid tree, or false
     */
    bool validTree(int n, vector<vector<int>>& edges) {
        // Write your code here
        vector<int> root(n, -1);
        for(int i = 0; i < edges.size(); i++) {
            int root1 = find(root, edges[i][0]);
            int root2 = find(root, edges[i][1]);
            if(root1 == root2)
                return false;
            root[root1] = root2;
        }
        return edges.size() == n - 1;
    }
    int find(vector<int> &root, int e) {
        if(root[e] == -1)
            return e;
        else
            return root[e] = find(root, root[e]);
    }
};

题目4:克隆图

https://www.lintcode.com/problem/clone-graph/description

 1.先把节点的对应关系表构造出来

2.然后把节点的边关系表构造出来

class Solution {
public:
    Node* cloneGraph(Node* node) {
        if(node == nullptr) return nullptr;
        //第一次遍历找到相互之前的对应关系
        unordered_map<Node*,Node*> TransformRule;
        queue<Node*> m_queue;
        unordered_set<Node*> m_set;
        m_queue.push(node);
        m_set.insert(node);

        while(!m_queue.empty()){
            Node* curNode = m_queue.front();
            m_queue.pop();
            Node* cloneNode = new Node(curNode->val);
            TransformRule.insert({curNode,cloneNode});

            for(auto neighbor : curNode->neighbors){
                if(!m_set.count(neighbor)){
                    m_set.insert(neighbor);
                    m_queue.push(neighbor);
                }
              
            }
        }
        //至此完成了转换关系,接下来负责边的连接
        for(auto item : m_set){
            for(auto neighbor: item->neighbors){
                TransformRule.find(item)->second->neighbors.push_back( TransformRule.find(neighbor)->second   );
            }
        }
        return TransformRule.find(node)->second;
    }
};

官方解法则更加优雅,在建立关系表的同时边的关系也确定下来了。主要就是编程思路不一样,在压入节点之前就已经把关系建立好了

class Solution {
public:
    /*
     * @param node: A undirected graph node
     * @return: A undirected graph node
     */
    UndirectedGraphNode* cloneGraph(UndirectedGraphNode* node) {
        if (!node) {
            return nullptr;
        }
        UndirectedGraphNode *p1 = node;
        UndirectedGraphNode *p2 = new UndirectedGraphNode(node->label);
        unordered_map<UndirectedGraphNode*, UndirectedGraphNode*> map;
        queue<UndirectedGraphNode*> q;
        q.push(node);
        map[node] = p2;

        while (!q.empty()) {
            p1 = q.front(); p2 = map[p1];
            q.pop();
            for (int i = 0; i < p1->neighbors.size(); i++) {
                UndirectedGraphNode *nb = p1->neighbors[i];
                if (map.count(nb)) {
                    p2->neighbors.push_back(map[nb]);
                } else {
                    UndirectedGraphNode *temp = new UndirectedGraphNode(nb->label);
                    p2->neighbors.push_back(temp);
                    map[nb] = temp;
                    q.push(nb);
                }
            }
        }

        return map[node];
    }
};

题目4:课程表 https://leetcode-cn.com/problems/course-schedule/

拓扑排序

 当然你可以按照图的模板慢慢写出来,但实际上我们写代码的时候只是用基本的结构就能代表了一个图了.

        unordered_map<int,int> NodeToNum;//节点的数量
        unordered_map<int,unordered_set<int>> neighbors;//节点的邻居

 

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        unordered_map<int,int> NodeToNum;//节点的数量
        unordered_map<int,unordered_set<int>> neighbors;//节点的邻居
        for(int i = 0 ;i < prerequisites.size(); i++){
            int from = prerequisites[i][0];
            int to   = prerequisites[i][1];
            if(!NodeToNum.count(from)){
                NodeToNum.insert({from,0});
            }
            if(!NodeToNum.count(to)){
                NodeToNum.insert({to,0});
            }
            NodeToNum[to]++;
            neighbors[from].insert(to);
        }
        //至此图节点的对应关系已经
        queue<int> m_queue;
        for(int i = 0 ; i < NodeToNum.size(); i++){
            if(NodeToNum[i] == 0){
                m_queue.push(i);
            }
        }
        //至此入度为0的点就进入了队列
        while(!m_queue.empty()){
            int curNode = m_queue.front();//查看当前节点的邻居
            m_queue.pop();

            for(int item : neighbors[curNode]){
                NodeToNum[item]--;
                if(NodeToNum[item]==0){
                    m_queue.push(item);
                }
            }
            NodeToNum.erase(curNode);
        }
        return NodeToNum.size() == 0 ;
    }
};

题目5: 课程表II,需要考虑一些特殊的处理

输入
3 [[1,0]]
输出
[0,1]
预期结果
[2,0,1]
class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        unordered_set<int> visited;
        unordered_map<int,int> NodeToNum;//节点的数量
        unordered_map<int,unordered_set<int>> neighbors;//节点的邻居
        for(int i = 0 ;i < prerequisites.size(); i++){
            int to = prerequisites[i][0];
            int from   = prerequisites[i][1];
            if(!NodeToNum.count(from)){
                NodeToNum.insert({from,0});
            }
            if(!NodeToNum.count(to)){
                NodeToNum.insert({to,0});
            }
            NodeToNum[to]++;
            neighbors[from].insert(to);
        }
        //至此图节点的对应关系已经
        queue<int> m_queue;
        for(int i = 0 ; i < NodeToNum.size(); i++){
            if(NodeToNum[i] == 0){
                m_queue.push(i);
            }

        }
        //至此入度为0的点就进入了队列
        vector<int> res;
        while(!m_queue.empty()){
            int curNode = m_queue.front();//查看当前节点的邻居
            res.push_back(curNode);
            visited.insert(curNode);
            m_queue.pop();


            for(int item : neighbors[curNode]){
                NodeToNum[item]--;
                if(NodeToNum[item]==0){//因为是有xiang
                    m_queue.push(item);
                }
            }
            NodeToNum.erase(curNode);
        }

        //如果存在环
        if(NodeToNum.size() != 0){
            return vector<int>();
        }

        for(int i = 0 ; i < numCourses; i++ ){
            if(!visited.count(i)){
                res.push_back(i);
            }
        }
        return res ;
    }
};

官方给出了一个非常优雅的实现

class Solution {
public:
    /**
     * @param numCourses a total of n courses
     * @param prerequisites a list of prerequisite pairs
     * @return true if can finish all courses or false
     */
    bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
        // Write your code here
        vector<unordered_multiset<int>> edges(numCourses);
        vector<int> d(numCourses, 0);
        for(int i = 0; i < prerequisites.size(); ++ i) {
            edges[prerequisites[i].second].insert(prerequisites[i].first);
            d[prerequisites[i].first] ++;
        }

        queue<int> q;
        for (int i = 0; i < numCourses; ++i)
            if (d[i] == 0)
                q.push(i);

        int node = 0;
        while (!q.empty()) {
            int x = q.front(); q.pop();
            node ++;
            for(auto it = edges[x].begin(); it != edges[x].end(); ++ it) {
                -- d[*it];
                if (d[*it] == 0) {
                    q.push(*it);
                }
            }
        }
        return node == numCourses;
    }
};

 题目6:序列重构https://www.lintcode.com/problem/sequence-reconstruction/description

判断是否只存在一个拓扑排序的序列
只需要保证队列中一直最多只有1个元素即可

在写程序的时候遇到了一个bug,注意以后程序不能这样写

int main() {
        vector<int> org{1};
        unordered_map<int,unordered_set<int>> m_map;
        unordered_map<int,int>  indegree;
        for(auto i : org){
            m_map.insert({i,unordered_set<int>()});
            indegree[i] = 0;
        }
        
        queue<int> m_queue;
        std::cout <<  "indegree.size : " << indegree.size() << std::endl;
         for(int i = 0 ; i < indegree.size(); i++){//这里hellp会输出两次,indegree[]本来是不存在Indegree[0]的
            if(indegree[i] == 0) {
                m_queue.push(i);
                cout<< "hello " <<endl;
            }
        } 
        cout << "===============" << endl;
  return 0;
}

题目7: 单词接龙 https://leetcode-cn.com/problems/word-ladder/

这真的只是中等题??

1.构建一个预处理数组,然后再进行宽度优先遍历/

输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

输出: 5

解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
     返回它的长度 5。

 思路:实际上还是进行暴力搜索,然后查看

 

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
          if(beginWord == endWord){
              return 1;
          }  
          if(wordList.size() == 0){
              return 0;
          }

          unordered_set<string> m_set;
          for(int i = 0 ; i < wordList.size(); i++){
              m_set.insert(wordList[i]);
          }

          queue<string> m_queue;
          m_queue.push(beginWord);

         int length = 0;
          while(!m_queue.empty()){  //开始bfs
            int size = m_queue.size();
            length++;
            for(int i =  0; i < size; i++){
              string cur_string = m_queue.front();
              m_queue.pop();
        
              if(cur_string == endWord){
                  return length;
              }

              for(int i =  0 ; i < cur_string.size(); i++){
                  char oldchar = cur_string[i];
                  for(char c = 'a'; c<= 'z'; c++){
                    if(c == oldchar) continue;
                    cur_string[i] = c;
                    if(m_set.count(cur_string)){
                        m_queue.push(cur_string);
                        m_set.erase(cur_string);
                    }
                }
                  cur_string[i] = oldchar;  
              }
            }
          }
        return 0;
    }
};

 

矩阵类型的BFS,待更新

 

总之,宽度优先搜索非常适合于求最短路径,以及最少需要多少步,以及一些关于联通域的问题,比如岛屿的数量

还有其他题目,下次再看了.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值