A*算法解决八数码问题

问题描述

1.1什么是八数码问题

八数码游戏包括一个33的棋盘,棋盘上摆放着8个数字的棋子,留下一个空位。与空位相邻的棋子可以滑动到空位中。游戏的目的是要达到一个特定的目标状态。标注的形式化如下:

 

1

2

3

4

5

6

7

8

1.2问题的搜索形式描述

状态:状态描述了8个棋子和空位在棋盘的9个方格上的分布。

初始状态:任何状态都可以被指定为初始状态。

操作符:用来产生4个行动(上下左右移动)。

目标测试:用来检测状态是否能匹配上图的目标布局。

路径费用函数:每一步的费用为1,因此整个路径的费用是路径中的步数。

现在任意给定一个初始状态,要求找到一种搜索策略,用尽可能少的步数得到上图的目标状态。

1.3解决方案介绍

1.3.1 算法思想

估价函数是搜索特性的一种数学表示,是指从问题树根节点到达目标节点所要耗费的全部代价的一种估算,记为f(n)。估价函数通常由两部分组成,其数学表达式为

f(n)=g(n)+h(n)

其中f(n) 是节点n从初始点到目标点的估价函数,g(n) 是在状态空间中从初始节点到n节点的实际代价,h(n)是从n到目标节点最佳路径的估计代价。保证找到最短路径(最优解)的条件,关键在于估价函数h(n)的选取。估价值h(n)<= n到目标节点的距离实际值,这种情况下,搜索的点数多,搜索范围大,效率低。但能得到最优解。如果估价值>实际值搜索的点数少,搜索范围小,效率高,但不能保证得到最优解。

搜索中利用启发式信息,对当前未扩展结点根据设定的估价函数值选取离目标最近的结点进行扩展,从而缩小搜索空间,更快的得到最优解,提高效率。

1.3.2 启发函数

     进一步考虑当前结点与目标结点的距离信息,令启发函数h ( n )为当前8个数字位与目标结点对应数字位距离和(不考虑中间路径),且对于目标状态有 h ( t ) = 0,对于结点mm的子结点) 有h ( m ) – h ( n ) <= 1 = Cost ( m, n ) 满足单调限制条件。

2算法介绍

2.1 A*算法的一般介绍

A*A-Star)算法是一种静态路网中求解最短路最有效的方法。

A star算法在静态路网中的应用

对于几何路网来说,可以取两节点间欧几理德距离(直线距离)做为估价值,即f=g(n)+sqrt((dx-nx)*(dx-nx)+(dy-ny)*(dy-ny));这样估价函数fg值一定的情况下,会或多或少的受估价值h的制约,节点距目标点近,h值小,f值相对就小,能保证最短路的搜索向终点的方向进行。明显优于盲目搜索策略。

2.2算法伪代码

  创建两个表,OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。算起点的估价值,将起点放入OPEN

  while(OPEN!=NULL)

  {

  从OPEN表中取估价值f最小的节点n;

if(n节点==目标节点)

{break;}

  for(当前节点的每个子节点X)

   {

  算X的估价值;

  if(X in OPEN)

    {

if( X的估价值小于OPEN表的估价值 )

{n设置为X的父亲;

  更新OPEN表中的估价值; //取最小路径的估价值}

    }

if(X inCLOSE) 

{

if( X的估价值小于CLOSE表的估价值 )

{n设置为X的父亲;

  更新CLOSE表中的估价值;

  把X节点放入OPEN //取最小路径的估价值}

    }

if(X not inboth)

{n设置为X的父亲;

  求X的估价值;

  并将X插入OPEN表中; //还没有排序}

   }//end for

  将n节点插入CLOSE表中;

  按照估价值将OPEN表中的节点排序; //实际上是比较OPEN表内节点f的大小,从最小路径的节点向下进行。

  }//end while(OPEN!=NULL)

保存路径,即 从终点开始,每个节点沿着父节点移动直至起点,这就是你的路径;


判断有无解问题:根据逆序数直接判断有无解,对于一个八数码,依次排列之后,每次是将空位和相邻位进行调换,研究后会发现,每次调换,逆序数增幅都为偶数,也就是不改变奇偶性,所以初始和目标状态的逆序数的奇偶性相同。

3算法实现

3.1实验环境与问题规模

对于8数码问题,每个结点有8个数字和一个空格,可以将空格看成0,那么一共有9个数字,32位的int可以表示2* 109 ,可以用一个整数表示一个结点对应的信息。计算一个整数中0(即空格)的位置比较耗时间,用一个整数存储当前结点0的位置,还要存储对应的 g , h 值以及该结点由哪个结点扩展来的信息。本实验用C++编写源程序,环境选用Visual Studio 2005

程序采用文本输入输出,输入文件为astar.inA*算法输出文件为astar.out,可以用记事本打开。输入格式为一个测试用例由两个中间由一空行隔开的8数码格局组成,输出为对应测试用例的走法路径及相关统计信息,程序假定输入数据符合要求,未做检查。

Astar.in:

2 0 3  //初态

1 8 4

7 6 5

 

1 2 3 // 终态

8 0 4

7 6 5

 

3.2数据结构

3.2.1 open表的数据结构表示
      考虑对open表的操作,每次需要得到所有待扩展结点中 值最小的那个结点,用堆进行实现,可以达到O ( log ( heapSize ) ) 时间复杂度。

3.2.2 closed表的数据结构表示
      closed表存储已扩展的结点间的扩展关系,主要用于输出路径。考虑结点扩展的操作,设待扩展的结点为m,由它扩展生成的结点为n1, n2, … 。结点m扩展完成后被放到closed表中,放入后它在closed表中位置不发生变化,可以将n1, n2, …的前驱结点置为mclosed表中的位置,当n1, n2, ..中有结点设为n1被扩展放入closed表时,n1的前驱刚好已经存储好。下面说明closed表中任意一个结点都存储有它的前驱结点的信息,考虑closed表中任意一个结点,如果它是初始结点,它没有前驱结点,如果不是根结点,扩展该结点时它的前驱结点已经记录。从而在closed表中形成扩展关系的树状结构。因为只需要前驱结点的下标位置,可以用数组实现,每个结点记录整数表示的8数码格局和它的前驱结点的下标,输出路径时,根据前驱结点形成到达根结点的链条,递归输出即可。

3.2.3 解决结点重复扩展问题
      对于一个结点有多种方式到达该结点,这样就可能多次将它加入open表中,而启发函数满足单调限制条件,后来达到该结点的路径不再是更优的,可以不予考虑。扩展某结点时先看该结点是否已经扩展过,如果扩展过则略过。实现的可以线形遍历closed表,但效率不高时间复杂度为O ( closedSize),考虑每个结点可以用一个整数标识,用二叉平衡查找树可以得到更好的时间复杂度O ( log (closedSize) ) ,程序中用基于红黑树思想的set实现。

3.3 实验结果

 

输入数据(0表示空格)

步数

扩展结点数

生成结点数

搜索用时(毫秒)

3 1 2 4 0 5 6 7 8

2

5

11

0

3 1 2 4 7 5 6 8 0

4

17

28

0

3 7 2 8 1 5 4 6 0

无解

 

 

 

1 2 3 4 5 6 7 8 0

22

7943

12019

2266

参考文献

王勋,凌云,费玉莲.2005.人工智能导论.北京:科学出版社

广树建,王钰淇.2008.新编C/C++程序设计教程.广州:华南理工大学出版社

王文杰,史忠植.2007.人工智能原理辅导与练习.北京:清华大学出版社出

附录—源代码及其注释

源代码及测试数据
/*
        算法:        A*
        是否最优解:是
        启发函数:   每一个数字位与目标中该数字位的距离,满足单调限制。说明:A*算法是启发式搜索算法,搜索时充分利用当前状态距目标距离远近的启发信息,选取当前未扩展结点中估价函数最小的进行扩展,生成结点数少,搜索空间较小,实现稍复杂,
        备注:       程序未对输入数据进行检查
*/

#pragma warning(disable:4786) 
#include <algorithm> 
#include <cstdio> 
#include <set> 
#include <utility> 
#include <ctime> 
#include <cassert> 
#include <cstring> 
#include <iostream>
using namespace std;

/*item记录搜索空间中一个结点         
state 记录用整数形式表示的8数码格局          
blank 记录当前空格位置,主要用于程序优化,
扩展时可不必在寻找空格位置         
g, h  对应g(n), h(n)          
pre   记录当前结点由哪个结点扩展而来 */
struct item  
{          
	int state;          
	int blank;         
	int g;         
	int h;           
	int pre; 
};

const int MAXSTEPS = 100000; 
const int MAXCHAR = 100; 
char buf[MAXCHAR][MAXCHAR]; //open表 
item open[MAXSTEPS]; 
//vector<item> open;
int steps = 0;

//closed表,已查询状态只要知道该状态以及它由哪个结点扩展而来即可,用于输出路径 
//每次只需得到对应f值最小的待扩展结点,用堆实现提高效率
pair<int, int> closed[MAXSTEPS];
//读入,将8数码矩阵格局转换为整数表示 

bool read(pair<int,int> &state) 
{          
	if (!gets(buf[0]))                 
		return false;         
	if (!gets(buf[1]))                 
		return false;         
	if (!gets(buf[2]))                 
		return false; 

	//cout << strlen(buf[0]) << ' ' << strlen(buf[1]) << ' ' << strlen(buf[2]) << endl;
	assert(strlen(buf[0]) == 5 && strlen(buf[1]) == 5 && strlen(buf[2]) == 5);
	// astar.in中的每行数据长度必须为5
	state.first = 0;
	for (int i = 0, p = 1; i < 3; ++i)         
	{                  
		for (int j = 0; j < 6; j += 2)                  
		{                          
			if (buf[i][j] == '0')                                  
				state.second = i * 3 + j / 2;     // state.second为0(空格)在节点中的位置                    
			else                                  
				state.first += p * (buf[i][j] - '0');                
			p *= 10;                 
		}         
	}

	/* 若初试节点为:   
	1 2 3
	8 0 4
	7 6 5
	则state.first为567408321,state.second为4
	*/
	return true;
}

//计算当前结点距目标的距离 
int calculate(int current, int target)  // return h=the sum of distances each block have to move to the right position,这里的each block不包括空格
{          
	int c[9], t[9];         
	int i, cnt = 0;          
	for (i = 0; i < 9; ++i)         
	{                  
		c[current % 10] = t[target % 10] = i;                 
		current /= 10;                
		target /= 10;         
	}  

	for (i = 1; i < 9; ++i)                  
		cnt += abs(c[i] / 3 - t[i] / 3) + abs(c[i] % 3 - t[i] % 3);         

	return cnt; 
}

//open表中结点间选择时的规则 f(n) = g(n) + h(n)

class cmp 
{ 
public: inline bool operator()(item a, item b)         
		{                  
			return a.g + a.h > b.g + b.h;         
		} 
}; 

//将整数形式表示转换为矩阵表示输出
void pr(int state) 
{          
	memset(buf, ' ', sizeof(buf));         
	for (int i = 0; i < 3; ++i)         
	{                  
		for (int j = 0; j < 6; j += 2)                 
		{                          
			if (state % 10)
				buf[i][j] = state % 10 + '0';                         
			state /= 10;                 
		}       

		buf[i][5] = '\0';                 
		puts(buf[i]);
	}
}

//用于判断当前空格是否可以向对应方向移动
inline bool suit(int a, int b)  //空格移动后的坐标为(a,b)
{          
	return (a >= 0 && a < 3 && b >= 0 && b < 3); 
} 


//递归输出搜索路径
void path(int index) 
{          
	if (index == 0)         
	{                  
		pr(closed[index].first);                 
		puts("");                 
		return;         
	}          
	path(closed[index].second);         
	pr(closed[index].first); //将整数形式表示转换为矩阵表示输出         
	puts("");         
	++steps; 
}

int getNixuNum( int state ) //求节点的逆序对数
{
	int sum = 0;
	int result[9];
	memset( result, 0, sizeof(result) );
	//cout << result[8] << result[7] << endl;

	char buf[10];
	itoa( state, buf, 10 );
	//cout << buf << endl;
	int k = 0;
	while( buf[k] != '\0' )
	{
		result[9-k-1] = buf[k] - '0';
		k++;
	}
	
	for( int i = 0; i < 9; i++ )
	{
		for( int j = i + 1; j < 9; j++ )
		{
			if( result[i] && result[j] && result[i] > result[j] )
			{
				sum++;
			}
		}
	}
	return sum; //返回3*3方格数组的逆序对数
}

int main() 
{    
	//cout << getNixuNum(87654321);
	//open.resize(MAXSTEPS);
	unsigned int t1 = clock();    
	//cout << open.size() << endl;
	if( freopen("astar.in", "r", stdin) == NULL ) 
	{
		cout << "file not find\n";
		exit(0);
	};    

	freopen("astar2.out", "w", stdout);         
	set<int>states;         
	char tmp[100];          
	int i, x, y, a, b, nx, ny, end, next, index, kase = 0;         
	pair<int,int> start, target;         
	item head;          //4个方向移动时的偏移量          
	const int xtran[4] = {-1, 0, 1, 0};         
	const int ytran[4] = {0, 1, 0, -1};          
	const int p[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};

	while (read(start))  // 读取初试状态节点       
	{                  
		unsigned int t2 = clock();                  
		printf("Case %d:\n\n", ++kase);                 
		gets(tmp);                 
		read(target);  // 读取目标状态节点          
		gets(tmp); 

		int targetNixuNum = getNixuNum(target.first);
		//若两者的逆序对数不是同为奇数或同为偶数,则无解
		if( !(getNixuNum(start.first)&1 && targetNixuNum&1 || !(getNixuNum(start.first)&1) && !(targetNixuNum&1)) )
		{
			cout << "无法从初始节点到终态节点\n";
			exit(0);
		}
		//初始化open表,将初始状态加入
		open[0].state = start.first;                  
		open[0].h = calculate(start.first, target.first); // 计算当前节点到目标节点的估计距离                
		open[0].blank = start.second;                 
		open[0].pre = -1;    // 初始节点无父节点             
		open[0].g = 0;  // 初始节点的g为0               
		index = 0;                  
		states.insert(start.first); // 扩展过节点保存在states中,即出现过的状态保存在states中,states为set<int>类型,其中的states中的元素唯一

		//提取open表中f值最小元素放入closed表,并对该结点进行扩展
		for (end = 1; end > 0; ++index)   // end为open表中的元素个数,一直循环到open表为空              
		{                          
			assert(index < MAXSTEPS);       
			//临时存储                         
			head = open[0]; // 由于使用pop_heap函数和push_heap函数,所以open[0]为g+h最小的元素

			//放入closed表记录当前格局和由哪个结点扩展而来(该结点肯定已在closed表中)
			closed[index].first = open[0].state; //放入close表中,表示已经扩展完的节点,下面的for循环会扩展其节点                         
			closed[index].second = open[0].pre; // index表示当前close表中当前扩展节点的下标
			//从open表中删除该结点                          
			pop_heap(open, open + end, cmp());//为algorithm文件中的函数,第一个参数指定开始位置,第二个指定结束,第三个指定比较函数                         
			--end;      

			//得到结果,递归输出路径
			if (head.state == target.first)                         
			{                                  
				path(index);                                 
				break;                         
			}

			x = head.blank / 3;                         
			y = head.blank % 3; //空格在3*3方格中的x,y坐标
			/*
			    |2 0 3|
			A = |1 8 4|
			    |7 6 5| // 看成3*3的数组
			则head.blank=1
			x=0,y=1,即空格的在3*3的数组中下标为(0,1)
			*/
			for (i = 0; i < 4; ++i)                         
			{                                  
				nx = x + xtran[i];                                 
				ny = y + ytran[i];   
				/*
				i=0时:(nx,ny)为当前空格向上移动一格后的坐标
				i=1时:(nx,ny)为当前空格向右移动一格后的坐标
				i=2时:(nx,ny)为当前空格向下移动一格后的坐标
				i=3时:(nx,ny)为当前空格向左移动一格后的坐标
				*/
				if (suit(nx, ny)) // 判断是否能够移动
				{                                          
					a = head.blank; // 空格当前位置,以上面矩阵A为例,a=1                                       
					b = nx * 3 + ny; // 空格移动后的新位置,开始是能够向右边移动,故b=0*3+2=2                                        
					//调换十进制表示整数对应两个数字位                                          
					next = head.state + ((head.state % p[a + 1]) / p[a] - (head.state % p[b + 1]) / p[b]) * p[b]  + ((head.state % p[b + 1]) / p[b] - (head.state % p[a + 1]) / p[a]) * p[a];   
					// 如head.state=567481302,空格向右移动一次后,next=567481032,即为移动后的节点
					
					// 判断能否由当前节点到达目标节点
					if( ( getNixuNum(next)&1 && targetNixuNum&1 ) || ( !(getNixuNum(next)&1) && !(targetNixuNum&1) ) )
					{
						//判断是否已经扩展过,即已经出现过
						if (states.find(next) == states.end()) //没出现就保存一下,也存入open表                                        
						{                                                  
							states.insert(next);                                                     
							open[end].pre = index; //扩展后的子节点,其父节点为当前扩展节点                                                
							open[end].blank = b;                                                 
							open[end].state = next;                                                  
							open[end].h = calculate(next,target.first); 
							open[end].g  = head.g + 1;  
							++end;  //open表中元素加1                                                
							push_heap(open, open + end, cmp());    //压入堆中                                     
						}           
					}
					                      
				}                         
			}                 
		} 

		if (end <= 0)                          
			puts("No solution");
		else                 
		{                          
			printf("Num of steps: %d\n", steps);                          
			printf("Num of expanded: %d\n", index);                         
			printf("Num of generated: %d\n", index + end);                         
			printf("Time consumed: %d\n\n", clock() - t2);                 
		} 

		states.clear();                 
		steps = 0;         
	}          
	printf("Total time consumed: %d\n", clock() - t1);         
	return 0;
}

测试:

输入文件:astar.in

输出文件:astar2.out


astar.in文件内容:

3 1 2
4 0 5
6 7 8

0 1 2
3 4 5
6 7 8

1 2 3
4 5 6
7 8 0

0 1 2
3 4 5
6 7 8

注:上面前两个3*3矩阵为第一个测试案例,其中第一个3*3为初态节点,第二个3*3为终态节点,后面两个3*3矩阵为第二个测试案例,

其中第一个3*3为初态节点,第二个3*3为终态节点,各个矩阵之间需要空一行


测试案例1:

astar.in:

3 1 2
4 0 5
6 7 8

0 1 2
3 4 5
6 7 8


astar2.out:

Case 1:

3 1 2
4   5
6 7 8

3 1 2
  4 5
6 7 8

  1 2
3 4 5
6 7 8

Num of steps: 2
Num of expanded: 2
Num of generated: 6
Time consumed: 64

Total time consumed: 92


测试案例2:

astar.in:

3 7 2
8 1 5
4 6 0

0 1 2
3 4 5
6 7 8

astar2.out:

Case 1:

无法从初始节点到终态节点

注:astar.in中每一行数据长度只能是5,0表示空格


测试案例3:

astar.in:

1 2 3
4 5 6
7 8 0

0 1 2
3 4 5
6 7 8

astar2.out:

Case 1:

1 2 3
4 5 6
7 8  

1 2 3
4 5 6
7   8

1 2 3
4   6
7 5 8

1 2 3
4 6  
7 5 8

1 2  
4 6 3
7 5 8

1   2
4 6 3
7 5 8

  1 2
4 6 3
7 5 8

4 1 2
  6 3
7 5 8

4 1 2
6   3
7 5 8

4 1 2
6 3  
7 5 8

4 1  
6 3 2
7 5 8

4   1
6 3 2
7 5 8

4 3 1
6   2
7 5 8

4 3 1
6 5 2
7   8

4 3 1
6 5 2
  7 8

4 3 1
  5 2
6 7 8

  3 1
4 5 2
6 7 8

3   1
4 5 2
6 7 8

3 1  
4 5 2
6 7 8

3 1 2
4 5  
6 7 8

3 1 2
4   5
6 7 8

3 1 2
  4 5
6 7 8

  1 2
3 4 5
6 7 8

Num of steps: 22
Num of expanded: 1104
Num of generated: 1742
Time consumed: 123

Total time consumed: 126


转载来源:http://wenku.baidu.com/view/87c92ef1ba0d4a7302763a29.html


八数码问题的另一个实现:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
#include<string>
#define inf 1<<30
#define eps 1e-7
#define LD long double
#define LL long long
#define maxn 1000000005
using namespace std;
struct Node{
	int maze[3][3];   //八数码具体情况 
	int h,g;    //两个估价函数
	int x,y;   //空位的位置
	int Hash;   //HASH值
	bool operator<(const Node n1)const{     //优先队列第一关键字为h,第二关键字为g
		return h!=n1.h?h>n1.h:g>n1.g;
	}
	bool check(){    //判断是否合法
		if(x>=0&&x<3&&y>=0&&y<3)
			return true;
		return false;
	}
}s,u,v,tt;
int HASH[9]={1,1,2,6,24,120,720,5040,40320};   //HASH的权值
int destination=322560;   //目标情况的HASH值 
/*
目标状态:
1 2 3
4 5 6 
7 8 0
其hash值为322560
*/
int vis[400000];            //判断状态已遍历,初始为-1,否则为到达这步的转向
int pre[400000];        //路径保存
int way[4][2]={{0,1},{0,-1},{1,0},{-1,0}};   //四个方向
void debug(Node tmp){
	for(int i=0;i<3;i++){
		for(int j=0;j<3;j++)
			printf("%d ",tmp.maze[i][j]);
		printf("\n");
	}
	printf("%d %d\n%d %d\n",tmp.x,tmp.y,tmp.g,tmp.h);
	printf("hash=%d\n",tmp.Hash);
}
int get_hash(Node tmp){    //获得HASH值
	int a[9],k=0;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			a[k++]=tmp.maze[i][j];
	int res=0;
	for(int i=0;i<9;i++){
		int k=0;
		for(int j=0;j<i;j++)
			if(a[j]>a[i])
				k++;
		res+=HASH[i]*k;
	}
	return res;
}

bool isok(Node tmp){    //求出逆序对,判断是否有解
	int a[9],k=0;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			a[k++]=tmp.maze[i][j];
	int sum=0;
	for(int i=0;i<9;i++)
		for(int j=i+1;j<9;j++)
			if(a[j]&&a[i]&&a[i]>a[j])
				sum++;
	return !(sum&1);    //由于目标解为偶数,所以状态的逆序数为偶数才可行,交换空格,逆序数增幅为偶数,故初始节点和目标的节点的逆序数奇偶性相同
}

int get_h(Node tmp){   //获得估价函数H
	int ans=0;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			if(tmp.maze[i][j])
				ans+=abs(i-(tmp.maze[i][j]-1)/3)+abs(j-(tmp.maze[i][j]-1)%3);
	return ans;
}
void astar(){    //搜索
	priority_queue<Node>que;
	que.push(s);
	while(!que.empty()){
		u=que.top();
		que.pop();
		for(int i=0;i<4;i++){
			v=u;
			v.x+=way[i][0];
			v.y+=way[i][1];
			if(v.check()){
				swap(v.maze[v.x][v.y],v.maze[u.x][u.y]);   //将空位和相邻位交换
				v.Hash=get_hash(v);			    //得到HASH值
				if(vis[v.Hash]==-1&&isok(v)){   //判断是否已遍历且是否可行,后者可以不要
					vis[v.Hash]=i;           //保存方向
					v.g++;;                  //已花代价+1
					pre[v.Hash]=u.Hash;     //保存路径
					v.h=get_h(v);           //得到新的估价函数H
					que.push(v);     //入队
				}
				if(v.Hash==destination)
					return ;
			}
		}
	}
}
void print(){
	string ans;
	ans.clear();
	int nxt=destination;
	while(pre[nxt]!=-1){  //从终点往起点找路径
		switch(vis[nxt]){   //四个方向对应
		case 0:ans+='r';break;
		case 1:ans+='l';break;
		case 2:ans+='d';break;
		case 3:ans+='u';break;
		}
		nxt=pre[nxt];	
	}
	for(int i=ans.size()-1;i>=0;i--)
		putchar(ans[i]);
	puts("");
}
int main(){
	Node test;
	/*int value = 0;
	for( int i = 0; i < 3; i++ )
	{
		for( int j = 0; j < 3; j++ )
		{
			test.maze[i][j] = value++;
		}
	}*/

	//cout << get_hash(test) << endl;
	
	char str[100];
	while(gets(str)!=NULL){
		int k=0;
		memset(vis,-1,sizeof(vis));
		memset(pre,-1,sizeof(pre));
		for(int i=0;i<3;i++)
			for(int j=0;j<3;j++){
				if((str[k]<='9'&&str[k]>='0')||str[k]=='x'){
					if(str[k]=='x'){
						s.maze[i][j]=0;
						s.x=i;
						s.y=j;
					}
					else
						s.maze[i][j]=str[k]-'0';
				}
				else
					j--;
				k++;
			}
			if(!isok(s)){   //起始状态不可行
				printf("unsolvable\n");
				continue;
			}
			s.Hash=get_hash(s);
			if(s.Hash==destination){   //起始状态为目标状态
				puts("");
				continue;
			}
			vis[s.Hash]=-2;
			s.g=0;s.h=get_h(s);
			astar();
			print();
	}
	return 0;
}

/*
输入格式:
1234567x8表示:x代表空格,0表示空格
1 2 3
4 5 6
7 0 8
终态:
1 2 3
4 5 6
7 8 0
*/

转载来源: http://blog.csdn.net/acm_cxlove/article/details/7745323

人工智能A*课件:

http://wenku.baidu.com/link?

url=1TbH7biYFTuyNVE65fy26uXLbnRRNwe9Flhy6dFwX6gs42Sp919efN6uoJWKm_tJNNv1MNl2pGkVGxl3OZz1v1rtY4Ge98m6LPfiG6Ms47G

http://www.docin.com/p-506506343.html

include using namespace std; struct node{ int nodesun[4][4]; int pre; //上一步在队列中的位置 int flag ; //步数标识,表示当前的步数为有效的 int value; //与目标的差距 int x,y; //空格坐标 }queue[1000]; //移动方向数组 int zx[4]={-1,0,1,0}; int zy[4]={0,-1,0,1}; //当前步数 int top; int desti[4][4];//目标状态 int detect(struct node *p)//检查是否找到 {int i,j; for(i=1;i<4;i++) for(j=1;jnodesun[i][j]!=desti[i][j]) return 0; return 1; } //打印 void printlj() {int tempt; int i,j; tempt=top; while(tempt!=0) { for(i=1;i<4;i++) for(j=1;j<4;j++) {cout<<queue[tempt].nodesun[i][j]; if(j==3) cout<<" "<<endl; } tempt=queue[tempt].pre; } } //现在状态与目标状态有多少个不同位置 int VALUE(struct node *p) {int count=0; int i,j; for(i=1;i<4;i++) for(j=1;jnodesun[i][j]!=desti[i][j]) count++; return count; } void main() { //初始化 int i,j,m,n,f; int min=10; int temp,find=0,minnumber; top=1; for(i=1;i<4;i++) for(j=1;j<4;j++) {cout<<"请输入第"<<i<<"行"<<"第"<<j<<"列的值"<>temp; queue[1].nodesun[i][j]=temp; } cout<<"请输入初始状态的空格的位置(行)"<>temp; queue[1].x=temp; cout<<"请输入初始状态的空格的位置(列)"<>temp; queue[1].y=temp; queue[1].value=VALUE(&queue[1]); queue[1].pre=0; //上一步在队列中的位置 queue[1].flag=0; //目标状态 for(i=1;i<4;i++) for(j=1;j<4;j++) {cout<<"请输入目标状态第"<<i<<"行"<<"第"<<j<<"列的值"<>temp; desti[i][j]=temp; } //根据估价函数 while(!find&&top>0) { for(i=1;i<=top;i++) //////////////////////////////////////////// //min为上一图中与目标图有多少个元素不相同,queue[i]为当前图与目标图有多少个元素不相同通过这两个数的比较,就可以得出当前图较之上一图向目标图接近同时把当前的i记录下来进行下一步比较 {if(queue[i].value<min&&queue[i].flag==0) {minnumber=i;// min=queue[i].value; //还有多少不同的位数 } } queue[minnumber].flag=1; //表示此位有效 ////////////////////////////////////// // for(f=0;f=1&&i=1&&j<=3) {top++; ///////////////////////////////////////////// //位置交换 queue[top]=queue[minnumber]; queue[top].nodesun[m][n]=queue[minnumber].nodesun[i][j]; queue[top].nodesun[i][j]=0; /////////////////////////////////////// //空格移动方向 queue[top].x=i; queue[top].y=j; /////////////////////////////////////// queue[top].pre=minnumber; //上一步在队列中的位置 queue[top].value=VALUE(&queue[top]); //有多少位与目标不同 queue[top].flag=0; //标识位初始化 if(detect(&queue[top])) //检查是否为目标 {printlj(); //打印 find=1; //设找到标识位 break; } } } } }
A*算法求解八数码问题 1、A*算法基本思想: 1)建立一个队列,计算初始结点的估价函数f,并将初始结点入队,设置队列头和尾指针。 2)取出队列头(队列头指针所指)的结点,如果该结点是目标结点,则输出路径,程序结束。否则对结点进行扩展。 3)检查扩展出的新结点是否与队列中的结点重复,若与不能再扩展的结点重复(位于队列头指针之前),则将它抛弃;若新结点与待扩展的结点重复(位于队列头指针之后),则比较两个结点的估价函数中g的大小,保留较小g值的结点。跳至第五步。 4)如果扩展出的新结点与队列中的结点不重复,则按照它的估价函数f大小将它插入队列中的头结点后待扩展结点的适当位置,使它们按从小到大的顺序排列,最后更新队列尾指针。 5)如果队列头的结点还可以扩展,直接返回第二步。否则将队列头指针指向下一结点,再返回第二步。 2、程序运行基本环境: 源程序所使用编程语言:C# 编译环境:VS2010,.net framework 4.0 运行环境:.net framework 4.0 3、程序运行界面 可使用程序中的test来随机生成源状态与目标状态 此停顿过程中按Enter即可使程序开始运行W(n)部分; 此停顿部分按Enter后程序退出; 4、无解问题运行情况 这里源程序中是先计算源状态与目标状态的逆序对的奇偶性是否一致来判断是否有解的。下面是无解时的运行画面: 输入无解的一组源状态到目标状态,例如: 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 8 7 0 运行画面如下: 5、性能比较 对于任一给定可解初始状态,状态空间有9!/2=181440个状态;当采用不在位棋子数作为启发函数时,深度超过20时,算法求解速度较慢; 其中启发函数P(n)与W(n)的含义如下: P(n): 任意节点与目标结点之间的距离; W(n): 不在位的将牌数; 源状态 目标状态 P(n) 生成节点数 W(n) 生成节点数 P(n) 扩展节点数 W(n) 扩展节点数 2 8 3 1 6 4 7 0 5 1 2 3 8 0 4 7 6 5 11 13 5 6 1 2 3 8 0 4 7 6 5 0 1 3 8 2 4 7 6 5 6 6 2 2 4 8 2 5 1 6 7 0 3 7 4 2 8 5 6 1 3 0 41 79 22 46 6 2 5 8 7 0 3 1 4 0 3 6 7 1 8 4 5 2 359 10530 220 6769 7 6 3 1 0 4 8 5 2 2 8 7 1 3 4 6 5 0 486 8138 312 5295 下图是解决随机生成的100中状态中,P(n)生成函数的生成节点与扩展节点统计图: 由上图可知,P(n)作为启发函数,平均生成节点数大约在1000左右,平均扩展节点数大约在600左右; 下图是解决随机生成的100中状态中,W(n)生成函数的生成节点与扩展节点统计图: 由上图可知,W (n)作为启发函数,平均生成节点数大约在15000左右,是P(n)作为启发函数时的平均生成节点的15倍;W (n)作为启发函数,平均扩展节点数大约在10000左右,是P(n)作为启发函数时的平均扩展节点的15倍; 下图是解决随机生成的100中状态中,两个生成函数的生成节点与扩展节点统计图: 由上述图表可以看到,将P(n)作为启发函数比将W(n)作为启发函数时,生成节点数与扩展节点数更稳定,相比较来说,采用P(n)作为启发函数的性能比采用W(n)作为启发函数的性能好。 6、源代码说明 1)AStar-EightDigital-Statistics文件夹:用来随机生成100个状态,并对这100个状态分别用P(n)与W(n)分别作为启发函数算出生成节点以及扩展节点,以供生成图表使用;运行界面如下: 2)Test文件夹:将0-8这9个数字随机排序,用来随机生成源状态以及目标状态的;运行界面如下: 3)AStar-EightDigital文件夹:输入源状态和目标状态,程序搜索出P(n)与W(n)分别作为启发函数时的生成节点数以及扩展节点数,并给出从源状态到目标状态的移动步骤;运行界面如下: 提高了运行速度的几处编码思想: 1、 在维护open以及close列表的同时,也维护一个类型为hashtable的open以及close列表,主要用来提高判断当前节点是否在open列表以及close列表中出现时的性能; 2、 对于每个状态,按照从左到右,从上到下,依次将数字拼接起来,形成一个唯一标识identify,通过该标识,可以直接判断两个状态是否是同一个状态,而不需要循环判断每个位置上的数字是否相等 3、 在生成每个状态的唯一标识identify时,同时计算了该状态的空格所在位置,通过空格所在位置,可以直接判断能否进行上移、下移、左移、右移等动作; 4、 只计算初始节点的h值,其它生成的节点的h值是根据当前状态的h值、移动的操作等计算后得出的,规则如下: a) 采用W(n)这种方式,不在位置的将牌数,共有以下3中情况: i. 该数字原不在最终位置上,移动后,在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值-1 ii. 该数字原在最终位置上,移动后,不在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值 +1 iii. 该数字原不在最终位置上,移动后,还是不在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值 iv. 该数字原在最终位置上,移动后,还在其最终位置 这种情况不存在 b) 采用P(n)这种方式,节点与目标距离,可通过下面3步完成 i. 首先计算在原位置时,与目标位置的距离,命名为Distance1 ii. 移动后,计算当前位置与目标位置的距离,命名为Distance2 iii. 计算子节点的h值: 子节点的h值 = 父节点的h值- Distance1+ Distance2 5、 在任意状态中的每个数字和目标状态中同一数字的相对距离就有9*9种,可以先将这些相对距离算出来,用一个矩阵存储,这样只要知道两个状态中同一个数字的位置,就可查出它们的相对距离,也就是该数字的偏移距离;例如在一个状态中,数字8的位置是3,在另一状态中位置是7,那么从矩阵的3行7列可找到2,它就是8在两个状态中的偏移距离。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值