【NOJ1571】【算法实验三】【分支限界法】八数码


1571.八数码

时限:5000ms 内存限制:20000K  总时限:10000ms

描述

在九宫格里放在1到8共8个数字还有一个是空格,与空格相邻的数字可以移动到空格的位置,问给定的状态最少需要几步能到达目标状态(用0表示空格):
1 2 3
4 5 6
7 8 0

输入

输入一个给定的状态。

输出

输出到达目标状态的最小步数。不能到达时输出-1。


#include <iostream>
#include <queue>
#include <map>
using namespace std;

struct node
{
	int a[3][3];	//存储八数码
	int zx,zy;		//0的位置
	int num;        //将每个八数码状态转换成九位整数(最大为9876543210,int范围足够)
};

node target;    //存储目标状态

queue <node> q1;    //广搜所用队列

map <int, int> used;    //判重

map <int, int> step;    //存储步数


int bfs();  //广搜

node setnum(node n1);   //计算n1.num

bool canmoveto(node n1, int d);     //判断越界或重复

node moveto(node n1, int d);        //返回新状态

bool totarget(node n1);     //判断是否到达目标状态

int main()
{
	node start;             //输入初始状态
	for(int i=0; i<3; i++)
	{
		for(int j=0; j<3; j++)
		{
			cin>>start.a[i][j];
			if(start.a[i][j]==0)    //记录0的位置
			{
				start.zx=i;
				start.zy=j;
			}
		}
	}
	start=setnum(start);    //计算start.num

	//初始节点入队,并更新used和step数组
	q1.push(start);
	//cout<<used[start.num]<<endl;
	used[start.num]=1;
	step[start.num]=0;

	//设定目标状态
	int k=1;
	for(int i=0; i<3; i++)
	{
		for(int j=0; j<3; j++)
		{
			target.a[i][j]=k;
			k++;
		}
	}
	target.a[2][2]=0;

    //进行广搜
	cout<<bfs()<<endl;

	return 0;
}


int bfs()
{
    node top,next;
    while(!q1.empty())
	{
		top=q1.front();
		//cout<<"gettop:"<<top.num<<endl;
		q1.pop();

		for(int d=0; d<4; d++)  //将0与四个方向上的数字交换,0==左,1==下,2==右,3==上
		{
			if(canmoveto(top, d))   //判断重复和越界
			{
				next=moveto(top, d);    //获得新状态
				if(totarget(next))      //到达目标状态则返回
				{
					return step[next.num];
				}
				else    //没到达目标状态则入队
				{
                    q1.push(next);
                    /*
                    cout<<step[next.num]<<endl;
                    cout<<"push"<<next.num<<endl;
                    */
				}
			}
		}
	}
	return -1;
}

//计算n1.num
node setnum(node n1)
{
    n1.num=0;
    for(int i=0; i<3; i++)
	{
		for(int j=0; j<3; j++)
		{
			n1.num*=10;         //将九个格内数字组合成九位整数
			n1.num+=n1.a[i][j];
		}
	}
	return n1;
}


//返回false条件:越界、重复
bool canmoveto(node n1, int d)
{
	node n2;    //先建立一个新节点,复制n1的状态
	for(int i=0; i<3; i++)
	{
		for(int j=0; j<3; j++)
		{
			n2.a[i][j]=n1.a[i][j];
		}
	}
	n2.zx=n1.zx;
	n2.zy=n1.zy;
	n2.num=n1.num;

	switch(d)
	{
		case 0:	//判断能否将0与左边数字交换
		{
			if(n2.zy==0)    //越界
				return false;
			else
			{
				swap(n2.a[n2.zx][n2.zy],n2.a[n2.zx][n2.zy-1]);  //将0与左边数字交换
				n2.zy--;    //更新0的位置
				break;
			}
		}
		case 1:	//下
		{
			if(n2.zx==2)
				return false;
			else
			{
				swap(n2.a[n2.zx][n2.zy],n2.a[n2.zx+1][n2.zy]);
				n2.zx++;
				break;
			}
		}
		case 2:	//右
		{
			if(n2.zy==2)
				return false;
			else
			{
				swap(n2.a[n2.zx][n2.zy],n2.a[n2.zx][n2.zy+1]);
				n2.zy++;
				break;
			}
		}
		case 3:	//上
		{
			if(n2.zx==0)
				return false;
			else
			{
				swap(n2.a[n2.zx][n2.zy],n2.a[n2.zx-1][n2.zy]);
				n2.zx--;
				break;
			}
		}
	}
	n2=setnum(n2);
	/*
	cout<<d<<' '<<n2.num<<endl;
	cout<<used.count(n2.num)<<endl;
	*/
	if(used.count(n2.num)!=0)   //判重,重复返回false
	{
		//cout<<"chongfu"<<endl;
		return false;
	}
	else
	{
		return true;
	}
}

//返回新状态
node moveto(node n1, int d)
{
	node n2;
	for(int i=0; i<3; i++)
	{
		for(int j=0; j<3; j++)
		{
			n2.a[i][j]=n1.a[i][j];
		}
	}
	n2.zx=n1.zx;
	n2.zy=n1.zy;
	n2.num=n1.num;

	switch(d)
	{
		case 0:	//将0与左边数字交换
		{
			swap(n2.a[n2.zx][n2.zy],n2.a[n2.zx][n2.zy-1]);
			n2.zy--;
			break;
		}
		case 1:	//下
		{
			swap(n2.a[n2.zx][n2.zy],n2.a[n2.zx+1][n2.zy]);
			n2.zx++;
			break;
		}
		case 2:	//右
		{
			swap(n2.a[n2.zx][n2.zy],n2.a[n2.zx][n2.zy+1]);
			n2.zy++;
			break;
		}
		case 3:	//上
		{
			swap(n2.a[n2.zx][n2.zy],n2.a[n2.zx-1][n2.zy]);
			n2.zx--;
			break;
		}
	}
	n2=setnum(n2);

	//更新used数组,n2状态已用过
	used[n2.num]=1;
	//更新step数组,到达n2状态步数=1+到达n1状态步数
	step[n2.num]=step[n1.num]+1;

	//返回新状态n2
	return n2;
}

//判断是否到达目标状态
bool totarget(node n1)
{
	for(int i=0; i<3; i++)
	{
		for(int j=0; j<3; j++)
		{
			if(n1.a[i][j]!=target.a[i][j])
			{
				return false;
			}
		}
	}
	return true;
}


【后记】

1.写的时候出了个bug,把0和相邻数字交换位置后忘了更新0的新位置。

2.关于map判重,在这篇文章里有讲到https://blog.csdn.net/qq_41727666/article/details/83004756

3.每个八数码状态的编码方法如下图所示,判重时要用到:

扫描二维码关注公众号,回复: 3563490 查看本文章
编码示意图
八数码状态编码示意图

猜你喜欢

转载自blog.csdn.net/qq_41727666/article/details/83031172