Aizu ALDS1_13_C 15 Puzzle(八数码问题升级 十五数码问题 BFS IDA*入门 剪枝)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/UncleJokerly/article/details/79963013

The goal of the 15 puzzle problem is to complete pieces on 4×44×4 cells where one of the cells is empty space.

In this problem, the space is represented by 0 and pieces are represented by integers from 1 to 15 as shown below.

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

You can move a piece toward the empty space at one step. Your goal is to make the pieces the following configuration in the shortest move (fewest steps).

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

Write a program which reads an initial state of the puzzle and prints the fewest steps to solve the puzzle.

Input

The 4×44×4 integers denoting the pieces or space are given.

Output

Print the fewest steps in a line.

Constraints

  • The given puzzle is solvable in at most 45 steps.

Sample Input

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

Sample Output

8

问题描述:

问题还是那个问题,只不过从9个格子升级到了16个格子,找出移到目标状态所需的最少步数。


解题思路:

弱智的我当然还是先来无脑的BFS了

MLE代码:

#include<stdio.h>
#include<string.h>
#include<map>
#include<queue>
#include<string>
#include<iostream> 
using namespace std;

string str;
map<string,int> book;

int nx[4][2]={-1,0,0,1,1,0,0,-1};

struct node
{
	string sum;
	int step;
};

void bfs()
{
	queue<node> q;
	node st,en;
	st.sum=str;
	st.step=0;
	q.push(st);
	while(!q.empty())
	{
		st=q.front();
		q.pop();
		
		int dis;//找'0'的下标 
		for(dis=0;dis<16;dis++)
		{
			if(st.sum[dis]=='0') break;
		}
		int vx=dis/4;
		int vy=dis%4;
		for(int i=0;i<4;i++)
		{
			int tx=vx+nx[i][0];
			int ty=vy+nx[i][1];
			int tz=tx*4+ty;//交换后的下标 
			if(tx>=0&&tx<4&&ty>=0&&ty<4)
			{
				en.sum=st.sum;
				en.sum[dis]=st.sum[tz];
				en.sum[tz]=st.sum[dis];
				if(en.sum=="123456789ABCDEF0")
				{
					printf("%d\n",st.step+1);
					return;
				}
				if(!book[en.sum])
				{
					book[en.sum]=1;
					en.step=st.step+1;
					q.push(en);
				}
			}
		}
		
	}
}

int main()
{
	int x;
	for(int i=0;i<16;i++)
	{
		scanf("%d",&x);
		if(x>9) str+=x-10+'A';
		else str+=x+'0';
	}
	//cout<<str<<endl;
	book.clear();
	book[str]=1;
	bfs();
	return 0;
}

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

结果很明显



直接来BFS肯定是行不通的了(为什么?)


从9个格子升级到16个格子,在搜索的过程中相应的中间状态也会增加很多,当起始状态距目标状态较远时,队列中不断添加中间状态最后导致内存超限。


IDA*算法就是一种剪枝(在我看来

下面引用

A*和IDA*算法

十五数码问题

IDA*算法解决十五数码问题

A*算法(A-Star)

为了赋予计算机“智慧”,我们必须更有智慧(:-P),计算机可以通过一个“估价函数”确定一个状态的“前途”的量,人呢……必须找到这个估价函数。
通常,估价函数计为f^(n),f^中,^在f上方,读作f-hat(帽子)。估价函数的定义如下:
f^(n)=g^(n)+h^(n)

n是状态的表示,通常是状态的编号之类的。在编程中,可以写作f_hat()或者直接写成f()即可。

g^(n)表示从初始状态到n 总共花费的代价。

h^(n)表示从n到目标状态估计需要花费的代价,对于一个正确的搜索,h^(n)<=h(n),h(n)表示从n到目标状态的实际代价(没有算出来的时候,当然是求不出来h(n)的,但是可以找到一个h^(n)<=h(n)哦),在这个范围内,h^(n)越接近h(n),搜索的启发效果越好。
对于h^1(n)和h^2(n),如果存在h^1(n)<h^2(n)<=h(n),且h^1(n)<=h^2(n),我们说h^2(n)比h^1(n)更灵通(more informed)。

f^(n)的值就是对n这个状态的估价。这个估价主要体现在h^(),因为g^()是已知的;g^()体现了广度优先,当h^()>g^()时,可以省略g^()从而提高效率。

搜索中,每到新的状态,计算它的f^()值,在目前所有的状态中,优先扩展f^()最小的,如果f^()最小的中有很多,则优先扩展其中g^()最大的。


15数码问题是人工智能中的一个经典问题。所谓的15数码问题:就是在一个4*4的16宫格棋盘上,摆放有15个将牌,每一个都刻有1-15中的某一个数码。棋盘中留有一个空格,允许其周围的某一个将牌向空格移动,这样通过移动将牌就可以不断改变将牌的布局。所要求解的问题:是给定一种初始布局(初始状态)和一个目标布局(目标状态),问如何移动数码实现从初始状态到目标状态的转变,下面给出一种示例:

1  2  3  4                          1  2 3  4

5  6  7  8                          5  6 7  8

9  10 1112                         9  10 11 12

13 14 15 0                          0  13 14 15

初始状态                            目标状态

本次实现中使用的是IDA*算法,在介绍该算法之前,需要先介绍几个概念,以便更好的介绍IDA*。

逆序数从左到右看每个数的右边比它小的数的个数,并进行求和。

作用:初始状态和目标状态都是4*4的矩阵,我们分别两个矩按从左到右从上到下的接起来成一个有16个数字的序列(0是空格),并除去0得到两个15数字的序列。分别计算两个序列的逆序数。于是这里用到一个定理:设初始状态0所在的行数为i,目标状态0所在的行数为j,两者之差的绝对值为k。若k为奇数,则两个矩阵相应的逆序数的奇偶性相异才有解。若k为偶数,则两个矩阵的逆序数必须相同才有解。不是上述两种情况即为无解。通过初始判定就可以不用搜索就能直接否定无解情况。

 

曼哈顿距离:在标准坐标系中,两点的x坐标差的绝对值与y坐标差的绝对值之和为曼哈顿距离。

作用:求出初始矩阵与目标矩阵对应值得曼哈顿距离并求和(除去0)得到的值为评估值,写成函数即为评估函数。该值为从初始状态到目标状态所要经过的最小步数,实际步数只会大于等于该值。


算法介绍:这次使用的算法是IDA*我们首先是用逆序数进行判定是否有解,有解才进行搜索。有解的话,则先得到评估函数的初始值,该值为最小步数,递归深度(步数)必然大于等于这个初始值limit。我们先按深度搜索寻遍该深度的所有情况,看是否能找到解,有解则该解是最优解。若没解,则将深度的限制条件limit加1,再次递归下一层深度的所有情况,有解即为最优解,无解则继续将深度限制条件limit加1,这样不停循环直到某个深度maxLevel,则放弃寻找,因为在maxLevel步中没有找到,继续找下去时间花销太高,故放弃寻找。这就是IDA*中ID的意思,迭代加深。其中,算法在递归中使用了当前递归深度level,用level+评估函数(当前状态到目标状态至少需要的步数)<=limit作为剪枝条件,不满足该条件的在该分支上肯定无解。这样我们就可以找到在maxLevel步以内的最优解。

————————————分割线—————————————
IDA*算法是A*算法和迭代加深算法的结合. 
A*算法需要维护open表和close表,以及排序选择最小代价的结点内存空间消耗过多。 
IDA*的答题思路是。首先 根据最初的数码表 
5 1 2 4 
9 6 3 8 
13 15 10 11 
14 0 7 12 
目标状态: 
1 2 3 4 
5 6 7 8 
9 10 11 12 
13 14 15 0 
计算它到目标状态的曼哈顿距离之和,以它作为一个标尺即整个程序的走步最多也不会多于初状态的曼哈顿距离。 
曼哈顿距离就是 每个数字最少需要几步走到最终位置。 
下面的程序按照 上左右下的顺序执行,递归调用.先调用dfs()函数执行0与其上面的数字交换,此处是15 判断是否可解(逆序数),可解可用则递归调用dfs() 如果OK 则继续调用深一层,不OK 进行向左 ,当上左右下都判断完毕不OK的话 回退一层,判断上一层的左 、右、下情况可以走通的话则继续向下递归。 

每一步执行之前 使用判断语句 当前调用深度+当前的曼哈顿距离<=最初的曼哈顿距离则可以继续。一旦大于就可以直接舍弃了。


———————————假装是分割线—————————————


对于本题已经告知有解,所以我们无需用逆序数先判断是否有解了。

总结一下(胡说八道):

对于本题使用IDA*,我们先计算初始状态到目标状态每个数字移到对应位置的曼哈顿距离的和limit,这个limit是从最初始的状态到目标状态最少需要的距离(没有任何阻碍不需要拐弯)


(百度百科:曼哈顿距离

从图中可以发现红黄蓝线的距离是一样的,就是曼哈顿距离。

而两点之间的绿线是欧几里得距离(装作自己知道


也就是说可能的步数最少就是这个limit。

我们要找的步数肯定是大于等于limit的,所以我们从最小的limit开始限制每次搜索的步数,如果在到达当前limit的时候没有到达目标状态说明可能还需要更多步才能走到,所以每次limit++,直到我们在某一个limit找到了目标状态,这就是迭代加深搜索(我编的)。

在每一次搜索时,每一个limit也就是当前搜索的最大步数了,每走一步计算一下当前走过的步数和剩余曼哈顿距离的和是否已经大于了limit,剪枝。


所以对于IDA*算法最重要的是要找出合适的预估函数hv(),赋予计算机“智慧”。(闭嘴


AC代码:

#include<stdio.h>  
#include<string.h>  
#include<math.h>  
#include<algorithm>
using namespace std;

int nx[4][2]={{-1,0},{0,-1},{0,1},{1,0}};//上 左 右 下  置换顺序

//各个数字应在位置对照表
int goal[16][2]= {{3,3},{0,0},{0,1},{0,2},//0 1 2 3
				  {0,3},{1,0},{1,1},{1,2},//4 5 6 7
				  {1,3},{2,0},{2,1},{2,2},//8 9 10 11
				  {2,3},{3,0},{3,1},{3,2}};//12 13 14 15
				  
int map[4][4],map2[16],limit;//limit全部的曼哈顿距离之和  
int flag,length;				  
      
int hv(int a[][4])//估价函数,曼哈顿距离,小于等于实际总步数
{
    int cost=0;
    for(int i=0;i<4;i++)
    {
        for(int j=0;j<4;j++)  
        {
            int w=map[i][j];
            if(w!=0) cost+=abs(i-goal[w][0])+abs(j-goal[w][1]);//不算0 
        }
    }
    return cost;
}

void dfs(int x,int y,int len,int pre_move)
{
    if(flag) return;
    int dv=hv(map);
    if(len==limit)
    {
        if(dv==0)  //成功 退出
        {
            flag=1;
            length=len;
            return;
        }
        else return;  //超过预设长度 回退
    }

    for(int i=0;i<4;i++)  
    {
        if(i+pre_move==3&&len>0) continue;//不和上一次移动方向相反,对第二步以后而言 
        int tx=x+nx[i][0];
        int ty=y+nx[i][1];
        if(tx>=0&&tx<4&&ty>=0&&ty<4)
        {
            swap(map[x][y],map[tx][ty]);
            int p=hv(map);
            if(p+len<=limit&&flag==0)
            {
                dfs(tx,ty,len+1,i);
                if(flag) return;
            }
            swap(map[x][y],map[tx][ty]);
        }
    }
}

int main()  
{
    int t=1;
    while(t--)       
    {
		int x1,y1;
        for(int i=0;i<16;i++)//map一维map2二维 
        {
            scanf("%d",&map2[i]);
            if(map2[i]==0)
            {
                x1=i/4;
				y1=i%4;
				map[x1][y1]=0;
            }
            else
            {
                map[i/4][i%4]=map2[i];
            }
        }                                      

        limit=hv(map);
        //printf("limit===%d\n",limit);
        flag=0;
        length=0;
        while(flag==0&&length<=45)//要求45步之内到达
        {
            dfs(x1,y1,0,0);
            if(flag==0) limit++; //得到的是最小步数
        }
        if(flag) printf("%d\n",length);
    }
    return 0;
}

阅读更多
想对作者说点什么?
相关热词

博主推荐

换一批

没有更多推荐了,返回首页