双向广搜

介绍:

广度优先搜索遵循从初始结点开始一层层扩展直到找到目标结点的搜索规则,它只能较好地解决状态不是太多的情况,承受力很有限。如果扩展结点较多,而目标结点又处在较深层,采用前文叙述的广度搜索解题,搜索量巨大是可想而知的,往往就会出现内存空间不够用的情况。

双向搜索A*算法对广度优先的搜索方式进行了改良或改造,加入了一定的“智能因素”,使搜索能尽快接近目标结点,减少了在空间和时间上的复杂度。

简单来说

双向广搜是用来解决题目的搜索范围太大而不适用于朴素广搜的情况,它通过同时从头和尾搜索,找到一个交汇点实现。

假如我们每拓展一次就会多6种状态,那么如果朴素广搜拓展10次就是6^10大约3KW种状态,但如果我们使用双向广搜,大约在6^5,这就很理想了。

1、搜索过程

有些问题按照广度优先搜索法则扩展结点的规则,既适合顺序,也适合逆序,于是我们考虑在寻找目标结点或路径的搜索过程中,初始结点向目标结点和目标结点向初始结点同时进行扩展,直至在两个扩展方向上出现同一个子结点,搜索结束,这就是双向搜索过程。出现的这个同一子结点,我们称为交汇点,如果确实存在一条从初始结点到目标结点的最佳路径,那么按双向搜索进行搜索必然会在某层出现“相交”,即有相交点,初始结点一相交点一目标结点所形成的一条路径即是所求路径。

假如我们的头搜索完了,而尾还没有搜索完,此时也没有出现交汇点,那么说明这个图不连通。

2、节点拓展顺序

 双向扩展结点,在两个方向的扩展顺序上,可以轮流交替进行,但由于大部分的解答树并不是棵完全树,在扩展完一层后,下一层则选择结点个数较少的那个方向先扩展,可以克服两个方向结点生成速度不平衡的状态,明显提高搜索效率。(即避免总是从头或尾搜索而退化成朴素广搜)

3、节点拓展方式

在朴素广搜中,我们是按节点拓展的,即每次我们选取一个节点,然后在这个节点的基础上进行拓展。但是在双向广搜中,我们是按层拓展的。下面举个例子说明:

再上图中,假如我们按点拓展,我们先从head拓展出的点h1里面拓展出点p,此时没有点与它交汇,接着拓展tail,此时刚好从tail拓展出的点t1中拓展出点p,此时p就是一个交汇点, 那么我们直接返回答案:dist[p]+dist[t1]+1=dist[h1]+dist[t1]+2。但与h1同层的h2可以直接拓展到与t1同层的t2,此时的最短路等于:dist[h2]+dist[t2]+1,比上面的要短1个单位。

4、数据结构

一般需要设置两个队列,一个从头开始搜索,一个从尾开始搜索


例题:190. 字串变换 - AcWing题库

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>//哈希表
#include <queue>

using namespace std;

int n;
string A, B;
string a[10], b[10];

int extend(queue<string> &q, unordered_map<string, int> &da, unordered_map<string, int> &db, 
    string a[], string b[]){
/*函数里面参数的意思是:
    当前进行拓展的队列是q
    da是我们当前拓展队列里面点的dist,而db是另一个方向拓展的队列里面的点
    a->b是我们的转移方案
*/
    int d = da[q.front()];//当前层的dist
    while(q.size() && da[q.front()] == d)//拓展一整层
    {
        string t = q.front();
        q.pop();
        
        for(int j = 0; j < n; j ++ )//遍历所有转移方案
            for(int i = 0; i < t.size(); i ++ )
            {
                if(t.substr(i, a[j].size()) == a[j])
                {
                    string state = t.substr(0, i) + b[j] + t.substr(i + a[j].size());
                    if(da.count(state)) continue;//如果已经访问过
                    if(db.count(state)) return da[t] + 1 + db[state];//如果是交汇点
                    //更新入队
                    da[state] = da[t] + 1;
                    q.push(state);
                }
            }
    }
    return 11;
}

int bfs()
{
    if(A == B) return 0;//特判
    queue<string> qa, qb;
    unordered_map<string, int> da, db;
    qa.push(A), qb.push(B);//分别将头和尾插入两个队列
    da[A] = 0, da[B] = 0;//dist=0
    
    int step = 0;
    while(qa.size() && qb.size()){//只有两个队列都不为空才继续拓展
    //如果一个队列为空还没有找到答案,说明这个图不连通,也就不存在答案
    
    //先拓展层数少的队列
        int t = 0;
        if(qa.size() <= qb.size())  t = extend(qa, da, db, a, b);
        else    t = extend(qb, db, da, b, a);//逆向搜索要倒着转移b->a
        // cout << "t: " << t << endl;
        if(t <= 10) return t;//答案要求次数不大于10
        if(++ step == 10)    return -1;//如果拓展了10次还没有答案,那么转移次数肯定大于10,直接结束
    }
    
    return -1;//不连通
}

int main()
{
    cin >> A >> B;
    while(cin >> a[n] >> b[n])  n ++ ;
        
    int t = bfs();
    if(t == -1) puts("NO ANSWER!");
    else    cout << t << endl;
    
    return 0;
}

posted @ 2022-05-05 08:41  光風霽月  阅读(47)  评论(0编辑  收藏  举报