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

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

原文链接: https://www.cnblogs.com/steady/archive/2011/01/18/1938642.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

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

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/20233

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月7日 下午9:41
下一篇 2023年2月7日 下午9:41

相关推荐