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个棋牌,剩下一个空格,只能通过棋牌向空格的移动来改变棋盘的布局。
要求:根据给定初始布局(即初始状态)和目标布局(即目标状态),如何移动棋牌才能从初始布局到达目标布局,找到合法的走步序列。
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使用深度优先搜索解决该问题
首先扩展最新产生的(即最深的)节点。防止搜索过程沿着无益的路径扩展下去,往往给出一个节点扩展的最大深度——深度界限。与宽度优先搜索算法最根本的不同在于:将扩展的后继节点放在OPEN表的前端。
2.3使用启发式搜索
特点:重排OPEN表,选择最有希望的节点加以扩展
f(n) = g(n) + h(n)
n ——搜索图中的某个当前被扩展的节点;
f(n) ——从初始状态节点s, 经由节点n到达目标节点ng,估计的最小路径代价;
g(n) ——从s到n 的实际路径代价;
h(n)—— 从 n 到 n g , 估计的最小路径代价。 估价函数:f(n)=d(n)+w(n)
其中:d(n)为n的深度 w(n)为不在位的棋子数
3,求解过程
不管哪种搜索,都统一用这样的形式表示:搜索的对象是一个图,它面向一个问题,不一定有明确的存储形式,但它里面的一个结点都有可能是一个解(可行解),搜索的目的有两个方面,或者求可行解,或者从可行解集中求最优解。
搜索算法可分为两大类:无信息的搜索算法和有信息的搜索算法。无信息的搜索又称盲目搜索,其特点是只要问题状态可以形式化表示,原则上就可用使用无信息的搜索,无信息搜索有如下常见的几种搜索策略:广度优先搜索、代价一致搜索、深度优先搜索、深度有限搜索、迭代深入优先搜索、双向搜索。我们说DFS和BFS都是蛮力搜索,因为它们在搜索到一个结点时,在展开它的后续结点时,是对它们没有任何‘认识’的,它认为它的孩子们都是一样的‘优秀’,但事实并非如此,后续结点是有好有坏的。好,就是说它离目标结点‘近’,如果优先处理它,就会更快的找到目标结点,从而整体上提高搜索性能。
为了改善上面的算法,我们需要对展开后续结点时对子结点有所了解,这里需要一个估值函数,估值函数就是评价函数,它用来评价子结点的好坏,因为准确评价是不可能的,所以称为估值。这就是我们所谓的有信息搜索。如果估值函数只考虑结点的某种性能上的价值,而不考虑深度,比较有名的就是有序搜索(Ordered-Search),它着重看好能否找出解,而不看解离起始结点的距离(深度)。如果估值函数考虑了深度,或者是带权距离(从起始结点到目标结点的距离加权和),那就是A*如果不考虑深度,就是说不要求最少步数,移动一步就相当于向后多展开一层结点,深度多算一层,如果要求最少步数,那就需要用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值越大,启发功能越强, 搜索效率越高.特别地
搜索仅沿最佳路径进行, 效率最高.
(2)h(n)=0
无启发信息, 盲目搜索, 效率低.
(3)h(n)>h*(n)
是一般的A算法,效率高, 但不能保证找到最佳路径. 有时为求解难题取h(n)>h*(n), 以提高效率.
4,程序设计
#include
#include
#include
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 <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; ijprint();
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