八数码问题

 摘要:近日来,人工智能成为科技领域搜索热词,无论是从人机大战的新闻来看,还是从新提出的深度学习理论来分析,我们可以可以清晰的预见,人工智能即将腾飞。

  
人工智能,顾名思义,就是模拟人类思考模式的超级算法系统,学习能力和推理能力是其核心内容。举个简单的例子,“机器学习(MachineLearning)”就是人工智能领域里很有前途的课题,其主要内容是利用大数据训练程序,让它们找到一些可遵循的规律,并且让程序本身大胆的预测结果。在这个过程中搜索策略变的尤为关键。 本文主要论述计算机科学与技术专业大三下专业课《人工智能》第二个实验算法。

关键字:人工智能,搜索问题,启发式搜索

Eight digital problem  

Abstract: in recent days, the artificial intelligence search words become areas of science and technology, whether from the point of man-machine war news, or the depth of the new proposed learning theory to the analysis, we can clearly foresee, artificial intelligence is about to take off.

  Artificial intelligence, as the name implies, is to simulate human thinking mode of super algorithm system, learning ability and reasoning ability is the core content. A simple example, the "machine learning (MachineLearning)" is the field of artificial intelligence is a promising subject, its main content is to use big data training program, let them find some follow rules, and make bold prediction to the program itself. In the process of the search strategy is particularly critical. This paper mainly discusses the computer science and technology under the junior in professional course "artificial intelligence".

Keywords: artificial intelligence, search problems, heuristic search

1,问题重述 
  
3×3九宫棋盘,放置数码为1 -8的8个棋牌,剩下一个空格,只能通过棋牌向空格的移动来改变棋盘的布局。

要求:根据给定初始布局(即初始状态)和目标布局(即目标状态),如何移动棋牌才能从初始布局到达目标布局,找到合法的走步序列。
             八数码问题_第1张图片

2,问题分析
  
对于八数码问题的解决,首先要考虑是否有答案。每一个状态可认为是一个1×9的矩阵,问题即通过矩阵的变换,是否可以变换为目标状态对应的矩阵?由数学知识可知,可计算这两个有序数列的逆序值,如果两者都是偶数或奇数,则可通过变换到达,否则,这两个状态不可达。这样,就可以在具体解决问题之前判断出问题是否可解,从而可以避免不必要的搜索。
  
如果初始状态可以到达目标状态,那么采取什么样的方法呢?
  
常用的状态空间搜索有深度优先和广度优先。广度优先是从初始状态一层一层向下找,直到找到目标为止。深度优先是按照一定的顺序前查找完一个分支,再查找另一个分支,以至找到目标为止。广度和深度优先搜索有一个很大的缺陷就是他们都是在一个给定的状态空间中穷举。这在状态空间不大的情况下是很合适的算法,可是当状态空间十分大,且不预测的情况下就不可取了。他的效率实在太低,甚至不可完成。由于八数码问题状态空间共有9!个状态,对于八数码问题如果选定了初始状态和目标状态,有9!/2个状态要搜索,考虑到时间和空间的限制,在这里采用A*算法作为搜索策略。在这里就要用到启发式搜索
  
启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无畏的搜索路径,提到了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。
  
启发中的估价是用估价函数表示的,如:f(n) = g(n) +h(n)其中f(n) 是节点n的估价函数,g(n)是在状态空间中从初始节点到n节点的实际代价,h(n)是从n到目标节点最佳路径的估计代价。 在此八数码问题中,显然g(n)就是从初始状态变换到当前状态所移动的步数,估计函数f(n)我们就可采用当前状态各个数字牌不在目标状态未知的个数,即错位数。 

  2.1使用宽度优先搜索方法解决该问题
   
为问题状态的表示建立数据结构:
 (1)
3×3的一个矩阵矩阵元素S ij∈{0,1,,8};其中1≤i,j≤3,
 (2)
数字0指示空格,
 (3)
数字1 - 8指示相应棋牌。
 (4)
制定操作算子集:

   直观方法——为每个棋牌制定一套可能的走步:左、上、右、下四种移动。这样就需32个操作算子。

   简易方法——仅为空格制定这4种走步,因为只有紧靠空格的棋牌才能移动。

   空格移动的唯一约束是不能移出棋盘。  
   八数码问题_第2张图片 
  
  2.2使用深度优先搜索解决该问题
  
首先扩展最新产生的(即最深的)节点防止搜索过程沿着无益的路径扩展下去,往往给出一个节点扩展的最大深度——深度界限与宽度优先搜索算法最根本的不同在于:将扩展的后继节点放在OPEN表的前端。 
  
  

  八数码问题_第3张图片

2.3使用启发式搜索
  
特点:重排OPEN表,选择最有希望的节点加以扩展

  种类:有序搜索 ( A 算法 ) A * 算法

用来加速搜索过程的有关问题领域的
特征信息 。包括:
用于决定要扩展的下一个节点的信息;
在扩展一个节点时,用于决定要生成哪一个或哪几个后继节点的信息;
用于决定某些应该从搜索树中抛弃或修剪的节点的信息;
使用 启发式信息指导 的搜索过程称为 启发式搜索 .
用来估算节点处于最佳求解路径上的希望程度的函数

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

n ——搜索图中的某个当前被扩展的节点;

f(n) ——从初始状态节点s, 经由节点n到达目标节点ng估计的最小路径代价;

g(n) ——s的实际路径代价;

 h(n)—— n n g , 估计的最小路径代价。
 
   

   估价函数:f(n)=d(n)+w(n) 
        
其中:d(n)n的深度 w(n)为不在位的棋子数
3,求解过程
  
不管哪种搜索,都统一用这样的形式表示:搜索的对象是一个图,它面向一个问题,不一定有明确的存储形式,但它里面的一个结点都有可能是一个解(可行解),搜索的目的有两个方面,或者求可行解,或者从可行解集中求最优解。
  
搜索算法可分为两大类:无信息的搜索算法和有信息的搜索算法。无信息的搜索又称盲目搜索,其特点是只要问题状态可以形式化表示,原则上就可用使用无信息的搜索,无信息搜索有如下常见的几种搜索策略:广度优先搜索、代价一致搜索、深度优先搜索、深度有限搜索、迭代深入优先搜索、双向搜索。我们说DFSBFS都是蛮力搜索,因为它们在搜索到一个结点时,在展开它的后续结点时,是对它们没有任何认识的,它认为它的孩子们都是一样的优秀,但事实并非如此,后续结点是有好有坏的。好,就是说它离目标结点,如果优先处理它,就会更快的找到目标结点,从而整体上提高搜索性能。
  
为了改善上面的算法,我们需要对展开后续结点时对子结点有所了解,这里需要一个估值函数,估值函数就是评价函数,它用来评价子结点的好坏,因为准确评价是不可能的,所以称为估值。这就是我们所谓的有信息搜索。如果估值函数只考虑结点的某种性能上的价值,而不考虑深度,比较有名的就是有序搜索(Ordered-Search),它着重看好能否找出解,而不看解离起始结点的距离(深度)。如果估值函数考虑了深度,或者是带权距离(从起始结点到目标结点的距离加权和),那就是A*如果不考虑深度,就是说不要求最少步数,移动一步就相当于向后多展开一层结点,深度多算一层,如果要求最少步数,那就需要用A*。简单的来说A*就是将估值函数分成两个部分,一个部分是路径价值,另一个部分是一般性启发价值,合在一起算估整个结点的价值,

考虑到八数码问题的特点,在本实验中使用A*算法求解。A*搜索是一种效的搜索算法,它把到达节点的耗散g(n)和从该节点到目标节点的消耗h(n)结合起来对节点进行评价:f(n)=g(n)+h(n)。当h(n)是可采纳时,使用Tree-Search的A*算法将是最优的。

           

      八数码问题_第4张图片

  A*算法,
GRAPHSEARCH 过程中,如果第 8 步的重排 OPEN 表是依据 f(n)=g(n)+h(n) 进行的,则称该过程为 A 算法 A 算法中,如果对所有的 n 存在 h(n)≤h*(n), 则称 h(n) h*(n) 的下界,它表示某种偏于保守的估计。 采用 h*(n) 的下界 h(n) 为启发函数的 A 算法,称为 A* 算法 。当 h=0 时, A* 算法就变为有序搜索算法。
  
A 算法中,如果满足条件:

  (1) g(n)是对g*(n)的估计,且g(n)>0;

  (2) h(n)h*(n)的下界,即对任意节点n均有0≤h(n)≤h*(n)A算法称为A*算法
  
A*算法的可纳性,对任一个图,存在从S到目标的路径,如果一个搜索算法总是结束在一条从S到目标的最佳路径上,则称此算法是可采纳的。算法A*保证只要最短路径存在,就一定能找出这条路径,所以算法A*是可纳的。
  
估价函数:f(n)=d(n)+w(n) 

  其中:d(n)n的深度 w(n)不在位的棋子数

      取h(n)=w(n),则有w(n)≤h*(n),h(n)满足A*算法的限制条件。 
  
在八数码难题中, 令估价函数

    f(n)=d(n)+p(n)

  启发函数h(n)=p(n),p(n)为不在位的棋子与其目标位置的距离之和,则有p(n)≤h*(n),满足A*算法的限制条件。  
  
w(n)——不在位的棋子数,不够贴切,错误选用节点加以扩展。
  
更接近于h*(n)h(n),其值是节点n与目标状态节点相比较,每个错位棋子在假设不受阻拦的情况下,移动到目标状态相应位置所需走步的总和。(n)w(n)更接近于h*(n),因为p(n)不仅考虑了错位因素,还考虑了错位的距离(移动次数)。
  
说明h值越大,启发功能越强, 搜索效率越高.特别地

   (1) h(n)=h*(n)

    搜索仅沿最佳路径进行, 效率最高.
 
(2)h(n)=0

    无启发信息, 盲目搜索, 效率低.
 
(3)h(n)>h*(n)

    是一般的A算法,效率高, 但不能保证找到最佳路径. 有时为求解难题取h(n)>h*(n), 以提高效率. 
  
             八数码问题_第5张图片

      
4,程序设计
 

#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;

class EightPuzzle
{
private:
	int num[9];
	int malposition;
	int depth;
	int evaluation;
public:
	EightPuzzle *parent;
	EightPuzzle *leaf_last;
	EightPuzzle *leaf_next;
public:
	EightPuzzle(int *num_input);
	void init(int *target);
	void setNum(int num[]);
	int *getNum();
	void getNum(int *num);
	int getMalposition()
	{
		return this->malposition;
	}
	int getDepth()
	{
		return this->depth;
	}
	int getEvaluation()
	{
		return this->evaluation;
	}
	void print();
	bool solvable(int *target);
	bool find_target(int *target);
	EightPuzzle& operator=(EightPuzzle& eightPuzzle);
	EightPuzzle& operator=(int other_num[9]);
	bool operator==(EightPuzzle& eigthPuzzle);
	bool operator==(int other_num[9]);
};

EightPuzzle::EightPuzzle(int *num_input)
{
	int ii;
	for (ii = 0; ii<9; ii++)
	{
		num[ii] = num_input[ii];
	}
	this->leaf_last = NULL;
	this->leaf_next = NULL;
	this->parent = NULL;
}

EightPuzzle& EightPuzzle::operator=(EightPuzzle& eightPuzzle)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		this->num[ii] = eightPuzzle.getNum()[ii];
	}
	this->malposition = eightPuzzle.getMalposition();
	this->depth = eightPuzzle.getDepth() + 1;
	this->evaluation = this->malposition + this->depth;
	return *this;
}
EightPuzzle& EightPuzzle::operator=(int other_num[9])
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		num[ii] = other_num[ii];
	}
	return *this;
}
bool EightPuzzle::operator==(EightPuzzle& eightPuzzle)
{
	int match = 1;
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (this->num[ii] != eightPuzzle.getNum()[ii])
		{
			match = 0;
			break;
		}
	}
	if (match == 0)
		return false;
	else
		return true;
}
bool EightPuzzle::operator==(int other_num[9])
{
	int match = 1;
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (this->num[ii] != other_num[ii])
		{
			match = 0;
			break;
		}
	}
	if (match == 0)
		return false;
	else
		return true;
}

void EightPuzzle::init(int *target)
{
	int ii;
	int temp = 0;
	for (ii = 0; ii < 9; ii++)
	{
		if (num[ii] != target[ii])
		{
			temp++;
		}
	}
	this->malposition = temp;
	if (this->parent == NULL)
	{
		this->depth = 0;
	}
	else
	{
		this->depth = this->parent->depth + 1;
	}
	this->evaluation = this->malposition + this->depth;
}

void EightPuzzle::setNum(int num[])
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		this->num[ii] = num[ii];
	}
}

int *EightPuzzle::getNum()
{
	return this->num;
}

void EightPuzzle::getNum(int *num)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		num[ii] = this->num[ii];
	}
}

bool EightPuzzle::solvable(int *target)
{
	int ii, ij;
	int count_num=0, count_target=0;
	for (ii = 0; ii < 9; ii++)
	{
		for (ij = 0; ij < ii; ij++)
		{
			if ((this->num[ij] < this->num[ii]) && (this->num[ij] != 0))
			{
				count_num++;
			}
			if (target[ij] < target[ii] && target[ij] != 0)
			{
				count_target++;
			}
		}
	}
	if ((count_num + count_target) % 2 == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}

bool EightPuzzle::find_target(int *target)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (this->num[ii] != target[ii])
		{
			break;
		}
	}
	if (ii == 9)
	{
		return true;
	}
	else
	{
		return false;
	}
}

bool move_up(int *num)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (num[ii] == 0)
		{
			break;
		}
	}
	if (ii < 3)
	{
		return false;
	}
	else
	{
		num[ii] = num[ii - 3];
		num[ii - 3] = 0;
	}
	return true;
}

bool move_down(int *num)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (num[ii] == 0)
		{
			break;
		}
	}
	if (ii > 5)
	{
		return 0;
	}
	else
	{
		num[ii] = num[ii + 3];
		num[ii + 3] = 0;
	}
	return true;
}

bool move_left(int *num)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (num[ii] == 0)
		{
			break;
		}
	}
	if (ii == 0 || ii == 3 || ii == 6)
	{
		return false;
	}
	else
	{
		num[ii] = num[ii - 1];
		num[ii - 1] = 0;
	}
	return true;
}

bool move_right(int *num)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (num[ii] == 0)
		{
			break;
		}
	}
	if (ii == 2 || ii == 5 || ii == 8)
	{
		return false;
	}
	else
	{
		num[ii] = num[ii + 1];
		num[ii + 1] = 0;
	}
	return true;
}

void EightPuzzle::print()
{
	int ii;
	for (ii = 0; ii<9; ii++)
	{
		if ((ii + 1) % 3 != 0)
		{
			cout << num[ii] << ",";
		}
		else
		{
			cout << num[ii] << endl;
		}
	}
}

bool existed(int *num, EightPuzzle *start)
{
	EightPuzzle *temp;
	for (temp = start; temp != NULL; temp = temp->parent)
	{
		if (*temp == num)
		{
			return true;
		}
	}
	return false;
}

EightPuzzle *best_route(EightPuzzle *start,EightPuzzle *target)
{
	EightPuzzle *temp, *best;
	temp = best = start;
	start->init(target->getNum());
	int min = start->getEvaluation();
	for (temp = start; temp != NULL; temp = temp->leaf_next)
	{
		if (min > temp->getEvaluation())
		{
			best = temp;
			min = temp->getEvaluation();
		}
	}
	return best;
}

void print_route(EightPuzzle *best,int list_length)
{
	int step = 0;
	EightPuzzle *temp;
	for (temp = best->parent; temp != NULL; temp = temp->parent)
	{
		cout << endl;
		temp->print();
		step++;
	}
	cout << endl << "The total steps is " << step << "." << endl;
	cout << endl << "The memory cost is " << list_length << "." << endl;
	return;
}

void proceeding(EightPuzzle &start, EightPuzzle &target)
{
	if (!start.solvable(target.getNum()))
	{
		cout <<endl<< "The serious number you input can't be solvable!" << endl;
		return;
	}
	EightPuzzle *best = &start;
	EightPuzzle *list = &start;
	EightPuzzle *apply,*temp;
	int num[9],list_length=0;
	while (best != NULL)
	{
		best = best_route(list,&target);
		if (best->find_target(target.getNum()))
		{
			print_route(best,list_length);
			return;
		}
		temp = best->leaf_last;
		best->getNum(num);
		if (move_up(num) && !existed(num, best))
		{
			apply = new EightPuzzle(num);
			apply->parent = best;
			apply->init(target.getNum());
			apply->leaf_last = temp;
			if (temp == NULL)
			{
				list = apply;
			}
			else
			{
				temp->leaf_next = apply;
			}
			temp = apply;
			list_length++;
		}
		best->getNum(num);
		if (move_down(num) && !existed(num, best))
		{
			apply = new EightPuzzle(num);
			apply->parent = best;
			apply->init(target.getNum());
			apply->leaf_last = temp;
			if (temp == NULL)
			{
				list = apply;
			}
			else
			{
				temp->leaf_next = apply;
			}
			temp = apply;
			list_length++;
		}
		best->getNum(num);
		if (move_left(num) && !existed(num, best))
		{
			apply = new EightPuzzle(num);
			apply->parent = best;
			apply->init(target.getNum());
			apply->leaf_last = temp;
			if (temp == NULL)
			{
				list = apply;
			}
			else
			{
				temp->leaf_next = apply;
			}
			temp = apply;
			list_length++;
		}
		best->getNum(num);
		if (move_right(num) && !existed(num, best))
		{
			apply = new EightPuzzle(num);
			apply->parent = best;
			apply->init(target.getNum());
			apply->leaf_last = temp;
			if (temp == NULL)
			{
				list = apply;
			}
			else
			{
				temp->leaf_next = apply;
			}
			temp = apply;
			list_length++;
		}
		temp->leaf_next = best->leaf_next;
		if (best->leaf_next != NULL)
		{
			best->leaf_next->leaf_last = temp;
		}
		best->leaf_next = best->leaf_last = NULL;
	}
}

void input(int num_init[])
{
	int ii, ij;
	cout << "Please input the initial state of the eight puzzle:" << endl;
	cout << "(0 for the blank)" << endl << endl;
	for (ii = 0; ii<9; ii++)
	{
		cin >> num_init[ii];
		if (num_init[ii]<0 || num_init[ii]>8)
		{
			cout << "Wrong number! Please input again:(0-8)" << endl;
			ii--;
		}
		for (ij = 0; ij<ii; ij++)
		{
			if (num_init[ii] == num_init[ij])
			{
				cout << "The number you inputed is duplicated! Try again:" << endl;
				ii--;
			}
		}
	}
}

int main(int argc, char **argv)
{
	double time;
	clock_t START, FINISH;
	int num_init[9];
	input(num_init);
	EightPuzzle *start = new EightPuzzle(num_init);
	int num_target[9] = { 1,2,3,8,0,4,7,6,5 };
	EightPuzzle *target = new EightPuzzle(num_target);
	cout << "The initial serial number is:" << endl;
	start->print();
	cout << "The target serial number is:" << endl;
	target->print();
	START = clock();
	proceeding(*start, *target);
	FINISH = clock();
	time = (double)(FINISH - START) * 1000 / CLOCKS_PER_SEC;
	cout << endl << "The total time cost to solve the puzzle is: ";
	cout<< time <<" millisecond."<< endl << endl;
	system("pause");
	return 0;
}

//216408753->18steps

你可能感兴趣的:(搜索,人工智能,计算机算法)