启发式BFS算法讲解及例题

引入

考虑这样一个问题,一张地图,有一个起点和终点,有障碍物,可以向上下左右走,求起点到终点的最短路。
这是普通的BFS遍历到的范围:
在这里插入图片描述
明显就多遍历了很多没用的点。

启发式搜索

而这样就显得不太聪明。
BFS是从一个点向外扩张,四面的扩张是毫无目的的,我们可以在搜索的过程中,给定要找的目标。由于最后的最短路长度只有一个值,所以不是最短路径上的点再去搜索他就是在浪费时间,可以用贪心的思想想一下,距离终点更近的点就先搜索
这样有优先级的数据结构,不难想到——优先队列。
这样进行搜索的范围大致如下:
在这里插入图片描述

估计函数

我们需要每个点一个值,表示这个点找到终点的代价,计算代价的这个函数称为估计函数
估价函数 f ( x ) = g ( x ) + h ( x ) f(x)=g(x)+h(x) f(x)=g(x)+h(x), g ( x ) g(x) g(x)表示从起点(初始状态)到当前点的花费, h ( x ) h(x) h(x)表示当前点到终点(目标状态)的花费。

无向图的h函数

无向图对于大多数题,一般使用曼哈顿距离估价。
即: h ( x ) = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ h(x)=|x_1-x_2|+|y_1-y_2| h(x)=x1x2+y1y2

有向图的h函数

有向图的 h h h函数为反图的最短路到当前点的值。

至于函数 g ( x ) g(x) g(x),就是从起点到现在这个点所用的代价( d i s dis dis)。
因此,我们可以用以下的格式存储每个点。

struct node{
	int x;//任意类型,表示当前状态
	int dis;//从起点到当前点的代价
	friend bool operator<(node a,node b){
		return a.dis+h(a.x)>b.dis+h(b.x);//优先队列的比较,计算后代价较小的先遍历
	}
};

注意

估价函数计算出的代价值一定不能大于现实的值,宁愿估小,也不能估大。
比如一个点的现实代价本来非常小,就是最优解,但是“一不小心”估大了,许多比它代价更大的点都在他之前遍历了,有没有起到优化效果了。

例题

八数码难题
首先提一句:暴搜可过。
先看 g g g函数,问的是次数,那么每走一步次数加一,那么 g ( x ) g(x) g(x)的值就是从给出的初始状态到当前状态所需的步数。
接着是 h h h函数,其实不用太过于严谨,可以将当前棋盘与目标棋盘不同的数量作为 h ( x ) h(x) h(x)
接着用优先队列广搜就行了。

#include<bits/stdc++.h>
#define int long long
using namespace std;
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
string t="123804765";
string s;
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
int diff(string s){
	int res=0;
	for(int i=0;i<s.length();i++)res+=(s[i]!=t[i]);
	return res;
}
struct node{
	string s;
	int dis;
	bool friend operator<(node a,node b){
		return diff(a.s)+a.dis>diff(b.s)+b.dis;
	}
};
unordered_map<string,bool>vis;
unordered_map<string,int>dis;
void bfs(){
	priority_queue<node>q;
	q.push(node{s,0});
	vis[s]=1;
	dis[s]=0;
	while(!q.empty()){
		node x=q.top();
		q.pop();
		if(x.s==t){
			print(x.dis);
			return;
		}
		char c[3][3];
		int X,Y;
		//在字符串转化为字符数组的同时找空格的位置
		for(int i=0;i<3;i++){
			for(int j=0;j<3;j++){
				c[i][j]=x.s[i*3+j];
				if(c[i][j]=='0'){
					X=i,Y=j;
				}
			}
		}
		for(int i=0;i<4;i++){
			int xx=X+dx[i];
			int yy=Y+dy[i];
			if(xx<0||yy<0||xx>=3||yy>=3)continue;
			swap(c[xx][yy],c[X][Y]);
			string p="";
			for(int i=0;i<3;i++)for(int j=0;j<3;j++)p.push_back(c[i][j]);//转化为字符串
			if(!vis[p]){
				vis[p]=1;
				dis[p]=x.dis+1;
				q.push(node{p,dis[p]});
			}
			else if(dis[p]>x.dis+1)dis[p]=x.dis+1,q.push(node{p,dis[p]});//在访问过的情况下查看一下是否为最优情况
			swap(c[xx][yy],c[X][Y]);
		}
	}
}
signed main(){
	cin>>s;
	bfs();
}
posted on 2025-05-15 16:55  fish2012  阅读(6)  评论(0)    收藏  举报  来源