#include <iostream>
#include <cmath>
#include <stack>
using namespace std;
/*
*八数码,未优化版
*总结
*如果用vector来保存的话,路径回溯有问题
*用map实现链表的随机访问或者直接在map上做
*用hash计算后在数组上做.
*都可以以logn的时间减小.这个只是自己玩玩.所以没弄那么多.
*以前看书的时候,说给链表加个头结点会很方便操作,一直没放在心上,我觉得自己也能写的很好.
*结果悲剧了...没有了头结点后的升序插入,相当麻烦.还是要尊重课本啊,都是牛人的总结
*/
struct Board{
int tile[3][3];
int r,c;
friend bool operator==(const Board &b1, const Board &b2)
{
for(int r = 0; r < 3; r++)
for(int c = 0; c < 3; c++)
if(b1.tile[r][c] != b2.tile[r][c])
return false;
return true;
}
void Print()
{
cout<<"0在: "<<r + 1 << " "<<c + 1<<"位置"<<endl;
for(int r = 0; r < 3; r++)
{
for(int c = 0; c < 3; c++)
cout<<tile[r][c]<<"\t";
cout<<endl;
}
}
};
//0放在22的位置
const int finalPos[9][2] = {{2, 2}, {0, 0}, {0, 1}, {0,2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}};
const Board finalboard = {{1, 2, 3, 4, 5, 6, 7, 8, 0}, 2, 2};
//逆时针,右上左下
const int dir[4][2] = {{0, 1}, {-1, 0}, {0, -1}, {1, 0}};
Board initboard;//初始布局
//搜索结点
struct Node
{
Node(){Dir = 100;}
Node(const Board &board);
void Print();
Board board;
int f,g,h;//启发搜索信息
int Dir;//父亲节点移动的方向,产生该结点
Node *next;
Node *father;//父节点
};
Node::Node(const Board &board)
{
this->board = board;
Dir = 100;
f = g = h = 0;
next = father = NULL;
}
void Node::Print()
{
cout<<"将0";
switch(Dir)
{
case 0:
cout<<"向右";
break;
case 1:
cout<<"向上";
break;
case 2:
cout<<"向左";
break;
case 3:
cout<<"向下";
break;
}
cout<<"移动后"<<endl;
board.Print();
}
//模拟优先队列的链表
class List{
private:
Node *head;
int len;
public:
List()
{
head = new Node(finalboard);
head->next = NULL;
len = 0;
}
~List()
{
Node *p;
while(head)//非空
{
p = head->next;
free(head);
head = p;
}
len = 0;
}
//如果找到布局一样的结点,放回他前一个结点的指针
bool Find(const Board &board, Node* &pPrev);
/*模拟的是优先队列,所以push也是按照<插入,并且只能在队列里面不存在s的时候才能插入
我考虑的这个状态里面是包含f,g,h的,所以虽然布局一样(h一样),但是他们的状态是不一样的.
两个布局一样的状态,是不同深度的两个结点,深度大的那个结点g大,于是那个结点的f也大
插入的时候,在这里要考虑两种种情况,(1)如果那个节点还不存在在open表中,那么直接按照f插入.
(2)如果是已经在open里面的,因为布局是一样的,所以h是一样的,那么如果f小的话,g也是小的.
所以还是按照f来判断.
第二种情况,不需要插入,通过Find函数找到位置来修改(g,f都变小)就可以,
然后再重新排序,排序的时候因为整个链表已经是有序的,所以只要往前找到适当的位置就好
这个Push是用来插入的*/
Node* Push(const Node &n);//返回插入位置
//重新排序这个操作只在open表中进行,prev指向要重新安排位置的结点的前一个结点
void Refresh(Node* prev);
//判断队列是否为空
bool IsEmpty(){return !head->next;}
Node Top();
void Pop();
//插入到链表的开头,不排序
void Push_Head(const Node &n);
Node* Get_FirstPointer();
};
Node List::Top()
{
if(!IsEmpty())
{
return *(head->next);
}
}
void List::Pop()
{
if(!IsEmpty())
{
Node *p = head->next->next;
delete head->next;
head->next = p;
len--;
}
}
//如果找到,返回前一个结点的指针
bool List::Find(const Board &board, Node* &pPrev)
{
Node *p = head;
while(p->next)
{
//找到,只要布局一样就可以
if(p->next->board == board)
{
pPrev = p;
return true;
}
p = p->next;
}
return false;
}
Node* List::Push(const Node &n)//返回插入位置
{
Node *p, *q, *r;
r = new Node(n);
r->next = NULL;//构造函数会拷贝next指针,先赋值为NULL
if(!head->next)//空链表
{
head->next = r;
}
else
{
p = head; q = p->next;
while(q->next && q->f < n.f)//查找插入位置
{
p = q;
q = q->next;
}
//这样写的优点是只有一个元素的时候这个判断也成立
if(q->f >= n.f)//pq之间
{
p->next = r;
r->next = q;
}
else
{
q->next = r;
}
}
len++;
return r;
}
void List::Refresh(Node* prev)
{
//先取出,再插入
Node *inserter = prev->next;
prev->next = inserter->next;
Push(*inserter);
}
void List::Push_Head(const Node &n)
{
Node *p = new Node(n);
p->next = head->next;
head->next = p;
}
Node* List::Get_FirstPointer()
{
return head->next;
}
/*
八数码问题的一个状态实际上是0~9的一个排列,
排列有奇排列和偶排列两类,从奇排列不能转化成偶排列或相反。
如果一个数字0~8的随机排列871526340,用F(X)表示数字X前面比它小的数的个数,全部数字的F(X)之和为Y=∑(F(X)),
如果Y为奇数则称原数字的排列是奇排列,如果Y为偶数则称原数字的排列是偶排列。
目标状态是f(123456780) = 28
*/
bool Solveable()
{
int i, j, array[9], k = 0, sum = 0;
for(i = 0; i < 3; i++)
for(j = 0; j < 3; j++)
array[k++] = initboard.tile[i][j];
for(i = 8; i >= 0; i--)
for(j = i - 1; j >= 0; j--)
if(array[j] < array[i])
sum++;
if(sum % 2)
return true;
else
return false;
}
//启发函数h不计算0的曼哈顿距离
int hFun(Board &board)
{
int r, c, tile, sum = 0;
for(r = 0; r < 3; r++)
for(c = 0; c < 3; c++)
{
tile = board.tile[r][c];
if(tile != 0)
sum += abs(r - finalPos[tile][0]) + abs(c - finalPos[tile][1]);
}
return sum;
}
List open_table, closed_table, save_table;
Node finalNode;
bool Astar()
{
Node node, nnode, *pn;
node = initboard;
node.h = hFun(node.board); node.f = node.g + node.h;
open_table.Push(node);
while(!open_table.IsEmpty())
{
node = open_table.Top();
open_table.Pop();
save_table.Push_Head(node);//保存被访问过的结点,打印路径用
if(finalboard == node.board)//到达目标布局
{
finalNode = node;
return true;
}
int r, c, nr, nc;
for(int d = 0; d < 4; d++)
{
if(abs(d - node.Dir) == 2)//会回到父亲结点的棋盘布局
continue;
r = node.board.r; c = node.board.c;
nr = r + dir[d][0]; nc = c + dir[d][1];
if(nr < 0 || nr > 3 || nc < 0 || nc > 3)
continue;
nnode = node;
nnode.Dir = d;//该状态下的移动方向
int temp = node.board.tile[nr][nc];//移动
nnode.board.tile[r][c] = temp;
nnode.board.tile[nr][nc] = 0;
nnode.board.r = nr; nnode.board.c = nc;
nnode.g++;//深度+1
nnode.h = hFun(nnode.board);//计算新的h
nnode.f = nnode.g + nnode.h;
if(open_table.Find(nnode.board, pn))
{
if(nnode.f < pn->next->f)//新结点的估价值小于OPEN表中的估价值
{
pn->next->father = save_table.Get_FirstPointer();//重新设置父节点
pn->next->g = nnode.g; pn->next->f = nnode.f;//不需要更新h,更新g,f就好
open_table.Refresh(pn);
}
}
else if(closed_table.Find(nnode.board, pn))
{
if(nnode.f < pn->f)//X的估价值小于CLOSE表的估价值
{
nnode.father = pn->next->father = save_table.Get_FirstPointer();
pn->next->g = nnode.g; pn->next->f = nnode.f;//更新CLOSE表中的估价值;因为下次可能还会遇到同样布局
open_table.Push(nnode);
}
}
else
{
nnode.father = save_table.Get_FirstPointer();
open_table.Push(nnode);
}
closed_table.Push(node);
}
}
return false;
}
void Trace()
{
stack<Node*> s;
Node *p = &finalNode;
int i = 0;
while(p)
{
i++;
s.push(p);
p = p->father;
}
cout<<"共"<<i<<"步移动."<<endl;
i = 0;
while(!s.empty())
{
i++;
p = s.top();
s.pop();
cout<<"第"<<i<<"步移动方案: "<<endl;
p->Print();
cout<<endl;
}
}
void Init()
{
int tile;
for(int r = 0; r < 3; r++)
for(int c = 0; c < 3; c++)
{
cin>>tile;
if(!tile)
{
initboard.r = r;
initboard.c = c;
}
initboard.tile[r][c] = tile;
}
}
int main()
{
freopen("in.txt", "r", stdin);
Init();
if(Solveable())
{
if(Astar())
{
cout<<"ok"<<endl;
Trace();
}
}
else
cout<<"unresolveable"<<endl;
return 0;
}
/*
数据输入格式:
1 4 6
0 2 3
7 5 8
这个要跑很久...得要去优化优化.
*/