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


    #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
    这个要跑很久...得要去优化优化.
    */
    
  • 相关阅读:
    运算符重载
    责任链模式
    MFC一些常见面试问题
    浅拷贝&深拷贝
    下雨的效果
    本地时间使用与倒计时
    钟表效果
    一种水纹波浪效果
    一个相册效果
    在Flash中管理鼠标右键
  • 原文地址:https://www.cnblogs.com/steady/p/1938642.html
Copyright © 2020-2023  润新知