启发式搜索 | 十六格拼图 | 15Puzzle | C/C++实现

问题描述

十六格拼图就是在4×4的格子上摆放15块拼图,空出一个格子,玩家要借助这1个空格上下左右滑动拼图,最终完成整幅图画

我们像下面这样将空格定为0,然后给15块拼图分别标上1到15号

1 2 3 4
6 7 8 0
5 10 11 12
9 13 14 15

1次操作可以将1块拼图移向空格,当15块拼图全部与下述位置吻合时完成游戏

1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 0

现给定十六格拼图的初始状态,请编写一个程序,求出完成该十六格拼图至少需要移动多少次

输入:
输入表示拼图块或空格的4×4个整数。每4个整数占1行,总计4行,相邻数据间用空格隔开
输出:
输出最少移动次数,占1行
限制:

输入示例

1 2 3 4
6 7 8 0
5 10 11 12
9 13 14 15

输出示例

8

讲解

本题的状态数极为庞大,因此不能像九宫格拼图一样用单纯的深度优先搜索或广度优先搜索求解(参见:九宫格拼图 | 8Puzzle | C/C++实现)。这里我们给各位介绍几种高等搜索算法,它们可以解开十六格拼图这类状态数量庞大的问题

迭代加深

通过单纯的深度优先搜索无法找出初始状态的最短路径,但是重复进行限制最大深度的深度优先搜索(深度受限搜索)却可以做到。简单说来,就是在循环执行深度受限搜索的过程中逐步增加限制值limit,直至找到解为止,这种算法称为迭代加深(Iterative Deepening)。

一般情况下,为了提高搜索速度,迭代加深不会记录已搜索过的状态。但与此同时,迭代加深也需要做一些适当的调整,从而避免出现需要回溯到上一状态的情况

IDA*

在迭代加深中,通过推测值进行剪枝处理的算法称为迭代加深A活着IDA。这里的推测值又称为启发,通常可以取完成目标所需的下限值

对于十六格拼图而言,如果能预估出当前状态到最终状态的最小成本h,我们就可以对搜索范围进行剪枝了。也就是说,如果当前状态的深度g加上最小成本h(即”从这里开始至少还需要h次状态迁移“)超过了限制深度d,就可以直接中断搜索。

这里的h是一个预估值,不需要十分精确。另外要注意,虽然增大h的值能提高搜索速度,但预估得太大会导致找不到解

我们来看看拼图的推测值:
候选1:设不在最终位置的拼图块数为推测值h1
候选2:设各拼图块到最终状态之间的曼哈顿距离总和为推测值h2

曼哈顿距离指:不进行任何斜向移动,仅进行上下左右移动时所测出的2点之间的总距离

这里的h1和h2都可以用作推测值(下限值),但h2比h1更大,所以更有优势。

A*

前面讲到迭代加深 A ∗ A* A中用到了推测值,实际上,这个值同样适用于以含有优先级队列的狄克斯特拉算法(或广度优先搜索)为基础的搜索算法。这类算法称为 A ∗ A* A算法,它用优先级队列管理状态,优先对起点到当前位置的成本+当前位置到目标状态的推测值最小的状态进行状态迁移,因此可以更快找到解

AC代码如下

1.用IDA*实现
#include<stdio.h>
#include<iostream>
#include<cmath>
#include<string>
#include<cassert>
using namespace std;
#define N 4
#define N2 16
#define LIMIT 100

static const int dx[4] = {0, -1, 0, 1};
static const int dy[4] = {1, 0, -1, 0};
static const char dir[4] = {'r', 'u', 'l', 'd'};
int MDT[N2][N2];

struct Puzzle { int f[N2], space, MD; };

Puzzle state;
int limit;//深度限制
int path[LIMIT];

int getAllMD(Puzzle pz) {
	int sum = 0;
	for(int i = 0; i < N2; i++) {
		if(pz.f[i] == N2) continue;
		sum += MDT[i][pz.f[i] - 1];
	}
	return sum;
} 

bool isSolved() {
	for(int i = 0; i < N2; i++) if(state.f[i] != i + 1) return false;
	return true;
}

bool dfs(int depth, int prev) {
	if(state.MD == 0) return true;
	//如果当前深度加上启发超过了限制,则进行剪枝
	if(depth + state.MD > limit) return false;
	
	int sx = state.space / N;
	int sy = state.space % N;
	Puzzle tmp;
	
	for(int r = 0; r < 4; r++) {
		int tx = sx + dx[r];
		int ty = sy + dy[r];
		if(tx < 0 || ty < 0 || tx >= N || ty >= N ) continue;
		if(max(prev, r) - min(prev, r) == 2) continue;
		tmp = state;
		//计算曼哈顿距离的差值,同时交换拼图块
		state.MD -= MDT[tx * N + ty][state.f[tx * N + ty] - 1];
		state.MD += MDT[sx * N + sy][state.f[tx * N + ty] - 1];
		swap(state.f[tx * N + ty], state.f[sx * N + sy]);
		state.space = tx * N + ty;
		if(dfs(depth + 1, r) ) { path[depth] = r; return true; }
		state = tmp;
	} 
	
	return false;
}

//迭代加深
string iterative_deepening(Puzzle in) {
	in.MD = getAllMD(in);//初始状态的曼哈顿距离
	
	for(limit = in.MD; limit <= LIMIT; limit++) {
		state = in;
		if( dfs(0, -100) ) {
			string ans = "";
			for(int i = 0; i < limit; i++) ans += dir[path[i]];
			return ans;
		}
	} 
	
	return "unsolvable";
} 

int main(){
	for(int i = 0; i < N2; i++)
		for(int j = 0; j < N2; j++)
			MDT[i][j] = abs(i / N - j / N) + abs(i % N - j % N);
			
	Puzzle in;
	
	for(int i = 0; i < N2; i++) {
		cin>>in.f[i];
		if(in.f[i] == 0) {
			in.f[i] = N2;
			in.space = i;
		}
	}
	string ans = iterative_deepening(in);
	cout<<ans.size()<<endl;
	
	return 0;
}
2.用A*实现
#include<cstdio>
#include<iostream>
#include<cmath>
#include<map>
#include<queue>

using namespace std;
#define N 4
#define N2 16

static const int dx[4] = {0, -1, 0, 1};
static const int dy[4] = {1, 0, -1, 0};
static const char dir[4] = {'r', 'u', 'l', 'd'};
int MDT[N2][N2];

struct Puzzle {
	int f[N2], space, MD;
	int cost;
	
	bool operator < (const Puzzle &p) const {
		for(int i = 0; i < N2; i++) {
			if(f[i] == p.f[i]) continue;
			return f[i] < p.f[i];
		}
		return false;
	}
}; 

struct State {
	Puzzle puzzle;
	int estimated;
	bool operator < (const State &s) const {
		return estimated > s.estimated;
	}
};

int getAllMD(Puzzle pz) {
	int sum = 0;
	for(int i = 0; i < N2; i++) {
		if(pz.f[i] == N2) continue;
		sum += MDT[i][pz.f[i] - 1];
	}
	return sum;
}

int astar(Puzzle s) {
	priority_queue<State> PQ;
	s.MD = getAllMD(s);
	s.cost = 0;
	map<Puzzle, bool> V;
	Puzzle u, v;
	State initial;
	initial.puzzle = s;
	initial.estimated = getAllMD(s);
	PQ.push(initial);
	
	while( !PQ.empty() ) {
		State st = PQ.top(); PQ.pop();
		u = st.puzzle;
		
		if(u.MD == 0) return u.cost;
		V[u] = true;
		
		int sx = u.space / N;
		int sy = u.space % N;
		
		for(int r = 0; r < 4; r++) {
			int tx = sx + dx[r];
			int ty = sy + dy[r];
			if(tx < 0 || ty < 0 || tx >= N || ty >= N ) continue;
			v = u;
			
			v.MD -= MDT[tx * N + ty][v.f[tx * N + ty] - 1];
			v.MD += MDT[sx * N + sy][v.f[tx * N + ty] - 1];
			
			swap(v.f[sx * N + sy], v.f[tx * N + ty]);
			v.space = tx * N + ty;
			if( !V[v] ) {
				v.cost++;
				State news;
				news.puzzle = v;
				news.estimated = v.cost + v.MD;
				PQ.push(news);
			}
		}
	}
	return -1;
}

int main(){
	for(int i = 0; i < N2; i++)
		for(int j = 0; j < N2; j++)
			MDT[i][j] = abs(i / N - j / N) + abs(i % N - j % N);
			
	Puzzle in;
	
	for(int i = 0; i < N2; i++) {
		cin>>in.f[i];
		if(in.f[i] == 0) {
			in.f[i] = N2;
			in.space = i;
		}
	}
	cout<<astar(in)<<endl;
	
	return 0;
}
  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值