广度优先搜索(BFS)进阶之双向广度优先搜索

        在上一篇文章中树以及树的遍历:宽度优先搜索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,如果时间复杂度实在太大,再考虑剪枝,最后再考虑双向广搜来改良时间复杂度。

        本宝宝打字不易,求家人们三连,谢谢,嘻嘻嘻。

(侵删)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值