八数码(未优化版本,跑的时间很长很长)

#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

这个要跑很久...得要去优化优化.

*/

你可能感兴趣的:(优化)