在上一篇文章中树以及树的遍历:宽度优先搜索BFS c++的代码实现-CSDN博客,我们提到了广度优先搜索,即宽度优先搜索(BFS),具体的BFS的实现以及原理,大家可以看回上一篇文章。在本篇我们将会讲双向广搜。
适用场景:有确定的起点以及终点,并且能将从起点到终点的单向搜索,转换成分别从起点和终点出发的相遇问题,那么在这种情况下,如果单向的BFS会超时,那么可以优先考虑双向广搜,以此减少时间复杂度。
在上篇文章中我们也讲到BFS的实现,那么接下来我们对比一下BFS和双向广搜的代码,以及所搜索的节点的个数来比较。
在这由于需要,我们要将树的定义更改一下,具体如下:
typedef struct mytree{
int data;
bool flag=false;
struct mytree *father,*lson,*rson;
}mt;
或许有读者会问,为什么要加一个布尔的flag和*father。其实很简单,flag用来判断这个节点是否被搜索过,方便判断有没有相遇;而*father就是该节点的父节点。
至于树的初始化在这就省略了,具体的可以看上篇文章。
接下来就是双向广搜的实现过程了,代码如下:
void two_bfsprint(mt *root,mt *t){
queue<mt*> q1;
queue<mt*> q2;
q1.push(root);
q2.push(t);
mt *now1,*now2;
now1=now2=new mytree;
while(!q1.empty()&&!q2.empty()){
int n=q1.size();
now2=q2.front();
q2.pop();
if(now2->flag){
cout<<now2->data;
return;
}else cout<<now2->data<<" ";
if(now2->father!=NULL){
q2.push(now2->father);
now2->father->flag=true;
}
while(n--){
now1=q1.front();
q1.pop();
if(now1->flag){
cout<<now1->data;
return;
}else cout<<now1->data<<" ";
if(now1->lson!=NULL){
q1.push(now1->lson);
now1->lson->flag=true;
}
if(now1->rson!=NULL){
q1.push(now1->rson);
now1->rson->flag=true;
}
}
cout<<endl;
}
}
root就是要搜索的起点,t就是要搜索的终点。
我们分别用q1和q2来将root和t入队。在双向广搜中,我们要将循环判断的条件改为:!q1.empty()&&!q2.empty(),为什么呢?因为如果有一个队列空了,那么说明另一个队列一定和它没有共同的子状态,那么另一个队列继续搜索下去也就失去了意义,那就直接中断搜索,减少时间复杂度。
在循环中我们用n存储树中某一层的所有节点的个数。比如下图中,在第三次循环时,我们分别将B,E,I这三个节点的子节点,也就是A,C,H,J,分别入队。也就是说,在第i次循环时,我们将该树的第i层的所有节点的所有子节点入队。
总体来说,上述代码中,所用到的双向广搜是一层一层的。q1从上至下,而q2则是反过来,从下往上。
(此图来源于网络素材)
我们来分析一下普通BFS的遍历和双向广搜。
如果我们用普通BFS那么从F遍历到H的顺序为:F->D->G->B->E->I->A->C->H。
反观如果用双向广搜的话q1遍历顺序为:F->D->G。q2遍历顺序为:H->I->G。在这当中,或许看起来时间复杂度并没有改善多少,但如果当需要遍历的层数很深或者树为树叉树(比如:4,5)时,那么,时间复杂度就可以大大改善。
接下来我们将普通BFS代码和双向广搜的代码:
typedef struct mytree1{
int data;
struct mytree1 *lson,*rson;
}mt1;
typedef struct mytree2{
int data;
bool flag=false;
struct mytree2 *father,*lson,*rson;
}mt2;
void maketree1(mt1 *&root){
string judge;
root=new mt1;
cout<<"请输入节点的数据:"<<endl;
cin>>root->data;
cout<<"是否添加数据为"<<root->data<<"节点的左孩子?(Y/N)"<<endl;
cin>>judge;
if(judge=="Y")
maketree1(root->lson);
else root->lson=NULL;
cout<<"是否添加数据为"<<root->data<<"节点的右孩子?(Y/N)"<<endl;
cin>>judge;
if(judge=="Y")
maketree1(root->rson);
else root->rson=NULL;
}
void maketree2(mt2 *&root){
string judge;
root=new mt2;
cout<<"请输入节点的数据:"<<endl;
cin>>root->data;
cout<<"是否添加数据为"<<root->data<<"节点的左孩子?(Y/N)"<<endl;
cin>>judge;
if(judge=="Y"){
maketree2(root->lson);
root->lson->father=root;
}
else root->lson=NULL;
cout<<"是否添加数据为"<<root->data<<"节点的右孩子?(Y/N)"<<endl;
cin>>judge;
if(judge=="Y"){
maketree2(root->rson);
root->rson->father=root;
}
else root->rson=NULL;
}
void one_bfsprint(mt1 *root,mt1 *t){
queue<mt1*> q;
q.push(root);
while(!q.empty()){
mt1 *now=new mytree1;
now=q.front();
q.pop();
if(now==t){
cout<<now->data;
return;
}else cout<<now->data<<" ";
if(now->lson!=NULL)q.push(now->lson);
if(now->rson!=NULL)q.push(now->rson);
}
}
void two_bfsprint(mt2 *root,mt2 *t){
queue<mt2*> q1;
queue<mt2*> q2;
q1.push(root);
q2.push(t);
mt2 *now1,*now2;
now1=now2=new mytree2;
while(!q1.empty()&&!q2.empty()){
int n=q1.size();
now2=q2.front();
q2.pop();
if(now2->flag){
cout<<now2->data;
return;
}else cout<<now2->data<<" ";
if(now2->father!=NULL){
q2.push(now2->father);
now2->father->flag=true;
}
while(n--){
now1=q1.front();
q1.pop();
if(now1->flag){
cout<<now1->data;
return;
}else cout<<now1->data<<" ";
if(now1->lson!=NULL){
q1.push(now1->lson);
now1->lson->flag=true;
}
if(now1->rson!=NULL){
q1.push(now1->rson);
now1->rson->flag=true;
}
}
cout<<endl;
}
}
其中mytree1是普通的BFS代码;mytree2是双向广搜的代码。maketree1是普通DFS的初始化;maketree2是双向广搜的初始化。one_bfsprint是普通BFS的路径打印;two_bfsprint则是双向广搜的路径打印。
读者可以对比发现,双向广搜的编码比普通BFS复杂。所以在竞赛中,如果可以用普通BFS通过就尽量使用普通BFS,如果时间复杂度实在太大,再考虑剪枝,最后再考虑双向广搜来改良时间复杂度。
本宝宝打字不易,求家人们三连,谢谢,嘻嘻嘻。
(侵删)