2005
年百度之星程序设计大赛试题总决赛题目
题目描述:
八方块移动游戏要求从一个含
8
个数字(用
1-8
表示)的方块以及一个空格方块(用
0
表示)的
3x3
矩阵的起始状态开始,不断移动该空格方块以使其和相邻的方块互换,直至达到所定义的目标状态。空格方块在中间位置时有上、下、左、右
4
个方向可移动,在四个角落上有
2
个方向可移动,在其他位置上有
3
个方向可移动。例如,假设一个
3x3
矩阵的初始状态为:
8 0 3
2 1 4
7 6 5
目标状态为:
1 2 3
8 0 4
7 6 5
则一个合法的移动路径为:
8 0 3 8 1 3 8 1 3 0 1 3 1 0 3 1 2 3
2 1 4 => 2 0 4 => 0 2 4 => 8 2 4 => 8 2 4 => 8 0 4
7 6 5 7 6 5 7 6 5 7 6 5 7 6 5 7 6 5
另外,在所有可能的从初始状态到目标状态的移动路径中,步数最少的路径被称为最短路径;在上面的例子中,最短路径为
5
。如果不存在从初试状态到目标状态的任何路径,则称该组状态无解。
请设计有效的(细节请见评分规则)算法找到从八方块的某初试状态到某目标状态的所有可能路径中的最短路径,并用
C/C++
实现。
输入数据:
程序需读入已被命名为
start.txt
的初始状态和已被命名为
goal.txt
的目标状态,这两个文件都由
9
个数字组成(
0
表示空格,
1-8
表示
8
个数字方块),每行
3
个数字,数字之间用空格隔开。
输出数据:
如果输入数据有解,输出一个表示最短路径的非负的整数;如果输入数据无解,输出
-1
。
自测用例:
如果输入为:
start.txt
和
goal.txt
,则产生的输出应为:
5
又例,如果用
7 8 4
3 5 6
1 0 2
替换
start.txt
中的内容,则产生的输出应为:
21
评分规则:
1
)我们将首先使用和自测用例不同的
10
个
start.txt
以及相同的
goal.txt
,每个测试用例的运行时间在一台
Intel Xeon 2.80GHz 4 CPU/ 6G
内存的
Linux
机器上应不超过
10
秒(内存使用不限制),否则该用例不得分;
2
)每个选手的总分(精确到小数点后
6
位)
=10
秒钟内能产生正确结果的测试用例数量
x10+
(
1/
产生这些正确结果的测试用例的平均运行毫秒
)
;
3
)如果按此评分统计仍不能得出总决赛将决出的一、二、三等奖共计九名获奖者,我们将先设
N=2
,然后重复下述过程直至产生最高的
9
位得分:用随机生成的另外
10
个有解的
start.txt
再做测试,并对这
10*N
个测试用例用
2
)中公式重新计算总分,
N++
。
下面是我的源代码:
#include "stdafx.h"
#include "string.h"
#include "fstream"
#include "vector"
#include "iostream"
#include "stack"
using namespace std;
#define MAX_LENGTH 1000000
typedef struct element{
int current[10];//用来保存当前的矩阵
int i;//保存当前的0位置
int j;//保存当前的方向
int revers_j;//保存最后一次移动的反方向
int counter;//保存当前移动的次数
} ELEMENT;
bool IsStringAllNum(char *p)//次函数验证是否传人的字符串是只包含数字
{
char *temp=p;
while(*temp!=0)
{
if((*temp>'9' || *temp<'0')&& *temp!=' '&& *temp!='+'&&*temp!='-')//如果不是数字有不是空格
{
cout<<"某行输入中含有非数字"<<endl;
return false;
}
temp++;
}
return true;
}
bool IsStringFromThreeParts(char *p)//这个函数验证是否这一行字符串是由3个部分组成
{
char *lp=p;
int counter=0;
bool IsBeforeBlack=true;
while(*lp!=0)
{
if(*lp==' ')
IsBeforeBlack=true;
else if(*lp!=' '&& IsBeforeBlack==true)//如果不等于空格
{
counter++;
IsBeforeBlack=false;
}
++lp;
}
if(counter==3)
return true;
else
{
cout<<"某行输入不止由3个数字组成"<<endl;
return false;
}
}
bool IsArrayLegal(int (&start)[10])//此函数验证start.txt或者goal.txt中的输入是否合法(数字分别为:0-8),假定输入为9个数字
{
bool tags[9]={false,false,false,false,false,false,false,false,false};
for(int i=1;i<10;++i)
{
if(start[i]<0 || start[i]>8)
{
cout<<"输入文件中含有0-8之外的数字"<<endl;
return false;
}
tags[start[i]]=true;
}
for(int i=0;i<9;++i)
{
if(tags[i]==false)
{
cout<<"start的输入中没有数字" <<i<<endl;
return false;
}
}
return true;
}
bool IsStartGoalEqual(int (&array1)[10],int(&array2)[10])//次函数验证是否array1和array2是相等的,如果是相等那么返回true
{
for(int i=1;i<10;++i)
{
if(array1[i]!=array2[i])
return false;
}
return true;
}
char * DeleteBlacks(char *p)//此函数去除字符串前面的空格
{
char *lp=p;
while(*lp==' ')
++lp;
return lp;
}
bool ReadStartAndGoal(int (&start)[10],int(&goal)[10])//从文件中读取目标和开始的状态
{
string str;
str="D://start.txt";
string str2;
str2="D://goal.txt";
fstream infile;
fstream infile2;
infile.open(str.c_str(),ios::in);
infile2.open(str2.c_str(),ios::in);
if((!infile.is_open())||(!infile2.is_open()))
{
cout<<"打开start.txt或者goal.txt文件失败"<<endl;
return false;
}
char temp[20];
char temp2[20];
char *p,*p2;
int value=0;
int value2=0;
for(int i=1;i<4;i++)//只读取3行,每行读取3个数字,共9个数字
{
infile.getline(temp,20);
infile2.getline(temp2,20);
p=temp;
p2=temp2;
if((!IsStringFromThreeParts(p))||(!IsStringFromThreeParts(p2)))
return false;
if((!IsStringAllNum(p))||(!IsStringAllNum(p2)))//如果字符串中含有非数字
return false;
for(int j=1;j<4;j++)
{
p=DeleteBlacks(p);
p2=DeleteBlacks(p2);
value=atoi(p);
value2=atoi(p2);
start[(i-1)*3+j]=value;
goal[(i-1)*3+j]=value2;
p++;
p2++;
}
}
if((!infile.eof())||(!infile2.eof()))
{
cout<<"输入文件的行数过多"<<endl;
return false;
}
if((!IsArrayLegal(start))||(!IsArrayLegal(goal)))//验证是否start和goal输入合法
return false;
infile.close();
infile2.close();
return true;
}
int CalculateExchangePositionNumber(int i,int j)//计算要交换的位置号,输入为当前0所在位置i,和交换方向j
{ //出错返回-1;
switch (j)
{
case 1://往上
return i-3;
case 2://往右
return i+1;
case 3://往下
return i+3;
case 4://往左
return i-1;
default:
return -1;
}
}
int FindZeroPosition(int (&array)[10])//找到0所在的位置,如果返回1-9z正常,如果返回0,说明不存在0
{
int result=0;
for(int i=1;i<10;i++)
{
if(array[i]==0)
result=i;
}
return result;
}
int FindReversJ(int j)//找到与j 相反的方向
{
switch(j)
{
case 1:
return 3;
case 2:
return 4;
case 3:
return 1;
case 4:
return 2;
default:
return -1;
}
}
bool FindIsChange(int (&array1)[10],int(&array2)[10],int (&ischange)[10])//此函数找到改变的所有位置,并且保存在ischange这个数组中,1表示改变,0表示没有改变
{
for(int i=1;i<10;i++)
{
if(array1[i]==array2[i])
ischange[i]=0;
else
ischange[i]=1;
}
return true;
}
int _tmain(int argc, _TCHAR* argv[])
{
//初始化avalible 方向数组
//avalible[i][j]其中i表示第几个位置,j表示哪个方向,按逆时针顺序:上,右,下,左 为1,2,3,4
//数值为1表示可以移动,数值为0表示不可以移动。
int avalible[10][5]=
{
0,0,0,0,0,//忽略
0,0,1,1,0,
0,0,1,1,1,
0,0,0,1,1,
0,1,1,1,0,
0,1,1,1,1,
0,1,0,1,1,
0,1,1,0,0,
0,1,1,0,1,
0,1,0,0,1
};
int start[10];//0号下标不用
int goal[10];//0号下标不用
int ischange[10];//用来保存改变状态
// vector<int> vector_start;//开始状态
// vector<int> vector_goal;//目的状态
int minlength=MAX_LENGTH;//保存最短路径
if(!ReadStartAndGoal(start,goal))//输入start和goal 2个矩阵
{
getchar();
return 1;
}
if(IsStartGoalEqual(start,goal))//如果start和goal相等
{
cout<<"start和goal相等,不用移动"<<endl;
getchar();
return 1;
}
int zero_origial_position=FindZeroPosition(start);
int i=zero_origial_position;//i表示当前的0所在的位置
int j=1;//j表示当前的移动方向
int counter=0;//counter表示当前的移动次数
int current[10];//current保存当前的矩阵状态
for(int h=1;h<10;h++)
current[h]=start[h];
stack<ELEMENT> mystack;//mystack用来保存移动过程的元素
int revers_j=0;//用来保存移动方向的相反方向,用来保证不向反方向移动而导致死循环
FindIsChange(start,goal,ischange);//找到改变的状态
while(1)
{
while(j<=4)
{
if(avalible[i][j]==1 && j!=revers_j &&ischange[(CalculateExchangePositionNumber(i,j))]==1 )//能够向j方向移动
{
revers_j=FindReversJ(j);//保存j的反方向
ELEMENT temp_element;
for(int k=1;k<10;++k)
temp_element.current[k]=current[k];
temp_element.counter=counter;
temp_element.i=i;
temp_element.j=j;
temp_element.revers_j=revers_j;
mystack.push(temp_element);//保存现场
int temp_value;//中间交换值
int exchange_position;//交换位置
exchange_position=CalculateExchangePositionNumber(i,j);
if(exchange_position==-1)
{
cout<<"计算交换位置出错"<<endl;
return 1;
}
temp_value=current[i];
current[i]=current[exchange_position];
current[exchange_position]=temp_value;
i=exchange_position; //0移动一个位置
counter++;//增加移动次数
j=1;//
//下面显示移动过程
cout<<"移动过程"<<endl;
for(int t=1;t<10;t++)
cout<<current[t];
cout<<endl;
if(counter>=minlength)
{
if(mystack.size()!=0)
{
ELEMENT temp_element;
temp_element=mystack.top();//恢复现场
mystack.pop();//弹出栈
for(int x=1;x<10;++x)
current[x]=temp_element.current[x];
i=temp_element.i;
j=temp_element.j+1;
revers_j=temp_element.revers_j;
counter=temp_element.counter;
}
else
{
cout<<"无解!!!"<<endl;
return 1;
}
}
else if(IsStartGoalEqual(current,goal))//如果当前状态跟目标状态相等
{
if(mystack.size()!=0)
{
minlength=counter;
ELEMENT temp_element;
temp_element=mystack.top();//恢复现场
mystack.pop();//弹出栈
for(int x=1;x<10;++x)
current[x]=temp_element.current[x];
i=temp_element.i;
j=temp_element.j+1;
revers_j=temp_element.revers_j;
counter=temp_element.counter;
}
else
{
cout<<"无解!!!"<<endl;
return 1;
}
}
}//end of if
else //如果不可以向这个方向移动
j=j+1;
}//end of while 2
if(j>4 && (i!=zero_origial_position))
{
if(mystack.size()!=0)
{
ELEMENT temp_element;
temp_element=mystack.top();//恢复现场
mystack.pop();//弹出栈
for(int x=1;x<10;++x)
current[x]=temp_element.current[x];
i=temp_element.i;
j=temp_element.j+1;
revers_j=temp_element.revers_j;
counter=temp_element.counter;
}
else
{
cout<<"无解!!!"<<endl;
return 1;
}
continue;
}
if(j>4 && (i==zero_origial_position))
break;//结束整个过程
}//end of while 1
if(minlength!=MAX_LENGTH)
cout<<"最短路径为:"<<minlength<<endl;
else
cout<<-1<<endl;
getchar();
return 0;
}
/*
在这个过程中发现的自己的一个小错误:
1.在初始化结构体的时候:
ELEMENT temp_element=
{
temp_array[10],i,j,revers_j,counter
};
是这么初始化的,后面在压栈和出栈的时候,发现里面的数据全为0
所以不能这么初始化结构体,当时是为了偷懒:
正确方法如下:
ELEMENT temp_element;
for(int k=1;k<10;++k)
temp_element.current[k]=current[k];
temp_element.counter=counter;
temp_element.i=i;
temp_element.j=j;
temp_element.revers_j=revers_j;
切记:要一个成员一个成员的赋值。
*/