神奇搜索算法A*

A*

A*是一种启发式搜索算法,又叫最佳图搜索算法。

何谓启发式搜索?

众所周知,计算机在执行搜索算法时是没开上帝视角的。因此,在搜索时,往往显得盲目,把所有可能的状态全部遍历,这种搜索我们统称盲目搜索。

启发式搜索,顾名思义,其实就是给了盲目的计算机一点启发,一点智慧,利用这些来引导计算机减少搜索范围,大幅降低搜索复杂度。试想在BFS的过程中,在某一阶段某些状态成为正解的概率明显比别的状态要大,那我们就优先选择这些状态继续BFS,达到降低复杂度的目的。那么怎么计算这个概率呢?

我们需要给计算机一些启发信息。不同的启发信息有不同的强度,既不能太强也不能弱,太强会导致得不到正解,而太弱又会导致优化效果不明显。都说了这么多了,那就来点具体的。

如何进行启发式搜索?

在A*之前,首先要提一下A算法,A*是A的一种特殊情形。

这里我们要引入一个函数——“估价函数”。

定义一个函数(f(n)=g(n)+h(n)),利用启发信息计算出(h),根据(f)函数的值找出当前搜索状态下的最有希望的节点进行扩展。其中(n)是一个状态,(f)就是估价函数,(g)(n)已经用掉的开销(可以理解做裸的BFS用到的信息),(h)是一个启发函数。

启发函数在不同的情况下是有不同的计算方法的,这要因情况而异。

为了更好定义A*算法,我们还要引入一些函数:(f^ *(n),g^ *(n),h^ *(n))

首先我们要明确,上面提到的估价函数,是对待扩展节点的一个“估计”,是从启发信息计算而来的,因此从初始状态到正解对应的状态的花费不是实际花费。

(f^ *(n)=g^ *(n)+h^ *(n))表示的是从搜索的起点经过(n)状态到达搜索目标的实际最小花费,因此,你可以把(f(n),g(n),h(n))看作这些带*的函数的估计值。

A*算法定义为保证(h(n)<h^ *(n))的A算法,其保证能得到最优解。

我们通过例题入手

P1379 八数码难题

题目描述

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


显然这题可以用双向BFS做(起点状态和中点状态模式相同),而且实际上裸的BFS这题也能过。。。

不过既然是在讲A*,就讨论A*怎么解(哇,发现题解有一篇我校巨佬的IDA*QWQ)。

为了使得(h)函数一定比(h^ *(n))小,我们考虑构造一个这样的函数。在起始状态,(h ^*(n))显然就是所有位置的数移动到目标状态的位置的所需步数。我们可以假定(h(n))(n)状态时不在目标位置的数的个数。因为显然,这些不再目标位置的数至少要通过一次移动才能到达目标位置。这是一个可行方案。

贴代码:

用了一个map来去除重复状态,其实matrix结构体里面随便咋重载都行,反正装的进去map就行。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
using namespace std;
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
struct matrix{
	int a[5][5];
	bool operator<(matrix x) const{
		for(int i=1;i<=3;++i)
		 	for(int j=1;j<=3;++j)
		 		if(x.a[i][j]!=a[i][j]) return x.a[i][j]<a[i][j];
		return 0;
	}
}st,f;
inline int h(matrix a)
{
	int tmp=0;
	for(int i=1;i<=3;++i)
	 	for(int j=1;j<=3;++j)
	 		if(a.a[i][j]!=st.a[i][j]) tmp++;
	return tmp;
}
struct node{
	matrix a;
	int step;
	bool operator<(node x) const{
		return x.step+h(x.a)<step+h(a);
	}
	node(){};
	node(matrix _a,int _step){a=_a,step=_step;}
};
priority_queue<node> q;
map<matrix,bool> mp;
int main()
{
	st.a[1][1]=1,st.a[1][2]=2,st.a[1][3]=3;
	st.a[2][1]=8,st.a[2][2]=0,st.a[2][3]=4;
	st.a[3][1]=7,st.a[3][2]=6,st.a[3][3]=5;
	char c;
	for(int i=1;i<=3;++i)
		for(int j=1;j<=3;++j){
			scanf("%c",&c);
			f.a[i][j]=c-'0';
		}
	q.push(node(f,0));
	while(q.size()){
		node x=q.top();q.pop();
		if(!h(x.a)){
			printf("%d
",x.step);
			return 0;
		}
		if(mp[x.a]) continue;
		mp[x.a]=1;
		int nx,ny;
		for(int i=1;i<=3;++i)
			for(int j=1;j<=3;++j)
				if(!x.a.a[i][j]) nx=i,ny=j;
		for(int i=0;i<4;++i){
			int xx=nx+dir[i][0],yy=ny+dir[i][1];
			if(xx>=1&&xx<=3&&yy>=1&&yy<=3){
				swap(x.a.a[xx][yy],x.a.a[nx][ny]);
				q.push(node(x.a,x.step+1));
				swap(x.a.a[xx][yy],x.a.a[nx][ny]);
			}
		}
	}
	return 0;
}

如果要加ID的话会更快,这里不详细讲,具体做法跟一般IDBFS是一样的。


P2324 [SCOI2005]骑士精神

跟上面这道题完全一样,没区别,稍微改一下代码就好了。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define ll long long
using namespace std;
int dir[8][2]={{1,2},{-1,2},{2,1},{2,-1},{-2,1},{-2,-1},{1,-2},{-1,-2}};
struct matrix{
	int a[6][6];
	bool operator<(matrix x) const{
		for(int i=1;i<=5;++i)
		 	for(int j=1;j<=5;++j)
		 		if(x.a[i][j]!=a[i][j]) return x.a[i][j]<a[i][j];
		return 0;
	}
}st,f;
inline int h(matrix a)
{
	int tmp=0;
	for(int i=1;i<=5;++i)
	 	for(int j=1;j<=5;++j)
	 		if(a.a[i][j]!=st.a[i][j]) tmp++;
	return tmp;
}
struct node{
	matrix a;
	int step;
	bool operator<(node x) const{
		return x.step+h(x.a)<step+h(a);
	}
	node(){};
	node(matrix _a,int _step){a=_a,step=_step;}
};
priority_queue<node> q;
map<matrix,bool> mp;
int main()
{
	st.a[1][1]=1,st.a[1][2]=1,st.a[1][3]=1,st.a[1][4]=1,st.a[1][5]=1;
	st.a[2][1]=0,st.a[2][2]=1,st.a[2][3]=1,st.a[2][4]=1,st.a[2][5]=1;
	st.a[3][1]=0,st.a[3][2]=0,st.a[3][3]=-1,st.a[3][4]=1,st.a[3][5]=1;
	st.a[4][1]=0,st.a[4][2]=0,st.a[4][3]=0,st.a[4][4]=0,st.a[4][5]=1;
	st.a[5][1]=0,st.a[5][2]=0,st.a[5][3]=0,st.a[5][4]=0,st.a[5][5]=0;
	int T;
	scanf("%d",&T);
	while(T--){
		mp.clear();
		while(q.size()) q.pop();
		char c;
		for(int i=1;i<=5;++i)
			for(int j=1;j<=5;++j){
				scanf(" %c",&c);
				if(c=='*') f.a[i][j]=-1;
				else f.a[i][j]=c-'0';
			}
		bool flag=0;
		q.push(node(f,0));
		while(q.size()){
			node x=q.top();q.pop();
			if(flag) continue;
			if(x.step>15){
				printf("-1
");
				flag=1;
			}
			if(!h(x.a)&&!flag){
				printf("%d
",x.step);
				flag=1;
			}
			if(mp[x.a]) continue;
			mp[x.a]=1;
			int nx,ny;
			for(int i=1;i<=5;++i)
				for(int j=1;j<=5;++j)
					if(x.a.a[i][j]==-1) nx=i,ny=j;
			for(int i=0;i<8;++i){
				int xx=nx+dir[i][0],yy=ny+dir[i][1];
				if(xx>=1&&xx<=5&&yy>=1&&yy<=5){
					swap(x.a.a[xx][yy],x.a.a[nx][ny]);
					q.push(node(x.a,x.step+1));
					swap(x.a.a[xx][yy],x.a.a[nx][ny]);
				}
			}
		}
	}
	return 0;
}

就是跑的比较慢啦。

原文地址:https://www.cnblogs.com/DarkValkyrie/p/11298282.html