转载请标明是引用于 http://blog.csdn.net/chenyujing1234
欢迎大家提出意见,一起讨论!
在网上有讲到<<度编程大赛试题----类似九格宫的C++试题>>的文章:
http://hi.baidu.com/twtiyb/item/cfd1464aeb6fcc0ec016130e
一、题目
题目是这样的:
八方块移动游戏要求从一个含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个数字,数字之间用空格隔开。假定start.txt和goal.txt不会相同。
输出数据:如果输入数据有解,输出一个表示最短路径的非负的整数;如果输入数据无解,输出-1.请在数字输出后在输出一回车换行符。
自测用例:如果输入为:start.txt和goal.txt,则产生的输出应为:
5
如果用
7 8 4
3 5 6
1 0 2
替换start.txt中的内容,则产生的输出应为:
21
如果用
7 5 2
0 6 3
4 1 8
替换start.txt中的内容,则产生的输出应为:
-1
评分规则:我们将首先使用10组不同的start.txt和goal.txt进行测试,每个测试用例的运行时间在一台Intel Xeon 2.80GHz 4 CPU/6G 内存的Linux机器上应不超过10秒(内存使用不限制),否则该用例不得分;
每个选手的得分由两部分组成:
正确性得分(10秒内能产生正确结果的测试用例数量x10)和时间性能得分(10秒钟内产生这些正确结果的测试用例的平均运行毫秒数)。
正确性得分高的将始终比正确性得分低的排名在前,即使前者的平均运行时间比后者的要长;正确性得分相同的将按平均运行时间的快慢排列。
二、C++算法实现
1、第一种算法:全部遍历,直到找到。
![](https://img-my.csdn.net/uploads/201206/27/1340786989_2925.jpg)
1、1 结论
虽然结果正确,但耗时太长,与评分规则不合。
如上图所求,它采用全部遍历的方法,按顺序交换位置后往vector后面存储。
指针依次向vector后面推进,如果我们的结果出现在vector的尾部,那么运算的时间是相当长的。
1、2 代码实现
#pragma warning(disable:4786)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <cmath>
#include <map>
using namespace std;
const int N = 3; // 数组的边长
class CNineWomb
{
public:
CNineWomb(char cInData[N][N])
{
for(int i = 0; i < N; i++)
{
for(int j = 0; j < N; j++)
{
m_cData[i][j] = cInData[i][j];
}
}
m_iMoveCount = 0;
}
bool operator ==(const CNineWomb& inCNineWomb)
{
for(int i = 0;i < N; i++)
{
for(int j = 0; j < N; j++)
{
if(inCNineWomb.m_cData[i][j] != m_cData[i][j])
{
return false;
}
}
}
return true;
}
void CNineWomb_Printf()
{
for(int i = 0;i < N; i++)
{
for(int j = 0;j < N; j++)
{
cout << m_cData[i][j] << " ";
}
cout << endl;
}
}
public:
char m_cData[N][N];
int m_iMoveCount;
};
void main()
{
// 第一组数据 (无解)
//char cStartData[N][N] = { '1', '5', '8', '2', '6', ' ', '4', '7', '3'};
//char cGoalData[N][N] = { '1', '2', '3', '8', ' ', '4', '7', '6', '5'};
// 第二组数据 (无解)
//char cStartData[N][N] = { '1', '2', '3', '8', ' ', '4', '7', '6', '5'};
//char cGoalData[N][N] = { '1', '5', '8', '2', '6', ' ', '4', '7', '3'};
// 第三组数据 (有解)
//char cStartData[N][N] = { '8', ' ', '3', '2', '1', '4', '7', '6', '5'};
//char cGoalData[N][N] = { '1', '2', '3', '8', ' ', '4', '7', '6', '5'};
// 第四组数据 (有解)
char cStartData[N][N] = { '7', '8', '4', '3', '5', '6', '1', ' ', '2'};
char cGoalData[N][N] = { '1', '2', '3', '8', ' ', '4', '7', '6', '5'};
int iMoveCount = 1; // 移动步数
int iSpaceI = 0, iSpaceJ = 0; // 存储空格子所在的位置
vector<CNineWomb> VecCNineWomb; // 存储盘数据的容器
vector<CNineWomb>::iterator itVecCNineWomb;
vector<CNineWomb>::iterator iter; // 临时指针
map <int,int> mapSonFather; // 根据sun的m_iMoveCount找父亲的m_iMoveCount;
// 加载源数据与目标数据
CNineWomb NWStartData(cStartData);
NWStartData.m_iMoveCount = 0;
CNineWomb NWGoalData(cGoalData);
VecCNineWomb.reserve(200000); // 容器中预留200000个空间
VecCNineWomb.push_back(NWStartData);// 把源数据压到容器中
itVecCNineWomb = VecCNineWomb.begin();
mapSonFather.insert(make_pair(0,0));
while(1)
{
//cout << iMoveCount <<endl;
if(itVecCNineWomb == VecCNineWomb.end())
{
cout << "There is no trace from soure to aim!" << endl;
break;
}
// 拷贝数据
CNineWomb NWFather((*itVecCNineWomb).m_cData);
NWFather.m_iMoveCount = (*itVecCNineWomb).m_iMoveCount;
// 是否是目标,是则退出
if(NWFather == NWGoalData)
break;
// 找出空格位置
iSpaceI = 0, iSpaceJ = 0;
for(int i = 0; i < N; i++)
{
for(int j = 0; j < N; j++)
{
if(' ' == NWFather.m_cData[i][j])
{
iSpaceI = i;
iSpaceJ = j;
break;
}
}
}
// 往上移
if(N-1 > iSpaceI)
{
CNineWomb NWSunUp(NWFather.m_cData);
swap(NWSunUp.m_cData[iSpaceI+1][iSpaceJ], NWSunUp.m_cData[iSpaceI][iSpaceJ]);
// 如果sun为全新的节点,将sun插入VecCNineWomb表.
if((iter = find(VecCNineWomb.begin(), VecCNineWomb.end(), NWSunUp)) == VecCNineWomb.end())
{
NWSunUp.m_iMoveCount = iMoveCount;
mapSonFather.insert(make_pair(NWSunUp.m_iMoveCount, NWFather.m_iMoveCount));
iMoveCount++;
VecCNineWomb.push_back(NWSunUp);
// 是否是目标,是则退出
if(NWSunUp == NWGoalData)
{
itVecCNineWomb = VecCNineWomb.end() - 1;
break;
}
}
}
// 往下移
if(0< iSpaceI)
{
CNineWomb NWSunDown(NWFather.m_cData);
swap(NWSunDown.m_cData[iSpaceI-1][iSpaceJ], NWSunDown.m_cData[iSpaceI][iSpaceJ]);
if((iter = find(VecCNineWomb.begin(), VecCNineWomb.end(),NWSunDown)) == VecCNineWomb.end())
{
NWSunDown.m_iMoveCount = iMoveCount;
mapSonFather.insert(make_pair(NWSunDown.m_iMoveCount, NWFather.m_iMoveCount));
iMoveCount++;
VecCNineWomb.push_back(NWSunDown);
// 是否是目标,是则退出
if(NWSunDown == NWGoalData)
{
itVecCNineWomb = VecCNineWomb.end() - 1;
break;
}
}
}
// 往左移
if(N-1 > iSpaceJ)
{
CNineWomb NWSunLeft(NWFather.m_cData);
swap(NWSunLeft.m_cData[iSpaceI][iSpaceJ+1], NWSunLeft.m_cData[iSpaceI][iSpaceJ]);
if((iter = find(VecCNineWomb.begin(), VecCNineWomb.end(), NWSunLeft)) == VecCNineWomb.end())
{
NWSunLeft.m_iMoveCount = iMoveCount;
mapSonFather.insert(make_pair(NWSunLeft.m_iMoveCount, NWFather.m_iMoveCount));
iMoveCount++;
VecCNineWomb.push_back(NWSunLeft);
// 是否是目标,是则退出
if(NWSunLeft == NWGoalData)
{
itVecCNineWomb = VecCNineWomb.end() - 1;
break;
}
}
}
// 往右移
if(0 < iSpaceJ)
{
CNineWomb NWSunRight(NWFather.m_cData);
swap(NWSunRight.m_cData[iSpaceI][iSpaceJ-1], NWSunRight.m_cData[iSpaceI][iSpaceJ]);
if((iter = find(VecCNineWomb.begin(), VecCNineWomb.end(), NWSunRight)) == VecCNineWomb.end())
{
NWSunRight.m_iMoveCount = iMoveCount;
mapSonFather.insert(make_pair(NWSunRight.m_iMoveCount, NWFather.m_iMoveCount));
iMoveCount++;
VecCNineWomb.push_back(NWSunRight);
// 是否是目标,是则退出
if(NWSunRight == NWGoalData)
{
itVecCNineWomb = VecCNineWomb.end() - 1;
break;
}
}
}
itVecCNineWomb++;
} // end of while(1)
cout << iMoveCount << endl;
if(itVecCNineWomb != VecCNineWomb.end()) // 找到路径
{
// 把与目标相匹配的CNineWomb取出来
CNineWomb NWSunMatch((*(itVecCNineWomb)).m_cData);
NWSunMatch.m_iMoveCount = (*(itVecCNineWomb)).m_iMoveCount;
int x = NWSunMatch.m_iMoveCount;
int iIndex = 0;
cout << "NWSunMached:" << endl;
NWSunMatch.CNineWomb_Printf();
// 打印出过程
cout << "Process:" << endl;
while(0 != x)
{
iter = VecCNineWomb.begin();
while(iter != VecCNineWomb.end())
{
if((*iter).m_iMoveCount == mapSonFather[x])
break;
else
iter++;
}
CNineWomb sun((*iter).m_cData);
sun.m_iMoveCount = (*iter).m_iMoveCount;
x = sun.m_iMoveCount;
cout << "========================"<< iIndex++ << "=============================" << endl;
sun.CNineWomb_Printf();
}
}
else // 打印所有的接点。
{
cout << "=====================================================" << "sorry! no found" << endl;
}
system("pause");
return ;
}
2、第二种算法:
它的算法测试不到2秒就产生了结果。
第二种算法把正在循环的元素放到lpListUsed容器中, 将 移动后的结果放到下次要循环的list容器lpListNextStep中,
当lpListUsed循环完后把lpListUsed与lpListNextStep指针调换, 接下来循环。
????? 疑问: 不知作者采用双list的作用是什么,我自认为采用一个list就可以解决问题。
2、1 结论:
每次循环的次数和总循环次数如下:
![](https://img-my.csdn.net/uploads/201207/02/1341200320_8416.jpg)
2、2 代码实现
// 法二
#include <iostream>
#include <list>
using std::cout;
using std::endl;
using std::cin;
using std::list;
bool bVisitTag[387420489]= {false}; //9^9用来表示其中的一种状态是否被访问过
//=========================================================================
//begin class Point define
//=========================================================================
class Point
{
public:
Point();
Point(const Point& rhs);
public:
bool operator==(const Point& rhs);
bool operator!=(const Point& rhs);
Point& operator= (const Point& rhs);
public:
int m_iRow; //
int m_iCol;
};
Point::Point(){}
Point::Point(const Point& rhs)
{
m_iRow = rhs.m_iRow;
m_iCol = rhs.m_iCol;
}
bool Point::operator!=(const Point& rhs){
return !operator==(rhs);
}
bool Point::operator==(const Point& rhs){
return (m_iRow== rhs.m_iRow)&&(m_iCol== rhs.m_iCol);
}
Point& Point::operator= (const Point& rhs){
m_iRow = rhs.m_iRow;
m_iCol = rhs.m_iCol;
return *this;
}
//=========================================================================
//end of class Point define
//=========================================================================
//=========================================================================
//begin class CState define
//=========================================================================
class CState
{ //存储9宫的状态
public:
CState(const CState& rhs);
CState();
public:
CState& operator=(const CState& rhs);
bool operator==(const CState& rhs);
public:
int vValue[3][3];
Point m_Center; //我们设0的位置是Center
};
CState::CState(const CState& rhs):m_Center(rhs.m_Center)
{
for(int iRow= 0; iRow< 3; ++iRow)
{
for(int iCol= 0; iCol< 3; ++iCol)
vValue[iRow][iCol] = rhs.vValue[iRow][iCol];
}
}
CState::CState(){}
CState& CState::operator= (const CState& rhs)
{
m_Center = rhs.m_Center;
for(int iRow= 0; iRow< 3; ++iRow){
for(int iCol= 0; iCol< 3; ++iCol)
vValue[iRow][iCol] = rhs.vValue[iRow][iCol];
}
return *this;
}
bool CState::operator==(const CState& rhs)
{
if(m_Center != rhs.m_Center)
return false;
for(int iRow= 0; iRow< 3; ++iRow)
{
for(int iCol= 0; iCol< 3; ++iCol)
if(vValue[iRow][iCol] != rhs.vValue[iRow][iCol])
return false;
}
return true;
}
//=========================================================================
//end of class CState define
//=========================================================================
CState StateBegin, StateEnd; //开始和结束状态
int Search();
int StateToInt(const CState& state);//编码
CState IntToState(int iState); //解码
int main()
{
StateBegin.vValue[0][0] = 7;
StateBegin.vValue[0][1] = 8;
StateBegin.vValue[0][2] = 4;
StateBegin.vValue[1][0] = 3;
StateBegin.vValue[1][1] = 5;
StateBegin.vValue[1][2] = 6;
StateBegin.vValue[2][0] = 1;
StateBegin.vValue[2][1] = 0;
StateBegin.vValue[2][2] = 2;
StateBegin.m_Center.m_iRow= 2;
StateBegin.m_Center.m_iCol= 1;
StateEnd.vValue[0][0] = 1;
StateEnd.vValue[0][1] = 2;
StateEnd.vValue[0][2] = 3;
StateEnd.vValue[1][0] = 8;
StateEnd.vValue[1][1] = 0;
StateEnd.vValue[1][2] = 4;
StateEnd.vValue[2][0] = 7;
StateEnd.vValue[2][1] = 6;
StateEnd.vValue[2][2] = 5;
StateEnd.m_Center.m_iRow= 1;
StateEnd.m_Center.m_iCol= 1;
// for(int iRow= 0; iRow< 3; ++iRow)
// {
// for(int iCol= 0; iCol< 3; ++iCol)
// {
// cin>>StateBegin.vValue[iRow][iCol];
// if(StateBegin.vValue[iRow][iCol]== 0)
// {
// StateBegin.m_Center.m_iRow= iRow;
// StateBegin.m_Center.m_iCol= iCol;
// }
// }
// }
//
// for(int iRow= 0; iRow< 3; ++iRow)
// {
// for(int iCol= 0; iCol< 3; ++iCol)
// {
// cin>>StateEnd.vValue[iRow][iCol];
// if(StateEnd.vValue[iRow][iCol]== 0)
// {
// StateEnd.m_Center.m_iRow= iRow;
// StateEnd.m_Center.m_iCol= iCol;
// }
// }
// }
cout<<Search()<<endl;
system("pause");
}
int StateToInt(const CState& state)
{
//编码
int iValue = 0;
for(int iRow= 2; iRow>= 0; --iRow){
for(int iCol=2; iCol>= 0; --iCol){
iValue*= 9;
iValue+= state.vValue[iRow][iCol];
}
}
return iValue;
}
CState IntToState(int iState)
{
//解码
CState state;
for(int iRow= 0; iRow< 3; ++iRow)
{
for(int iCol= 0; iCol< 3; ++iCol)
{
state.vValue[iRow][iCol] = iState%9;
iState/= 9;
if(state.vValue[iRow][iCol]== 0)
{
state.m_Center.m_iRow = iRow;
state.m_Center.m_iCol = iCol;
}
}
}
return state;
}
//=========================================================================
//BFS search
//=========================================================================
int Search()
{
list<CState> buffer1, buffer2;
int iStep= 0;
list<CState> *lpListUsed; //存放当前这一步
list<CState> *lpListNextStep; //存放下一步
if(StateBegin== StateEnd)
return 0; //直接到达的话就是0了
lpListUsed = &buffer1;
lpListNextStep = &buffer2;
lpListUsed->push_back(StateBegin);
int iPosition = StateToInt(StateBegin);
bVisitTag[iPosition] = true;
//bVisitTag[StateToInt(StateBegin)]= true; //打标记
int iMoveCountEvery = 1;// 每次移动步数
int iMoveCountAll = 1; // 总移动步数
while(!lpListUsed->empty())
{
CState StateNow;
CState StateNext;
++iStep;
cout << iMoveCountEvery << endl;
iMoveCountEvery = 1;
while(!lpListUsed->empty())
{
iMoveCountEvery++;
iMoveCountAll++;
StateNow= lpListUsed->front();
lpListUsed->pop_front();
if(StateNow == StateEnd)
{
cout << "All Count:"<< iMoveCountEvery << endl;
return iStep-1;
}
if(StateNow.m_Center.m_iRow-1>= 0)
{
StateNext = StateNow;
--StateNext.m_Center.m_iRow;
StateNext.vValue[StateNow.m_Center.m_iRow][StateNow.m_Center.m_iCol] =StateNow.vValue[StateNext.m_Center.m_iRow][StateNext.m_Center.m_iCol];
StateNext.vValue[StateNext.m_Center.m_iRow][StateNext.m_Center.m_iCol] = 0;
int iPosition = StateToInt(StateNext);
if(!bVisitTag[iPosition])
{
bVisitTag[iPosition]= true;
lpListNextStep->push_back(StateNext);
}
}
if(StateNow.m_Center.m_iRow+1< 3)
{
StateNext = StateNow;
++StateNext.m_Center.m_iRow;
StateNext.vValue[StateNow.m_Center.m_iRow][StateNow.m_Center.m_iCol] =StateNow.vValue[StateNext.m_Center.m_iRow][StateNext.m_Center.m_iCol];
StateNext.vValue[StateNext.m_Center.m_iRow][StateNext.m_Center.m_iCol] = 0;
int iPosition = StateToInt(StateNext);
if(!bVisitTag[iPosition])
{
bVisitTag[iPosition]= true;
lpListNextStep->push_back(StateNext);
}
}
if(StateNow.m_Center.m_iCol-1>= 0)
{
StateNext = StateNow;
--StateNext.m_Center.m_iCol;
StateNext.vValue[StateNow.m_Center.m_iRow][StateNow.m_Center.m_iCol] =StateNow.vValue[StateNext.m_Center.m_iRow][StateNext.m_Center.m_iCol];
StateNext.vValue[StateNext.m_Center.m_iRow][StateNext.m_Center.m_iCol] = 0;
int iPosition = StateToInt(StateNext);
if(!bVisitTag[iPosition])
{
bVisitTag[iPosition]= true;
lpListNextStep->push_back(StateNext);
}
}
if(StateNow.m_Center.m_iCol+1< 3)
{
StateNext = StateNow;
++StateNext.m_Center.m_iCol;
StateNext.vValue[StateNow.m_Center.m_iRow][StateNow.m_Center.m_iCol] =StateNow.vValue[StateNext.m_Center.m_iRow][StateNext.m_Center.m_iCol];
StateNext.vValue[StateNext.m_Center.m_iRow][StateNext.m_Center.m_iCol] = 0;
int iPosition = StateToInt(StateNext);
if(!bVisitTag[iPosition])
{
bVisitTag[iPosition]= true;
lpListNextStep->push_back(StateNext);
}
}
}
std::swap(lpListUsed, lpListNextStep);
}
return -1;
}
2、3 与第一种算法的比较
可以说wind_2008_06_29 的算法是对我的算法的以下几个方面进行优化:
(1) 在类CState中添加存储0位置的信息Point m_Center;,不用每次都在while循环中去算它的位置。
(2) 从上图可知,list中存储的结果最多可达到20823个,如果在这么大的数据内判断将插入的一个元素是否在这个lsit里。
将花费很长时间,我就采用这样的方法( if((iter = find(VecCNineWomb.begin(), VecCNineWomb.end(), NWSunUp)) == VecCNineWomb.end()) ),
而第二种方法却已经判断过的元素pop掉了( lpListUsed->pop_front(); ),这样在查找元素,插入元素时就省去了时间。
从上图可知,最多也就在14714个元素的list进行操作而已。
由于把元素pop掉了,此时再用find来查找是现在的元素是否在以前出现过显然已经不完全了,所以得自己维护一个变量来记录元素出现过的情况。
巧妙地维护一个全局变量bVisitTag,并用:
int StateToInt(const CState& state);//编码
CState IntToState(int iState); //解码
两个API函数 来实现.