分支限界算法 之 A*算法(启发式搜索算法)---九宫重排游戏(也称八数码问题)

3*3的棋盘中摆放了0~8这9个数字,每次只能允许0与其上下左右相邻的4个位置进行交换,
使得棋盘最终达到一种目标状态,要求输出最少交换次数的过程。例如

   起始状态    目标状态
 1 2 3        1 2 3 
 6 0 4    =>  8 0 4
 8 7 5        7 6 5   

最少交换次数的变化过程如下

   1 2 3        1 2 3     1 2 3     1 2 3     1 2 3 
   6 0 4    =>  0 6 4  => 8 6 4  => 8 6 4  => 8 0 4
   8 7 5        8 7 5     0 7 5     7 0 5     7 6 5  

注:其中0也可以理解为空格,上下左右4格相邻格的数字可以移动到空格中。
要求:移动次数最少,并且输出每一步移动后棋盘的状态,请写出算法程序。

分析:
1.所有可能的状态数为9!=362880,
2.解空间树为4叉树(实际剪枝后多数节点的度为2和3),
深度可以达到100以上,回溯算法较难控制运算量

A*算法思路:
1.创建解空间树的根节点,并进入优先级队列Q
//Q中存储了搜索过程中的所有叶子结点

2.while(Q非空) {
队头元素e(节点)出队列, if(e达到问题解) { 输出解空间树中 e->…->根节点的路径信息。
break;//只找一个解时,则可以立即退出while循环 } else {
生成e的所有儿子节点,将通过剪枝函数的节点 进入队列Q; }
}
//从队列中出来的节点,即是解空间树中的非叶子节点,搜索过程中整个 //解空间树不能释放,否则会导致无法输出从根->目标节点的路径信息, //所以以下程序用向量v存储了解空间树中的所有节点。
//其中剪枝函数(也可叫限界函数)的基本控制逻辑如下:

*/

一个解,stl


#include
#include
#include
#include
#include
#include
using namespace std; 

struct node{
	int   step,len;//从根到当前节点状态走了step步,离目标至少len步 
	POINT d[9];//当前状态下数字0~9的行列号 
	int   chess[3][3];//当前状态对应的矩阵 
	struct node *parent;//指向当前节点的父节点	
};
struct cmp{ //自定义比较运算符 
		bool operator()(node *a,node *b)
		{
			return a->step+a->len > b->step+b->len;//step+len小的优先排队头 
		}
}; 

vector<node*>v;//v存树中所有节点的地址 
priority_queue<node*,vector<node*>,cmp>Q;//Q存树中所有叶子节点 
//Q的元素类型是指针型,并且确定优先级的比较函数cmp必须自定义实现。
 
POINT obj[9]={{1,1},{0,0},{0,1},{0,2},{1,2},{2,2},{2,1},{2,0},{1,0}},//目标状态 
      offset[4]={{-1,0},{0,1},{1,0},{0,-1}};//4种移动的坐标偏移值 
int  cnt=0;//解的移动步骤 

void output(node*t)
{	
	if(t->parent) output(t->parent);
	cout<<endl<<cnt++<<"步:"<<endl;
	for(int i=0;i<3;i++)
	{		
	    for(int j=0;j<3;j++)	
	       cout<<t->chess[i][j]<<" ";
	    cout<<endl;
    }
}

node* xianjie(node *p,int k)
{	
    node *q;
	int i=p->d[0].x+offset[k].x,j=p->d[0].y+offset[k].y,m;	
	if(i<0||i>2||j<0||j>2) return 0;
	q=new node(*p);
	m=p->chess[i][j]; 
	q->d[0].x =i; 
	q->d[0].y =j;
	q->d[m].x =p->d[0].x; 
	q->d[m].y =p->d[0].y; 			
	q->chess[p->d[0].x][p->d[0].y] = m;
	q->chess[i][j] = 0;
	q->step ++;
	q->len +=abs(p->d[0].x-obj[m].x)+abs(p->d[0].y-obj[m].y)-
	         (abs(i-obj[m].x)+abs(j-obj[m].y)); 
	q->parent = p;
	for(m=0;m<v.size();m++)
	{
	  for(i=0;i<9;i++)
		if(q->d[i].x!=v[m]->d[i].x || q->d[i].y!=v[m]->d[i].y) break;
	  if(i==9) break;
	}
	if(m<v.size())//q节点已经存在 
	{ 
	   if(q->len+q->step < v[m]->len+v[m]->step) 
	   {
	   	    v[m]->step =  q->step;
		  	v[m]->len  =  q->len;
		  	v[m]->parent = p;
	   }
	   delete q; 
	   return 0;
	}
	else    return q;	
}

int isok(node* t)
{	
	for(int i=0;i<9;i++) 
	   if(t->d[i].x!=obj[i].x || t->d[i].y!=obj[i].y)  return 0;
	return 1;
}

void Init()//创建并初始化根节点 
{
	node *t=new node;
	int i,j,num;
	cout<<"初始数据:\n";
	t->len = 0;
	for(i=0;i<3;i++)
	  for(j=0;j<3;j++)
	  {	  	 
	     cin>>num;
		 t->chess[i][j]=num;
		 t->d[num].x =i;
		 t->d[num].y =j;
		 if(num)
		    t->len += abs(obj[num].x-i)+abs(obj[num].y-j);		 
	  }
	t->step = 0;
	t->parent =0;
	Q.push(t);//根节点t进入队列Q 
	v.push_back(t);//根节点t进入向量v 
}

int main()
{
	node *p,*q;	
	Init();//初始化	
	while(!Q.empty())
	{
	    p=Q.top();//获取队头节点	
		Q.pop();//删除队头节点 
		if(isok(p)) 
		{
		   output(p);//输出从根到p节点的变化过程 
		   break;
		}
		for(int i=0;i<4;i++)//穷举p产生的所有儿子节点 
		   if(q=xianjie(p,i))//有效的节点会在xianjie中计算其评估值h(q)  
		   {                 //存放在q->len中 
		      Q.push(q);//以q->step+q->len小优先插入优先级队列Q 
			  v.push_back(q);  
		   } 	    
	}
	cout<<"\n访问节点数:"<<v.size();
	getch();
	for(int i=0;i<v.size();i++)
	        delete v[i];
	return 0;
}

多个解,stl

#include
#include
#include
#include
#include
#include
#include
using namespace std; 

struct node{
	int   step,len;//从根到当前节点状态走了step步,离目标至少len步 
	POINT d[9];//当前状态下数字0~9的行列号 
	int   chess[3][3];//当前状态对应的矩阵 
	struct node *parent;//指向当前节点的父节点	
};
struct cmp{ //自定义比较运算符 
		bool operator()(node *a,node *b)
		{
			return a->step+a->len > b->step+b->len;//step+len小的优先排队头 
		}
}; 

vector<node*>v;//存树中所有节点的地址 
priority_queue<node*,vector<node*>,cmp>Q;//存树中所有叶子节点 
//Q的元素类型是指针型,并且确定优先级的比较函数cmp必须自定义实现。
 
POINT obj[9]={{1,1},{0,0},{0,1},{0,2},{1,2},{2,2},{2,1},{2,0},{1,0}},//目标状态 
      offset[4]={{-1,0},{0,1},{1,0},{0,-1}};//4种移动的坐标偏移值 
int  Min,c1,cnt=0;//解的移动步骤 

void output(node*t)
{	
	if(t->parent) output(t->parent);
	cout<<endl<<setw(3)<<cnt++<<"步:";
	for(int i=0;i<3;i++)
	{	
		if(i) cout<<"      ";
	    for(int j=0;j<3;j++)	
	       cout<<setw(2)<<t->chess[i][j];
		cout<<endl;		    
    }
}

node* xianjie(node *p, int k, int status)
{	
    node *q;
	int i=p->d[0].x+offset[k].x,j=p->d[0].y+offset[k].y,m;	
	if(i<0||i>2||j<0||j>2) return 0;//q状态中0的行列号(i,j) 
	q=new node(*p);
	m=p->chess[i][j]; 
	q->d[0].x =i; //p变到q状态,只有数字0和m的位置有变化 
	q->d[0].y =j;
	q->d[m].x =p->d[0].x; 
	q->d[m].y=p->d[0].y; 			
	q->chess[p->d[0].x][p->d[0].y] = m;
	q->chess[i][j] = 0;
	q->step ++;
	q->len +=abs(p->d[0].x-obj[m].x)+abs(p->d[0].y-obj[m].y)-
	         (abs(i-obj[m].x)+abs(j-obj[m].y)); 
	q->parent = p;
	if(status==0)
	{
		for(m=0;m<v.size();m++)
		{
		  for(i=0;i<9;i++)
			if(q->d[i].x!=v[m]->d[i].x || q->d[i].y!=v[m]->d[i].y) break;
		  if(i==9) break;
		}
		if(m<v.size())//q节点已经存在 
	    { 
	       if(q->len+q->step < v[m]->len+v[m]->step) 
	       {
	   	       v[m]->step =  q->step;
		  	   v[m]->len  =  q->len;
		  	   v[m]->parent = p;
	       }
	       delete q; 
	       return 0;
	    }//找一个解的剪枝逻辑
	}
	else
	{
		node *p1=p;
	    for( ;p1;p1=p1->parent)
		{
		  for(i=0;i<9;i++)
			if(q->d[i].x!=p1->d[i].x || q->d[i].y!=p1->d[i].y) break;
		  if(i==9) break;
		}
		if(p1 || q->step+q->len>Min) { delete q; return 0;}//找所有解的剪枝逻辑
	}	
	return q;	
}

int isok(node* t)
{	
	for(int i=0;i<9;i++) 
	   if(t->d[i].x!=obj[i].x || t->d[i].y!=obj[i].y)  return 0;
	return 1;
}

void Init()//创建并初始化根节点 
{
	node *t=new node;
	int i,j,num;
	cout<<"初始数据:\n";
	t->len = 0;
	for(i=0;i<3;i++)
	  for(j=0;j<3;j++)
	  {	  	 
	     cin>>num;
		 t->chess[i][j]=num;
		 t->d[num].x =i;
		 t->d[num].y =j;
		 if(num) 
			 t->len += abs(obj[num].x-i)+abs(obj[num].y-j);		 
	  }
	t->step = 0;
	t->parent =0;
	Q.push(t);//根节点t进入队列Q 
	v.push_back(t);//根节点t进入向量v 
}

void BFS()
{
	node *p,*q;	
	Init();//初始化	
	while(!Q.empty())
	{
	    p=Q.top();//获取队头节点	
		Q.pop();//删除队头节点 
		if(p->len ==0) //isok(p)
		{
		   Min=p->step;
		   output(p);//输出从根到p节点的变化过程 
		   break;
		}
		for(int i=0;i<4;i++)//穷举p产生的所有儿子节点 
		   if(q=xianjie(p,i,0))//有效的节点会在xianjie中计算其评估值h(x)  
		   {                 // 
		      Q.push(q);  
			  v.push_back(q);  
		   } 	    
	}
	cout<<"\n访问节点数:"<<v.size();
	getch();
	while(!Q.empty())  	 Q.pop();
	p=v[0];//根节点保留,作为BFS1()计算的初始数据
	for(int i=1;i<v.size();i++)
	        delete v[i];	
	v.clear() ;	
	v.push_back(p);  Q.push(p);   
}

void BFS1()
{
	node *p,*q;	
	//根节点在BFS中最后两个语句已经 初始化
	cout<<"\n其它最优解:";
	if(Min==0) return ;//如果无解,直接结束	
	while(!Q.empty())
	{
		p=Q.top() ;
		Q.pop() ;
		if(p->len==0 && p->step==Min) //isok(p)
		{
			cout<<"\n\n解"<<++c1;
			cnt=0;
			output(p);
			getch();//输出一个解,暂停 
		}
		else if(p->step + p->len <= Min)
		  for(int i=0;i<4;i++)//穷举p产生的所有儿子节点 
		    if((q=xianjie(p,i,1))!=NULL)
		    {                   
		       Q.push(q);  
			   v.push_back(q);  
		    } 
	}
	cout<<"\n访问节点数:"<<v.size();
	getch();
	while(Q.size())  Q.pop();
	for(int i=0;i<v.size();i++)
	        delete v[i];
	v.clear() ;  
} 
int main()
{
	BFS();//找一个最优解的广度优先搜索 
	BFS1();//找所有最优解的广度优先搜索	
	return 0;
}

你可能感兴趣的:(算法,算法,c++,数据结构)