#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 这个要跑很久...得要去优化优化. */