八数码难题( A*算法详解,附题目链接 )

经典八数码问题,给一个3*3的方阵,上面写着0~8的数字,

求把方阵变换成另一个方阵所需最小步数

题目链接: 八数码难题 洛谷P1379

考虑暴力搜索,一共有9的9次方种状态,超时

A*算法:相比于盲目搜索,每次朝着离目标状态最接近的方向搜索

定义估价函数 f(n)= h(n)+ g(n)

我们要搜索最小步数,因此我们希望 f(n)最小,h(n)是启发函数,是人为构造的,表示当前状态距离目标状态预期需要的最小步数,而 g(n)表示从初始状态到目前走了多少步数,h(n)经过思考后,在本题中可以定义为每个不同数码在当前状态距离目标状态曼哈顿距离的和。

在具体实现A*时,我们按照一般bfs的思路,把起始状态入队,循环取出队列中f(n)最小的状态(这里可以用优先队列优化),然后把该状态可能的后续状态都入队,重复上述步骤,直到取出的状态为最终状态。

不会bfs的同学戳这里:

bfs与dfs详解

不会优先队列(堆)的同学戳这里:

优先队列详解

具体实现如下(加了可视化操作,比原题略有改动)

#include<iostream>
#include<queue>
#include<utility>
#include<map>
#include<cmath>
#include<windows.h> 
#define mp(x,y) make_pair(x,y)

using namespace std;

//输入两个长度为9的数字串,分别代表初始状态和目标状态

typedef struct node{//定义节点为目前棋盘状态
	pair<int,int> center;
	int g[4][4];
}node;

bool operator == (node x,node y){//用于比较节点是否相同
	for(int i=1;i<=3;i++)
	{
		for(int j=1;j<=3;j++)
		{
			if(x.g[i][j]!=y.g[i][j])return 0;
		}
	}
	return 1;
}

bool operator < (node x,node y){//用于查找节点
	for(int i=1;i<=3;i++)
	{
		for(int j=1;j<=3;j++)
		{
			if(x.g[i][j]<y.g[i][j])return 1;
			else if(x.g[i][j]>y.g[i][j])return 0;
		}
	}
	return 0;
}

void init_node(node &x){//初始化节点
	for(int i=0;i<=3;i++)
	{
		for(int j=0;j<=3;j++)
		{
			x.g[i][j]=0;
		}
	}
}

char o1[15],o2[15];//输入初始状态与目标状态

node s,e;

priority_queue< pair<int,node> > q;

map<node,int> g; //用于记录状态是否出现过,避免重复搜索

map<node,node> pre; //记录前驱节点

int dx[4]={-1,0,1,0};//搜索的四个方向
int dy[4]={0,1,0,-1};

int cal_h(node x)//计算启发函数(两个状态各个数位曼哈顿距离的和)
{
	int sum=0;
	pair<int,int> h[9];//预处理优化
	for(int i=1;i<=3;i++)
	{
		for(int j=1;j<=3;j++)
		{
			h[x.g[i][j]]=mp(i,j);
		}
	}
	for(int i=1;i<=3;i++)
	{
		for(int j=1;j<=3;j++)
		{
			sum+=abs(i-h[e.g[i][j]].first)+abs(j-h[e.g[i][j]].second);
		}
	}
	return sum;
}

void print_mp(node x)//输出当前状态
{
	system("cls");
	for(int i=1;i<=4;i++)cout<<"\n";
	for(int i=1;i<=3;i++)
	{
		for(int j=1;j<=6;j++)cout<<"\t";
		for(int j=1;j<=3;j++)
		{
			cout<<x.g[i][j]<<"  ";
		}
		cout<<"\n\n";
	}
	Sleep(1000);
}

void print(node x)//递归输出
{
	if(x==s)print_mp(x);
	else{
		print(pre[x]);
		print_mp(x);
	}
}

int main()
{
	cin>>o1+1;
	cin>>o2+1;
	
	init_node(s);//将输入转化为节点状态
	for(int i=1;i<=9;i++)
	{
		s.g[(i+2)/3][(i-1)%3+1]=o1[i]-'0';
		if(s.g[(i+2)/3][(i-1)%3+1]==0)s.center=mp((i+2)/3,(i-1)%3+1);
	}
	
	init_node(e);
	for(int i=1;i<=9;i++)
	{
		e.g[(i+2)/3][(i-1)%3+1]=o2[i]-'0';
		if(e.g[(i+2)/3][(i-1)%3+1]==0)e.center=mp((i+2)/3,(i-1)%3+1);
	}
	
    //广搜,优先队列优化
	g[s]=0;
	
	q.push(mp(-(cal_h(s)+g[s]),s));
	
	while(!q.empty())
	{ 
		node tmp=q.top().second;
		q.pop();
		if(tmp==e)break;
		if(g[tmp]==25)//如果搜索的次数过多,结束搜索
		{
			cout<<"-1"<<endl;
			return 0;
		}
		int x=tmp.center.first;
		int y=tmp.center.second;
		for(int i=0;i<=3;i++)
		{
			int xx=x+dx[i];
			int yy=y+dy[i];
			if(xx>=1&&xx<=3&&yy>=1&&yy<=3)
			{
				node u=tmp;
				swap(u.g[x][y],u.g[xx][yy]);
				if(g.find(u)==g.end())//如果没搜索过该状态,将其入队
				{
					u.center=mp(xx,yy);
					g[u]=g[tmp]+1;
					pre[u]=tmp;
					q.push(mp(-(cal_h(u)+g[u]),u));
				}
			}
		}
	}
	
	print(e);
	
	return 0;
}



A*练习题:

骑士精神 洛谷P2324  A*经典题目

k短路 洛谷P4467  比较有难度的题目,需要结合其他算法

                                                                                                                                  转载请注明出处

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是用A*算法实现八数码难题的伪代码: ``` function Astar_Search(initial_state, goal_state): open_list = [initial_state] // 开放列表,初始状态加入其中 closed_list = [] // 封闭列表,不包含初始状态 g_score = {} // g值,表示从初始状态到当前状态的代价 g_score[initial_state] = 0 // 初始状态的代价为0 f_score = {} // f值,表示从初始状态到目标状态的估计代价 f_score[initial_state] = heuristic(initial_state, goal_state) // 初始状态的估计代价 parent = {} // 父节点,用于回溯 while open_list is not empty: current = state in open_list with the lowest f_score // 从开放列表中选取f值最小的状态 open_list.remove(current) closed_list.append(current) if current == goal_state: // 找到目标状态,回溯路径 path = [] while current in parent: path.append(current) current = parent[current] path.append(initial_state) path.reverse() return path for neighbor in get_neighbors(current): // 扩展当前状态的邻居节点 if neighbor in closed_list: continue // 跳过封闭列表中的节点 tentative_g_score = g_score[current] + 1 // 计算邻居节点的代价,每次移动一个数字或空格算作一步 if neighbor not in open_list or tentative_g_score < g_score[neighbor]: parent[neighbor] = current g_score[neighbor] = tentative_g_score f_score[neighbor] = g_score[neighbor] + heuristic(neighbor, goal_state) if neighbor not in open_list: open_list.append(neighbor) // 加入开放列表,如果邻居节点不在开放列表中 return [] // 无解 function heuristic(state, goal_state): // 启发函数,计算从当前状态到目标状态的估计代价,使用曼哈顿距离 distance = 0 for i in range(1, 9): current_pos = get_position(state, i) goal_pos = get_position(goal_state, i) distance += abs(current_pos[0] - goal_pos[0]) + abs(current_pos[1] - goal_pos[1]) return distance function get_position(state, value): // 获取数字value在状态state中的位置 for i in range(3): for j in range(3): if state[i][j] == value: return (i, j) function get_neighbors(state): // 获取当前状态的邻居节点 neighbors = [] empty_pos = get_position(state, 0) for move in [(0, -1), (-1, 0), (0, 1), (1, 0)]: new_pos = (empty_pos[0] + move[0], empty_pos[1] + move[1]) if 0 <= new_pos[0] < 3 and 0 <= new_pos[1] < 3: new_state = swap(state, empty_pos, new_pos) neighbors.append(new_state) return neighbors function swap(state, pos1, pos2): // 交换状态state中位置pos1和位置pos2上的数字或空格 new_state = [row[:] for row in state] new_state[pos1[0]][pos1[1]] = state[pos2[0]][pos2[1]] new_state[pos2[0]][pos2[1]] = state[pos1[0]][pos1[1]] return new_state ``` 在实现A*算法时,需要注意以下几点: 1. 需要使用优先队列来维护开放列表,确保每次选取f值最小的状态进行扩展。 2. 启发函数和代价函数的设计很关键,可以影响算法的效率和正确性。对于八数码难题,可以使用曼哈顿距离作为启发函数,每次移动一个数字或空格算作一步。 3. 在计算估计代价时,需要使用从起始状态到当前状态的代价加上从当前状态到目标状态的启发函数值。 4. 通过回溯,可以找到从起始状态到目标状态的最优路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值