理解广度优先搜索

1.   定义

BFS是Breath First Search的缩写,是广度优先搜索的意思,是图的遍历方式的一种。

由于BFS是从起点一层一层的进行搜索的,所以凡是需要求最短路径的问题,都可以尝试看BFS能否解决,比如Dijkstra的单源最短路径算法使用了BFS的思想。另外,在执行广度优先搜索的过程中将构造出一棵树,这也是Prim的最小生成树算法思想。在做BFS的时候,有两点需要特别注意:

1.      为了防止搜索进入无限循环,节点需要判重,也就是已经访问过的节点不要再访问了,所以需要记录一个节点是不是已经访问过了。

2.      另外BFS有可能会要求记录路径,这时候需要记录一个节点的前驱节点。这些信息可以保存在节点数据结构里,也可以存在map里,比如C++的unordered_map。如果只记录一个前驱节点,那我们只能记录一个路径。但是如果记录所有前驱,则我们可以记录所有路径。

算法导论上的伪代码如下,很经典

1  for each vertex u ∈ V [G] - {s}

2      do color[u] ← WHITE

3         d[u] ←∞

4         π[u] ← NIL

6  d[s] ← 0

7  π[s] ← NIL

8  Q ← ?

9 ENQUEUE(Q, s)

10  while Q ≠ ?

11      do u ← DEQUEUE(Q)

12         for each v ∈ Adj[u]

13             do if color[v] = WHITE

14                   then color[v] ← GRAY

15                        d[v] ← d[u] + 1

16                        π[v] ← u

17                        ENQUEUE(Q, v)

18         color[u] ← BLACK

2.   队列

C++中的queue,这是一种FIFO队列,还有一种Priorityqueue,这里就不讨论了。如果不使用STL,那么队列一般用数组或链表来实现。


(constructor)


Construct queue (public member function )


empty


Test whether container is empty (public member function )


size


Return size (public member function )


front


Access next element (public member function )


back


Access last element (public member function )


push


Insert element (public member function )


emplace


Construct and insert element (public member function )


pop


Remove next element (public member function )


swap


Swap contents (public member function )

3.   例一:八数码问题

八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。

分析:这道题关键在于抽象出图的节点和路径,看下图:每个状态都是图的一个节点,而从一个状态如果能一次转换到另一个状态的话,我们认为这两个节点之间有边。这样,就抽象出一个图。这样,对这个图做BFS,就能找到到目标状态的最短路径。

unordered_map<string,int> _set;
string myswap(int i,int j,string s){
   char temp=s[i];
   s[i]=s[j];
   s[j]=temp;
   return s;
}
vector<string> f(string s, int d){
   vector<string> v;
   if(s.length()!=9){
       return v;
    }
   int i=0;
   for(;i<9;i++){
       if(s[i]=='0')
           break;
    }
   int x=i/3;
   int y=i%3;
   if(x-1>=0){
       string temp=myswap((x-1)*3+y,i,s);
       if(_set.find(temp)==_set.end()){
           v.push_back(temp);
           pair<string&,int> p(temp,d+1);
           _set.insert(p);
       }
    }
   if(x+1<=2){
       string temp=myswap((x+1)*3+y,i,s);
       if(_set.find(temp)==_set.end()){
           v.push_back(temp);
           pair<string&,int> p(temp,d+1);
           _set.insert(p);
       }
    }
   if(y-1>=0){
       string temp=myswap(x*3+y-1,i,s);
       if(_set.find(temp)==_set.end()){
           v.push_back(temp);
           pair<string&,int> p(temp,d+1);
           _set.insert(p);
       }
    }
   if(y+1<=2){
       string temp=myswap(x*3+y+1,i,s);
       if(_set.find(temp)==_set.end()){
           v.push_back(temp);
           pair<string&,int> p(temp,d+1);
           _set.insert(p);
       }
    }
   /*for(int i=0;i<v.size();i++){
       cout<<v[i]<<endl;
   }*/
   return v;
}

int bfs(string s){
   if(s.compare("123456780")==0)
       return 0;
   queue<string> q;
   q.push(s);
   _set.insert(make_pair<string&,int>(s,0));
   while(!q.empty()){
       string first=q.front();
       q.pop();
       vector<string> v=f(first,_set[first]);
       for(int i=0;i<v.size();i++){
           if(v[i].compare("123456780")==0){
                return _set[v[i]];
           }
           q.push(v[i]);
       }
    }
   return 0;
}

unordered_map<string,string> _set;
string myswap(int i,int j,string s){
   char temp=s[i];
   s[i]=s[j];
   s[j]=temp;
   return s;
}
vector<string> f(string s){
   vector<string> v;
   if(s.length()!=9){
       return v;
    }
    inti=0;
   for(;i<9;i++){
       if(s[i]=='0')
           break;
    }
   int x=i/3;
   int y=i%3;
   if(x-1>=0){
       string temp=myswap((x-1)*3+y,i,s);
       if(_set.find(temp)==_set.end()){
           v.push_back(temp);
           _set.insert(make_pair<string&,string&>(temp,s));
       }
    }
   if(x+1<=2){
       string temp=myswap((x+1)*3+y,i,s);
       if(_set.find(temp)==_set.end()){
           v.push_back(temp);
           _set.insert(make_pair<string&,string&>(temp,s));
       }
    }
   if(y-1>=0){
       string temp=myswap(x*3+y-1,i,s);
       if(_set.find(temp)==_set.end()){
           v.push_back(temp);
           _set.insert(make_pair<string&,string&>(temp,s));
       }
    }
   if(y+1<=2){
       string temp=myswap(x*3+y+1,i,s);
       if(_set.find(temp)==_set.end()){
           v.push_back(temp);
           _set.insert(make_pair<string&,string&>(temp,s));
       }
    }
   return v;
}

vector<string> bfs(string s){
   vector<string> _v;
   if(s.compare("123456780")==0){
       _v.insert(_v.begin(),s);
       return _v;
    }
   queue<string> q;
   q.push(s);
   string x="000000000";
   _set.insert(make_pair<string&,string&>(s,x));
   while(!q.empty()){
       string first=q.front();
        q.pop();
       vector<string> v=f(first);
       for(int i=0;i<v.size();i++){
           if(v[i].compare("123456780")==0){
                for(string_s=v[i];_s.compare(x)!=0;_s=_set[_s]){
                    _v.insert(_v.begin(),_s);
               }
                return _v;
           }
           q.push(v[i]);
       }
    }
   return _v;
}

4.   例二:word ladder

Given two words (beginWord and endWord),and a dictionary, find the length of shortest transformation sequence frombeginWord to endWord, such that:

Only one letter can be changed at a time

Each intermediate word must exist in thedictionary

For example,

Given:

start = "hit"

end = "cog"

dict =["hot","dot","dog","lot","log"]

As one shortest transformation is"hit" -> "hot" -> "dot" ->"dog" -> "cog",

return its length 5.

分析:

双向广度优先搜索法,是同时从初始状态和目标状态出发,采用广度优先搜索的策略,向对方搜索,如果问题存在解,则两个方向的搜索会在中途相遇,即搜索到同一个结点。将两个方向的搜索路径连接起来,就可以得到从初始结点到目标结点的搜索路径。由于采用双向搜索,需要使用两个队列。(采用了双向BFS后,这道题在Leetcode上的运行时间由660ms降低到了88ms)

unordered_map<string,int>_map_depth_begin;
unordered_map<string,int>_map_depth_end;
vector<string> linked(string s){
   vector<string> result;
   for(int i=0;i<s.length();i++){
       char c=s[i];
       for(char temp='a';temp<='z';temp++){
           if(temp!=c){
                s[i]=temp;
                result.push_back(s);
           }
       }
       s[i]=c;
    }
   return result;
}
int ladderLength(string beginWord, stringendWord, unordered_set<string>& wordDict) {
   wordDict.insert(beginWord);
   wordDict.insert(endWord);
   queue<string> q_begin;
   queue<string> q_end;
   q_begin.push(beginWord);
   q_end.push(endWord);
    _map_depth_begin[beginWord]=1;
   _map_depth_end[endWord]=1;
   while(!q_begin.empty() && !q_end.empty()){
       string first=q_begin.front();
       q_begin.pop();
       vector<string> result=linked(first);
       for(int i=0;i<result.size();i++){
           string second=result[i];
           if(wordDict.find(second)!=wordDict.end()){
               if(_map_depth_begin.find(second)==_map_depth_begin.end()){
                   _map_depth_begin[second]=_map_depth_begin[first]+1;
                    if(second.compare(endWord)==0){
                        return_map_depth_begin[second];
                    }
                   if(_map_depth_end.find(second)!=_map_depth_end.end()){
                        return_map_depth_begin[second]+_map_depth_end[second]-1;
                    }
                    q_begin.push(second);
                }
           }
       }
       string first_end=q_end.front();
       q_end.pop();
       vector<string> result_end=linked(first_end);
       for(int i=0;i<result_end.size();i++){
           string second_end=result_end[i];
           if(wordDict.find(second_end)!=wordDict.end()){
               if(_map_depth_end.find(second_end)==_map_depth_end.end()){
                    _map_depth_end[second_end]=_map_depth_end[first_end]+1;
                   if(second_end.compare(beginWord)==0){
                        return_map_depth_end[second_end];
                    }
                   if(_map_depth_begin.find(second_end)!=_map_depth_begin.end()){
                        return_map_depth_begin[second_end]+_map_depth_end[second_end]-1;
                    }
                    q_end.push(second_end);
                }
           }
       }
    }
   return 0;
}

5.   例三:Word Ladder II

Given two words (start and end), and adictionary, find all shortest transformation sequence(s) from start to end,such that:

Only one letter can be changed at a time

Each intermediate word must exist in thedictionary

For example,

Given:

start = "hit"

end = "cog"

dict =["hot","dot","dog","lot","log"]

Return

[

["hit","hot","dot","dog","cog"],

["hit","hot","lot","log","cog"]

]

Note:

All words have the same length.

All words contain only lowercase alphabeticcharacters.

分析:

由于时间复杂度和空间复杂度要求非常高,这道题成为Leetcode上通过率最低的题。

一个比较容易想的思路是:双向BFS先算最短路径n,再做深度为n的DFS。。。时间复杂度太高,没有通过。最终看到一篇博客

http://yucoding.blogspot.com/2014/01/leetcode-question-word-ladder-ii.html

,很受启发,它的基本思想是:

1.      分层记录节点,用两个队列(或一个队列一个set)

2.      记录每个节点的所有前驱节点

代码

vector<vector<string>> myresult;
unordered_map<string,vector<string>> _map;
vector<string> neighbors(string s, unordered_set<string> &dict){
    vector<string> result;
    for(int i=0;i<s.size();i++){
        char temp=s[i];
        for(char c='a';c<='z';c++){
            if(c!=temp){
                s[i]=c;
                if(dict.find(s)!=dict.end()){
                    result.push_back(s);
                }
            }
        }
        s[i]=temp;
    }
    for(int i=0;i<result.size();i++){
        _map[result[i]].push_back(s);
    }
    return result;
}
void dfs(string start, string end,vector<string>& temp){
    if(end.compare(start)==0){
        myresult.push_back(temp);
    }
    else{
        for(int i=0;i<_map[end].size();i++){
            temp.push_back(_map[end][i]);
            dfs(start,_map[end][i],temp);
            temp.pop_back();
        }
    }
}
void output(string start, string end, vector<string>& last, unordered_set<string> &dict){
    for(int i=0;i<last.size();i++){
        vector<string> temp;
        temp.push_back(end);
        temp.push_back(last[i]);
        dfs(start,last[i],temp);
    }
    for(int i=0;i<myresult.size();i++){
        reverse(myresult[i].begin(),myresult[i].end());
    }
}
vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict){
    bool flag=false;
    vector<string> last;
    dict.insert(start);
    dict.insert(end);
    queue<string> first;
    unordered_set<string> second;
    first.push(start);
    dict.erase(start);
    while(!first.empty()){
    while(!first.empty()){
        string _front=first.front();
        first.pop();
        vector<string> _neighbors=neighbors(_front,dict);
        for(int i=0;i<_neighbors.size();i++){
            second.insert(_neighbors[i]);
            if(_neighbors[i].compare(end)==0){
                flag=true;
                last.push_back(_front);
            }
        }
    }
    if(flag==false){
        for (auto it=second.begin(); it != second.end(); ++it){
            first.push(*it);
            dict.erase(*it);
        }
        second.clear();
    }
    else{
       output(start,end,last,dict);
    }
    }
    return myresult;
}
时间: 2024-10-10 12:59:50

理解广度优先搜索的相关文章

【算法导论】--C++实现广度优先搜索bfs

一.题目 根据上次随机生成的100个顶点的无向图和有向图,对其进行广度优先搜索. 二.理解广度优先搜索 广度优先搜索可以将其想象成水滴落入水面溅起了的一圈一圈的涟漪,是由一个起始点开始一圈一圈进行扩散搜索的. [课上老师是这样说的,大家想象一下,发现其实非常形象] 广度优先搜索总是从一个起始点出发,首先扩散这个点周围所有的邻居,然后邻居在去扩散邻居的邻居(*^-^*)...然后一直到最后将整张图都扩散完. 三.代码实现 对于第一次随机生成100个顶点的图进行了细节的修改,将每个顶点的类型改为了自

广度优先搜索(BFS)

广度优先 Description: 阿狸被困在迷宫,snoopy要去救他,snoopy可以向上.下.左.右四个方向行走,每走一步(格)就要喝掉一瓶益力多.现在给它一个迷宫地图请问:snoopy最少需要多少瓶益力多才能走出迷宫? Input: 先输入一个数t,表示测试的数据个数, 下面输入的就是t个迷宫, 每个迷宫的输入都应包含以下数据, 输入迷宫的大小 n(n<=15),表示迷宫大小为n*n. 再输入迷宫, 用大写字母“S”表示snoopy的位置, 用小写字母“E”表示阿狸被困的位置, 用“.”

图的搜索算法之广度优先搜索

图的邻接表表示 对图(有向或无向)G=<V,E>(为方便记,假定V=1,2,-,n),其邻接表表示是一个由|V|个链表组成数组.对每一个u∈V,链表Adj[u]称为相应顶点u的邻接表.它包括G中全部与u相邻的顶点.每一个邻接表中顶点一般是按随意顺序存放的. 无向图的邻接表表示 有向图的邻接表表示 广度优先搜索(Broad First Search) 1.问题的理解与描写叙述 给定一个图(有向或无向)G=<V,E>和当中的一个源顶点s.广度优先搜索系统地探索G的边以"发现&

“生动”讲解——深度优先搜索与广度优先搜索

深度优先搜索(Depth First Search,DFS) 主要思想:不撞南墙不回头 深度优先遍历的主要思想就是:首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点:当没有未访问过的顶点时,则回到上一个顶点,继续试探访问别的顶点,直到所有的顶点都被访问. 沿着某条路径遍历直到末端,然后回溯,再沿着另一条进行同样的遍历,直到所有的顶点都被访问过为止. 图解: 分析: 通过上面的图例可以非常直观的了解深度优先搜索的工作方式.下面来分析一下如何用代码来实现它. 大家都知道,深度

南阳理工--21--三个水杯~~广度优先搜索

这一题运用广度优先搜索可以解决,主要是各个状态的转移以及某个状态出现过要标记,避免重复,进入死循环. 下面是AC代码,上面有详细的讲解: # include <iostream> # include <cstring> # include <queue> using namespace std; class data //队列的结点, { public: int water[3]; //三个水杯的状态 int step; //步骤 }; bool visited[100

迷宫问题(maze problem)——深度优先(DFS)与广度优先搜索(BFS)求解

1.问题简介 给定一个迷宫,指明起点和终点,找出从起点出发到终点的有效可行路径,就是迷宫问题(maze problem). 迷宫可以以二维数组来存储表示.0表示通路,1表示障碍.注意这里规定移动可以从上.下.左.右四方方向移动.坐标以行和列表示,均从0开始,给定起点(0,0)和终点(4,4),迷宫表示如下: int maze[5][5]={ {0,0,0,0,0}, {0,1,0,1,0}, {0,1,1,0,0}, {0,1,1,0,1}, {0,0,0,0,0} }; 那么下面的迷宫就有两条

广度优先搜索(bfs)

学了将近半年的信息了,昨天猛地间发现我好像不会搜索.... 这就意味着我在noip的时候连暴力都不会打...为了避免这种事情的发生,我决定一定要好好学搜索.. 好了,废话不多说了,下面开始我们的正式话题:广度优先搜索 1.前言 广度优先搜索其实是一种用来遍历连通图的一种算法,它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域,故得名.                       貌似有的东西就真的跟徐大佬说的一样:说不清楚,只能靠自己去做题才能真正理解. 所以,如果我说的你不是很明白

广度优先搜索求树的深度

#include<iostream> #include<vector> #include<stack> #include<string> #include<queue> #include<algorithm> #include<numeric> using namespace std; class node{ public: int val; node* left; node* right; node():val(0),l

迷宫问题的求解(广度优先搜索)

     迷宫问题很容易可以理解为广度优先搜索问题,站在一个点上,首先试一试自己周围的点是否可以走,如果是路则加入待走队列,如果是墙则丢弃.迷宫问题在广度优先搜索的时候需要特别注意的就是要及时抛弃,遇到走过的点立即丢弃,遇到墙立即丢弃,不然时间复杂度就很高.    题目描述 Ignatius被魔王抓走了,有一天魔王出差去了,这可是Ignatius逃亡的好机会. 魔王住在一个城堡里,城堡是一个A*B*C的立方体,可以被表示成A个B*C的矩阵,刚开始Ignatius被关在(0,0,0)的位置,离开城