BFS&DFS进阶

目录

1.启发式算法:     

2.双向广搜(DBFS)解决八数码问题: 


1.启发式算法:     

如果在选择节点时能充分利用与问题有关的特征信息,估计出节点的重要性,就能在搜索时选择重要性较高的节点,以利于求得最优解。这个过程称为启发式搜索。

与被解问题的某些特征值有关的控制信息(如解的出现规律、解的结构特征等)称为搜索的启发信息。它反映在评估函数中,评估函数的作用是评估待扩展各节点在问题求解中的价值,即评估节点的重要性。

评估函数:f(x)= g(x)+ h(x)

g(x)是从初始节点到一个节点x的实际代价。

h(x)是这个节点x到目标节点的最优路径的估计代价,体现了问题的启发式信息,h(x)称为启发式函数。

一般操作步骤:

1 读取初始状态和目标状态

2 将初始节点压入open表中

3 取出open表中估计值最小的节点,放入close表中

4 判断该节点是否为目标节点,不是的话,拓展该节点,将子节点放入open表中,返回上一步

5 将该节点压入栈中,并将指针指向父节点

6 如果父节点不为空,继续5

7 如果栈不为空,出栈并输出该节点

如下是两个启发式搜索的链接,不懂的可以再研究一下:

https://www.bilibili.com/video/BV1gt4y1Q7dt?from=search&seid=10570594341702602487 https://www.bilibili.com/video/BV1QK4y1j7bi?from=search&seid=18017027603264843331 

另外启发式搜索也可以解决如下的八数码问题,如图所示:


2.双向广搜(DBFS)解决八数码问题: 

首先,我们来了解一下什么是八数码

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。(题目链接://https://vijos.org/p/1360
【分析】

题目读完第一感觉是和求解最短路径问题类似,考虑使用BFS,状态很好找,每次移动空格就会形成一种新的状态,例如:

其次,那什么是双向广搜呢?

双向广度优先,两个队列,一个从起点开始扩展状态,另一个从终点开始扩展状态;如果两者相遇,则表示找到了一条通路,而且是最短的通路。双向广度优先可以大大提高效果,而且可以减少很多不必要的状态扩展。盗个图来说明一下,其中的阴影部分为减少的扩展状态

如下是本题目的代码(用的是全排列+dbfs),可供参考:

#include<cstdio>
#include<cstring>
#include<ctime>
char ans[11],start[10];
bool isUsed[11];
int changeId[9][4]={{-1,-1,3,1},{-1,0,4,2},{-1,1,5,-1},
					{0,-1,6,4},{1,3,7,5},{2,4,8,-1},
					{3,-1,-1,7},{4,6,-1,8},{5,7,-1,-1}
					};//0出现在0->8的位置后该和哪些位置交换 
const int M=400000;//9!=362800,因此数组开40W足够了 
int num[M],len=0,des=123804765;//num存储所有排列,len表示排列的个数也就是9!,des为目的状态直接用整数表示便于比较 
int isV[M][2];//bfs时判断状态是否出现过;isV的下标和num的下标一一对应,表示某种排列是否出现过
//通过isV和num建立起某种排列的组合成的整数int和bool的关系,其实STL中有map实现了key-->value,用排列作为key,value用bool即可 
int que1[M/2][3],que2[M/2][3];//0-->排列,1-->排列中0的位置,2-->步数 
void swap(char *c,int a,int b){//交换字符串中的两个位置 
	char t=c[a];
	c[a]=c[b];
	c[b]=t;
}
void paiLie(int n,int k){//深搜产生0-8的全排列 
	for(int i=0;i<n;i++){
		if(!isUsed[i]){
			ans[k]=i+'0';
			isUsed[i]=1;
			if(k==n){//已经有n个转换存储 
				ans[k+1]='\0';
				sscanf(ans+1,"%d",&num[len++]);
			}
			else
				paiLie(n,k+1);
			isUsed[i]=0;//回溯一步 
		}
	}
}
int halfFind(int l,int r,int n){//二分查找 
	int mid=l+(r-l)/2;
	if(num[mid]==n)return mid;
	else if(l<r&&num[mid]>n)return halfFind(l,mid-1,n);
	else if(l<r&&num[mid]<n) return halfFind(mid+1,r,n);
	return -1;
}
bool expand(int head,int &tail,int who,int q[][3]){
	char cur[10];//用于保存当前状态的字符串 
	int  pos=q[head][1],temp;//当前状态中0的位置
	sprintf(cur,"%09d",q[head][0]);//int-->char*这里的09d至关重要,否则算不出答案 
	for(int i=0;i<4;i++){//扩展当前的状态,上下左右四个方向 
		int swapTo=changeId[pos][i];//将要和那个位置交换 
		if(swapTo!=-1){//-1则不交换 
			swap(cur,pos,swapTo);//交换0的位置得到新状态 
			sscanf(cur,"%d",&temp);//新状态转换为int保存到temp 
			int k=halfFind(0,len,temp);//没有返回就查找当前排列的位置,将查出来的下标作为isV的下标 
			if(isV[k][0]==0){//如果 没有出现过,则将这个新状态进队 
				q[tail][0]=temp,q[tail][1]=swapTo,q[tail][2]=q[head][2]+1;
				isV[k][0]=who;
				isV[k][1]=tail;
				tail++;
			}
			else if(isV[k][0]&&isV[k][0]!=who){
				if(who==1)
					printf("%d", q[head][2]+que2[isV[k][1]][2]+1);
				else
					printf("%d", q[head][2]+que1[isV[k][1]][2]+1);
				return true;
			}
			swap(cur,pos,swapTo);//一个新状态处理完了一定要记得将交换的0交换回来 
		}
	}
	return false;
}
void bfs(int n,int p){
	int head1=0,tail1=1,head2=0,tail2=1;//head队头,tail队尾 
	que1[head1][0]=n,que1[head1][1]=p,que1[head1][2]=head1;//初始状态保存到对头,并设置当前步数为0 
	que2[head2][0]=des,que2[head2][1]=4,que2[head2][2]=head2;//初始状态保存到对头,并设置当前步数为0 
	int k=halfFind(0,len,n);
	isV[k][0]=1,isV[k][1]=0;
	k=halfFind(0,len,des);
	isV[k][0]=2,isV[k][1]=0;
	while(head1!=tail1||tail2!=head2){//队列不为空则继续搜索 
		if(tail2-head2>=tail1-head1){//2比1元素多就把1扩展 
			if(expand(head1,tail1,1,que1))return; 
			head1++;
		}
		else{
			if(expand(head2,tail2,2,que2))return; 
			head2++;
		}
	}
}
int main(){//812340756
	int n,i=-1,count=0;
	paiLie(9,1);//先将0-8的全排列按照升序产生出来存入num数组 
	scanf("%s",start);//输入初始状态 
	while(start[++i]!='0');//查找初始状态0的位置 
	sscanf(start,"%d",&n);//字符串转换为整数
	//int s=clock(); 
	if(n!=des)//判断输入状态是否就是目的状态 
		bfs(n,i); 
	else
		printf("%d",count);
	//printf("\n%.6lf",double(clock()-s)/CLOCKS_PER_SEC);
	return 0;
}

这边是两个相关问题的链接:   

 https://www.bilibili.com/video/BV185411A7nG?from=search&seid=11681381869968971120 https://www.bilibili.com/video/BV1ib4y1D7uc

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值