启发式搜索 八数码问题
启发式搜索浅谈,解决八数码问题

启发式搜索浅谈,解决⼋数码问题博客迁移⾄相信很多⼈都接触过九宫格问题,也就是⼋数码问题。
问题描述如下:在3×3的棋盘,摆有⼋个棋⼦,每个棋⼦上标有1⾄8的某⼀数字,不同棋⼦上标的数字不相同。
棋盘上还有⼀个空格,与空格相邻的棋⼦可以移到空格中。
要求解决的问题是:给出⼀个初始状态和⼀个⽬标状态,找出⼀种从初始转变成⽬标状态的移动棋⼦步数最少的移动步骤。
其实在很早之前我就做过这道题了,当时我⽤的是双向⼴搜,后来⼜⼀知半解的模仿了⼀个启发式搜索(A*)。
昨天在图书馆看书的时候,翻阅了⼀下⼈⼯智能的书籍,⼜发现了这个经典的⼋数码问题。
于是便看了下去,渐渐的明⽩了启发式搜索的真正含义,才知道⾃⼰⼏年前模仿的那个代码是什么意思。
在这篇⽂章⾥,我把昨天的所学东西稍稍的记录⼀下,顺便分享给⼤家,如果有什么错误,欢迎各位指出。
平时,我们所使⽤的搜索算法⼤多数都是⼴搜(BFS)和深搜(DFS),其实他们都是盲⽬搜索,相对来说搜索的状态空间⽐较⼤,效率⽐较低。
⽽启发式搜索则相对的智能⼀些,它能对所有当前待扩展状态进⾏评估,选出⼀个最好的状态、最容易出解的状态进⾏搜索。
这样,我们就可以避免扩展⼤量的⽆效状态,从⽽提⾼搜索效率。
在对状态进⾏评估的时候,我们会使⽤到⼀个这样的等式f(n)=g(n)+h(n)。
那这个等式是什么含义呢?其中g(n)代表到达代价,即从初始状态扩展到状态n的代价值。
h(n)代表状态n的估计出解代价,即从状态n扩展到⽬标状态的代价估计值。
所以,f(n)代表估计整体代价,即⼀个搜索路径上经过状态n且成功出解的估计代价值。
于是,在启发式搜索算法中,我们只要对每⼀个状态,求出其f(n),每次取f(n)最⼩的状态进⾏扩展即可。
那现在还有⼀个问题,就是如何确定g(n)和h(n)到底是什么函数?否则f(n)也⽆法求出。
其实g(n)相对来说⽐较好确定,因为在到达状态n之后,必定有⼀条从初始状态到n的搜索路径,于是我们可以从这条路径上找出到达代价g(n),⼀般的我们就取路径长度即可。
实验三:A星算法求解8数码问题实验

实验三:A星算法求解8数码问题实验实验三:A*算法求解8数码问题实验一、实验目的熟悉和掌握启发式搜索的定义、估价函数和算法过程,并利用A*算法求解N数码难题,理解求解流程和搜索顺序。
二、实验内容1、八数码问题描述所谓八数码问题起源于一种游戏:在一个3×3的方阵中放入八个数码1、2、3、4、5、6、7、8,其中一个单元格是空的。
将任意摆放的数码盘(城初始状态)逐步摆成某个指定的数码盘的排列(目标状态),如图1所示图1 八数码问题的某个初始状态和目标状态对于以上问题,我们可以把数码的移动等效城空格的移动。
如图1的初始排列,数码7右移等于空格左移。
那么对于每一个排列,可能的一次数码移动最多只有4中,即空格左移、空格右移、空格上移、空格下移。
最少有两种(当空格位于方阵的4个角时)。
所以,问题就转换成如何从初始状态开始,使空格经过最小的移动次数最后排列成目标状态。
2、八数码问题的求解算法2.1 盲目搜索宽度优先搜索算法、深度优先搜索算法2.2 启发式搜索启发式搜索算法的基本思想是:定义一个评价函数f,对当前的搜索状态进行评估,找出一个最有希望的节点来扩展。
先定义下面几个函数的含义:f*(n)=g*(n)+h*(n) (1)式中g*(n)表示从初始节点s到当前节点n的最短路径的耗散值;h*(n)表示从当前节点n到目标节点g的最短路径的耗散值,f*(n)表示从初始节点s经过n到目标节点g的最短路径的耗散值。
评价函数的形式可定义如(2)式所示:f(n)=g(n)+h(n) (2)其中n是被评价的当前节点。
f(n)、g(n)和h(n)分别表示是对f*(n)、g*(n)和h*(n)3个函数值的估计值。
利用评价函数f(n)=g(n)+h(n)来排列OPEN表节点顺序的图搜索算法称为算法A。
在A算法中,如果对所有的x,h(x)<=h*(x) (3)成立,则称好h(x)为h*(x)的下界,它表示某种偏于保守的估计。
基于启发式搜索算法A星解决八数码问题

int statue[size][size]; //记录当前节点的状态 struct Node * Tparent; //用来构成搜索树,该树由搜索图的反向指针构成 struct Node * opennext; //用来构成 open 表,该指针指向该节点在 open 表中的下一个 节点 struct Node * closenext; //用来构成 open 表,该指针指向该节点在 close 表中的下一个 节点 struct Node * brothernext; //构成兄弟链表,该指针指向该节点在兄弟链表中的下一个节 点 int f; //记录当前节点的 f 函数值 int g; //记录当前节点的 g 函数的值 int h; //记录当前节点的 h 函数的值 };
5
get_bestroute (bestNode); return; }
2.2.7 生成 bestNode 所指节点的后继节点
定义一个后继节点链表,表头为 head_b,将 bestNode 所指节点的不是前驱节点的后继 节点,链接到后继及诶单链表中。getchild 函数可以实现这个功能。
//产生 bestNode 的一切后继节点。。 head head_b; //定义 bestNode 的后继节点表 head_b.next=NULL; getchild (&head_b,bestNode); //产生 bestNode 的子节点,将不是 bestNode 的父节点的
while (head_b.next!=NULL) { Node *tmp=getbrother (&head_b); //从后继节点表中取出一个节点记为 tmp,并从
八个数字问题实验报告.doc

八个数字问题实验报告. 《八数码问题》实验报告首先,实验的目的:熟悉启发式搜索算法。
二、实验内容:启发式搜索算法用于解决8位数问题。
编制了程序,实现了解决8位数问题的算法。
采用评估功能,其中:是搜索树中节点的深度;在节点数据库中放错位置的件数;这是每个棋子与其在节点数据库中的目标位置之间距离的总和。
三、实验原理:1.问题描述:八位数问题也被称为九宫问题。
在3×3的棋盘上,有八个棋子,每一个棋子都标有一定的1到8的数字,不同棋子上标的数字是不同的。
棋盘上还有一个空格(用数字0表示),与空格相邻的棋子可以移动到空格中。
要解决的问题是: 给定初始状态和目标状态,找出从初始状态到目标状态移动次数最少的移动步骤。
所谓问题的一种状态是棋盘上棋子的排列。
解决八位数问题实际上是找出一系列从初始状态到目标状态的中间过渡状态。
2.原则描述:启发式搜索(1)原理启发式搜索是评估每个搜索在状态空间中的位置以获得最佳位置,然后从这个位置搜索到目标。
这样,可以省略大量不必要的搜索路径,并且提高了效率。
在启发式搜索中,位置的评估非常重要。
不同的评估会产生不同的效果。
(2)评估函数计算节点的评估函数,可分为两部分:1.成本已经支付(从开始节点到当前节点);2.要支付的价格(当前节点到目标节点)。
节点n的评估函数被定义为从初始节点通过n到目标节点的路径的最小成本的估计值,即=。
是从初始节点到达当前节点n的实际成本;是从节点n到目标节点的最佳路径的估计开销。
比例越大,它越倾向于先搜索宽度或同等成本。
相反,比例越大,启发式性能越强。
(3)算法描述:(1)将起始节点S放入OPEN表中,计算节点S的值;(2)如果OPEN为空表,则无法退出且没有解决方案;(3)从OPEN表中选择具有最小值的节点。
如果多个节点具有相同的值,当其中一个节点是目标节点时,选择目标节点;否则,任意一个节点被选为节点;(4)从OPEN表中移除节点,并将其放入CLOSED扩展节点表中;(5)如果它是目标节点,它成功退出并获得解决方案;⑥扩展节点以生成其所有后续节点。
人工智能实验一_八数码问题

用A*算法解决八数码问题1 问题描述1.1 待解决问题的解释八数码游戏(八数码问题)描述为:在3×3组成的九宫格棋盘上,摆有八个将牌,每一个将牌都刻有1-8八个数码中的某一个数码。
棋盘中留有一个空格,允许其周围的某一个将牌向空格移动,这样通过移动将牌就可以不断改变将牌的布局。
这种游戏求解的问题是:给定一种初始的将牌布局或结构(称初始状态)和一个目标的布局(称目标状态),问如何移动将牌,实现从初始状态到目标状态的转变。
1.2 问题的搜索形式描述(4要素)初始状态:8个数字将牌和空格在九宫格棋盘上的所有格局组成了问题的状态空间。
其中,状态空间中的任一种状态都可以作为初始状态。
后继函数:通过移动空格(上、下、左、右)和周围的任一棋子一次,到达新的合法状态。
目标测试:比较当前状态和目标状态的格局是否一致。
路径消耗:每一步的耗散值为1,因此整个路径的耗散值是从起始状态到目标状态的棋子移动的总步数。
1.3 解决方案介绍(原理)对于八数码问题的解决,首先要考虑是否有答案。
每一个状态可认为是一个1×9的矩阵,问题即通过矩阵的变换,是否可以变换为目标状态对应的矩阵?由数学知识可知,可计算这两个有序数列的逆序值,如果两者都是偶数或奇数,则可通过变换到达,否则,这两个状态不可达。
这样,就可以在具体解决问题之前判断出问题是否可解,从而可以避免不必要的搜索。
如果初始状态可以到达目标状态,那么采取什么样的方法呢?常用的状态空间搜索有深度优先和广度优先。
广度优先是从初始状态一层一层向下找,直到找到目标为止。
深度优先是按照一定的顺序前查找完一个分支,再查找另一个分支,以至找到目标为止。
广度和深度优先搜索有一个很大的缺陷就是他们都是在一个给定的状态空间中穷举。
这在状态空间不大的情况下是很合适的算法,可是当状态空间十分大,且不预测的情况下就不可取了。
他的效率实在太低,甚至不可完成。
由于八数码问题状态空间共有9!个状态,对于八数码问题如果选定了初始状态和目标状态,有9!/2个状态要搜索,考虑到时间和空间的限制,在这里采用A*算法作为搜索策略。
用A算法解八数码问题

用A*算法解八数码问题1.启发式搜索广度优先搜索和双向广度优先搜索都属于盲目搜索,这在状态空间不大的情况下是很合适的算法,可是当状态空间十分庞大时,它们的效率实在太低,往往都是在搜索了大量无关的状态结点后才碰到解答,甚至更本不能碰到解答。
搜索是一种试探性的查寻过程,为了减少搜索的盲目性引,增加试探的准确性,就要采用启发式搜索了。
所谓启发式搜索就是在搜索中要对每一个搜索的位置进行评估,从中选择最好、可能容易到达目标的位置,再从这个位置向前进行搜索,这样就可以在搜索中省略大量无关的结点,提高了效率。
2.A*算法A*算法是一种常用的启发式搜索算法。
在A*算法中,一个结点位置的好坏用估价函数来对它进行评估。
A*算法的估价函数可表示为:f'(n) = g'(n) + h'(n)这里,f'(n)是估价函数,g'(n)是起点到终点的最短路径值(也称为最小耗费或最小代价),h'(n)是n到目标的最短路经的启发值。
由于这个f'(n)其实是无法预先知道的,所以实际上使用的是下面的估价函数:f(n) = g(n) + h(n)其中g(n)是从初始结点到节点n的实际代价,h(n)是从结点n到目标结点的最佳路径的估计代价。
在这里主要是h(n)体现了搜索的启发信息,因为g(n)是已知的。
用f(n)作为f'(n)(1)g(n)>=g'(n)的近似,也就是用g(n)代替g'(n),h(n)代替h'(n)。
这样必须满足两个条件:(大多数情况下都是满足的,可以不用考虑),且f必须保持单调递增。
(2)h必须小于等于实际的从当前节点到达目标节点的最小耗费h(n)<=h'(n)。
第二点特别的重要。
可以证明应用这样的估价函数是可以找到最短路径的。
3.A*算法的步骤A*算法基本上与广度优先算法相同,但是在扩展出一个结点后,要计算它的估价函数,并根据估价函数对待扩展的结点排序,从而保证每次扩展的结点都是估价函数最小的结点。
八数码问题的启发式计算

八数码问题的启发式计算
八数码问题是一个十分经典的启发式搜索问题,其中启发式计算可以用来评估搜索状态的优先级。
启发式计算是通过一定的估计方法来估测当前状态距离目标状态的距离或成本,从而帮助搜索算法选择最有希望的路径。
在八数码问题中,可以使用曼哈顿距离作为启发式函数。
曼哈顿距离可以通过将当前状态和目标状态分别转化为二维坐标,并计算两个坐标之间的曼哈顿距离来表示状态之间的距离。
具体步骤如下:
1. 将当前状态和目标状态转换为二维坐标:将每个数字在三乘三的游戏板中的位置映射为坐标,例如将数字1映射为(0, 0),数字2映射为(1, 0),以此类推。
2. 对于当前状态的每个数字,计算它与目标状态对应数字的曼哈顿距离:通过求两个数字的坐标的横向距离和纵向距离之和来计算曼哈顿距离。
例如,对于数字4在当前状态的坐标是(1,
2),在目标状态的坐标是(0, 2),则它们之间的曼哈顿距离为1。
3. 将所有数字的曼哈顿距离相加,得到当前状态的启发式函数值:将步骤2中计算的所有数字的曼哈顿距离相加,得到当前状态的启发式函数值。
4. 让搜索算法根据启发式函数值优先选择具有较小启发式函数值的状态进行搜索。
使用曼哈顿距离作为启发式计算的方法可以在一定程度上优化搜索算法的效率,使得更有希望到达目标状态的路径更有可能被优先选择。
但需要注意的是,曼哈顿距离只是一种启发式函
数,不能保证一定能找到最优解,可能存在剪枝路径的情况。
若需要保证找到最优解,可以使用A*算法结合曼哈顿距离进行搜索。
启发式搜索算法

人工智能基础实验报告实验名称:八数码问题姓名:张俊学号:2220092333指导老师:邓安生启发式搜索算法1. 实验内容:使用启发式搜索算法求解8数码问题。
⑴ 编制程序实现求解8数码问题A *算法,采用估价函数()()()()w n f n d n p n ⎧⎪=+⎨⎪⎩, 其中:()d n 是搜索树中结点n 的深度;()w n 为结点n 的数据库中错放的棋子个数;()p n 为结点n 的数据库中每个棋子与其目标位置之间的距离总和。
⑵ 分析上述⑴中两种估价函数求解8数码问题的效率差别,给出一个是()p n 的上界的()h n 的定义,并测试使用该估价函数是否使算法失去可采纳性。
2. 实验目的熟练掌握启发式搜索A *算法及其可采纳性。
3. 实验原理八数码问题是在3行和3列构成的九宫棋盘上放置数码为1到8的8个棋盘,剩下一个空格的移动来不断改变棋盘的布局,求解这类问题的方法是:给定初始布局(即初始状态)和目标布局(即目标状态),定义操作算子的直观方法是为每个棋牌制定一套可能的走步》上,下,左,右四种移动,再根据所定义的启发式搜索函数在搜索过程中选择最合适的操作算子,得到最优的路径。
4.源代码#include <iomanip> #include <stdlib.h> #include <time.h> #include <iostream> #include <stdio.h> #include <conio.h>#include <math.h>//以上为C++源文件 using namespace std; static int space=0; int target[9];class EightNum//定义一个EightNum 类 {public:int num[9];int f;//初始状态与目标状态相比,棋子错放个数int deap;//深度int evalfun;//状态的估价值EightNum *parent;//以下为类内成员函数的声明EightNum(int nnum[9]);int get_evalfun();int get_deapfun();void eval_func(int id);int Canspread(int n);void Spreadchild(int n);void getnum(int num1[9]);void setnum(int num1[9]);void show(void);int operator ==(EightNum& NewEightN);int operator ==(int num2[9]);int Shownum();};//-----------------------以下为EightNum类成员函数定义-----------------// class Stack{private:EightNum * eightnum;public:Stack * next;EightNum * Minf();EightNum * Belong(EightNum * suc);void Putinto(EightNum * suc);};EightNum::EightNum(int nnum[9]){//此函数功能为:初始化num[];for(int i=0;i<9;i++)num[i]=nnum[i];f=0;deap=0;parent=NULL;}int EightNum::get_evalfun(){return evalfun;}int EightNum::get_deapfun(){return deap;}void EightNum::eval_func(int id){//此函数为估价函数int i,qifa;qifa=0;switch(id){case 1:{for(i=0;i<9;i++){if(num[i]!=target[i])qifa++;}break;}case 2:{int j, h1,h2;for(i=0;i<9;i++){for(j=0;j<9;j++){if(num[j]==i)h1=j;if(target[j]==i)h2=j;}qifa+=(int)(fabs((double)(h1/3 - h2/3)) + fabs((double)(h1%3 - h2%3)));}break;}case 3:{int j, h1,h2;for(i=0;i<9;i++){for(j=0;j<9;j++){if(num[j]==i)h1=j;if(target[j]==i)h2=j;}qifa+=(int)(fabs((double)(h1/3 - h2/3)) + fabs((double)(h1%3 - h2%3)));}qifa=3*qifa;break;}default :break;}f=qifa;if(this->parent==NULL) deap=0;else deap=this->parent->deap+1;evalfun=deap+f;}int EightNum::Canspread(int n){//判断空格"0"可否移动int i,flag = 0;for(i = 0;i < 9;i++)if(this->num[i] == 0)break;switch(n){case 1:if(i/3 != 0)flag = 1;break;case 2:if(i/3 != 2)flag = 1;break;case 3:if(i%3 != 0)flag = 1;break;case 4:if(i%3 != 2)flag = 1;break;default:break;}return flag ;}void EightNum::Spreadchild(int n){//扩展child节点的子节点int i,loc,qifa;for(i = 0;i < 9;i++)this->num[i] = this->parent->num[i];for(i = 0;i < 9;i++)if(this->num[i] == 0)break;if(n==0)loc = i%3+(i/3 - 1)*3;else if(n==1)loc = i%3+(i/3 + 1)*3;else if(n==2)loc = i%3-1+(i/3)*3;elseloc = i%3+1+(i/3)*3;qifa = this->num[loc];this->num[i] = qifa;this->num[loc] = 0;}void EightNum::getnum(int num1[9]){ for(int i=0;i<9;i++)num1[i]=num[i];}void EightNum::setnum(int num1[9]){ for(int i=0;i<9;i++)num[i]=num1[i];}void EightNum::show(){//输出函数for(int i=0;i<9;i++){cout<<num[i]<<" ";if((i+1)%3==0)cout<<"\n";}cout<<"--------------------";}int EightNum::Shownum(){if(this == NULL)return 0;else{int n = this->parent->Shownum();this->show();cout<<endl;return n+1;}}int EightNum::operator ==(EightNum& NewEightN){int compere=1;for(int i=0;i<9;i++)if(num[i]!=NewEightN.num[i]){compere=0;break;}if(compere==0) return 0;else return 1;}//-----------------------以下为分函数的定义---------------------////判断是否有解的函数int solve(int num[9],int target[9]){int i,j;int num_con=0,tar_con=0;for(i=0;i<9;i++)for(j=0;j<i;j++){if(num[j]<num[i] && num[j]!=0)num_con++;if(target[j]<target[i] && target[j]!=0)tar_con++;}num_con=num_con%2;tar_con=tar_con%2;if((num_con==0 && tar_con==0)||(num_con==1 && tar_con==1))return 1;elsereturn 0;}EightNum * Stack::Minf(){Stack * qifa =this->next;Stack * min = this->next;Stack * minp = this;EightNum * minx;while(qifa->next != NULL){if((qifa->next->eightnum->get_evalfun()) < (min->eightnum->get_evalfun())){min = qifa->next;minp = qifa;}qifa = qifa->next;}minx = min->eightnum;qifa = minp->next;minp->next = minp->next->next;free(qifa);return minx;}//判断节点是否属于OPEN表或CLOSED表EightNum * Stack::Belong(EightNum * suc){Stack * qifa = this-> next ;if(qifa == NULL)return NULL;while(qifa != NULL){if(suc==qifa->eightnum)return qifa ->eightnum;qifa = qifa->next;}return NULL;}//把节点存入OPEN 或CLOSED 表中void Stack::Putinto(EightNum * suc){Stack * qifa;qifa =(Stack *) malloc(sizeof(Stack));qifa->eightnum = suc;qifa->next = this->next;this->next = qifa;}int BelongProgram(EightNum * suc ,Stack *Open ,Stack *Closed ,EightNum goal,int m ){EightNum * qifa = NULL;int flag = 0;if((Open->Belong(suc) != NULL) || (Closed->Belong(suc) != NULL)){if(Open->Belong(suc) != NULL) qifa = Open->Belong(suc);else qifa = Closed->Belong(suc);flag=1;}else{Open->Putinto(suc);suc->eval_func(m);}return flag;}//扩展后继节点总函数void Spread(EightNum * suc, Stack * Open, Stack * Closed, EightNum goal,int m){int i;EightNum * child;for(i = 0; i < 4; i++){if(suc->Canspread(i+1)){space++;child = (EightNum *) malloc(sizeof(EightNum));child->parent = suc;child->Spreadchild(i);child->eval_func(m);if(BelongProgram(child, Open, Closed, goal,m)) //判断子节点是否属于OPEN或CLOSED表free(child);}}}//执行函数EightNum * Process(EightNum * org, EightNum goal, Stack * Open, Stack * Closed,int m){while(1){if(Open->next == NULL)return NULL;EightNum * minf =Open->Minf();Closed->Putinto(minf);if((*minf)==goal)return minf;Spread(minf, Open, Closed, goal,m);}}//------------------------A*算法搜索函数----------------------//void A(int id,EightNum start,EightNum Target){EightNum * result;space=0;float time;Stack *Open = (Stack *) malloc(sizeof(Stack));Open->next = NULL;Stack *Closed = (Stack *) malloc(sizeof(Stack));Closed->next = NULL;clock_t startt,finisht;startt=clock();//开始时间start.eval_func(id);Open->Putinto(&start);result = Process(&start, Target, Open, Closed,id); //进行剩余的操作cout<<"\n搜索过程:\n"<<result->Shownum()<<endl;finisht=clock();time=(float)(finisht-startt);cout<<endl<<id<<"算法处理结果:所耗时间:";cout<<time;cout<<"ms, ";cout<<"所耗空间:";cout<<space;cout<<"块, "<<endl<<endl;}//-----------------------------主函数-----------------------------//int main(void)//主函数{int i,j;int flag;int num[9];int error;do{error=0;cout<<"请输入八数码问题的初始状态(0代表空格,“棋子”间用空格隔开):"<<endl;for(i=0;i<9;i++){flag=0;cin>>num[i];for(j=0;j<i;j++)if(num[j]==num[i])flag=1;if(num[i]<0||num[i]>8||flag==1){error++;}}if(error!=0)cout<<"输入数据错误!请重新输入!"<<endl;}while(error!=0);//输入八数码问题的初始状态(0代表空格,“棋子”间用空格隔开);int error1;do{error1=0;cout<<"请输入新的目标状态(用0代表空格,“棋子”间用空格隔开):"<<endl;for(i=0;i<9;i++){flag=0;cin>>target[i];for(j=0;j<i;j++)if(target[j]==target[i])flag=1;if(target[i]<0||target[i]>9||flag==1){error1++;}}if(error1!=0)cout<<"输入数据错误!请重新输入!"<<endl;}while(error1!=0);//输入八数码问题的目标状态(用0代表空格,中间用空格隔开);EightNum start(num),Target(target);int m=solve(num,target);//判断初始状态到目标状态是否有解,有解返回1,误解返回0;if(m==0){cout<<"此状态无解!"<<endl;return 0;}int id=0;while(id!=3){cout<<"1. 错放的棋子个数为;\n2.每个棋子与目标位置之间的距离总和为;"<<endl;cout<<"3.结束,退出程序!"<<endl;cout<<"\n请选择功能,分别输入“1”“2”“3”进行选择:"<<endl;cin>>id;switch(id){case 1:{cout<<"错放的棋子个数结果为:\n(以下逐一展示搜索过程:)"<<endl;A(1,start,Target);break;}case 2:{cout<<"每个棋子与其目标位置之间的距离总和为:\n(以下逐一展示搜索过程:)"<<endl;A(2,start,Target);break;}default: break;}}cout<<"啊啊….程序结束!!";}实验截图实验中遇到的问题1:开始程序只能运行一种方式即按照错位个数搜索,后经过查找相关资料,修改后可程序可进行选择,两种方法结合在一起根据选择运行。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
启发式搜索1. 介绍八数码问题也称为九宫问题。
在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。
棋盘上还有一个空格(以数字0来表示),与空格相邻的棋子可以移到空格中。
要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。
所谓问题的一个状态就是棋子在棋盘上的一种摆法。
解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。
2. 使用启发式搜索算法求解8数码问题。
1) A ,A 星算法采用估价函数()()()()w n f n d n p n ⎧⎪=+⎨⎪⎩, 其中:()d n 是搜索树中结点n 的深度;()w n 为结点n 的数据库中错放的棋子个数;()p n 为结点n 的数据库中每个棋子与其目标位置之间的距离总和。
2)宽度搜索采用f(i)为i 的深度,深度搜索采用f(i)为i 的深度的倒数。
3. 算法流程① 把起始节点S 放到OPEN 表中,并计算节点S 的)(S f ;② 如果OPEN 是空表,则失败退出,无解;③ 从OPEN 表中选择一个f 值最小的节点i 。
如果有几个节点值相同,当其中有一个 为目标节点时,则选择此目标节点;否则就选择其中任一个节点作为节点i ;④ 把节点i 从 OPEN 表中移出,并把它放入 CLOSED 的已扩展节点表中;⑤ 如果i 是个目标节点,则成功退出,求得一个解;⑥ 扩展节点i ,生成其全部后继节点。
对于i 的每一个后继节点j :计算)(j f ;如果j 既不在OPEN 表中,又不在CLOCED 表中,则用估价函数f 把它添入OPEN 表中。
从j 加一指向其父节点i 的指针,以便一旦找到目标节点时记住一个解答路径;如果j 已在OPEN 表或CLOSED 表中,则比较刚刚对j 计算过的f 和前面计算过的该节点在表中的f 值。
如果新的f 较小,则(I)以此新值取代旧值。
(II)从j 指向i ,而不是指向他的父节点。
(III)如果节点j 在CLOSED 表中,则把它移回OPEN 表中。
⑦ 转向②,即GOTO ②。
4. 估价函数计算一个节点的估价函数,可以分成两个部分:1、 已经付出的代价(起始节点到当前节点);2、 将要付出的代价(当前节点到目标节点)。
节点n 的估价函数)(n f 定义为从初始节点、经过n 、到达目标节点的路径的最小代价的估计值,即)(*n f = )(*n g + )(*n h 。
)(*n g 是从初始节点到达当前节点n 的实际代价;)(*n h 是从节点n 到目标节点的最佳路径的估计代价,体现出搜索过程中采用的启发式信息(背景知识),称之为启发函数。
)(*n g 所占的比重越大,越趋向于宽度优先或等代价搜索;反之,)(*n h 的比重越大,表示启发性能就越强。
5. 实验代码为方便起见,目标棋局为不变(1)以下代码估价函数为深度+错放棋子个数 (2) 若估价函数为深度+每个棋子与其目标位置之间的距离总和,则加入估价函数int calvalue1(int a[]) //不在位棋子数{int c = 0;int b=0;for (int i = 0;i <= 8;i++)for (int j = 0;j <= 8;j++)if (a[i] = goal[j])if (goal[j] != 0)c=c+abs(i%3-j%3)+abs((i- i%3)/3+(j- j%3)/3);return c;}(3)宽度搜索采用OPEN->jiedian.f = depth;(4) 深度搜索采用OPEN->jiedian.f = -depth;源代码:1. #include "stdio.h"2.3. int goal[9] = { 1,2,3,8,0,4,7,6,5 }, sgoal[9];//goal 为棋盘的目标布局,并用中间状态sgoal与之比较4.5.struct Board6.{7.int shuzu[9];8.int d, f, e;//d:深度;f:启发函数;e:记录前一次的扩展节点9.};10.11.struct NodeLink12.{13.Board jiedian;14.NodeLink *parent;15.NodeLink *previous;16.NodeLink *next;17.NodeLink *path;18.};19.//更新纪录八数码的状态20.void setboard(int a[], int b[], int flag) //flag=0,写棋子;flag=1,写棋盘21.{22.for (int i = 0;i <= 8;i++)23.if (flag)24.a[b[i]] = i;25.else26.b[a[i]] = i;27.}28.//计算启发值的函数29.int calvalue(int a[]) //不在位棋子数30.{31.int c = 0;32.for (int i = 0;i <= 8;i++)33.if (a[i] != goal[i])34.if (goal[i] != 0)35.c++;36.return c;37.}38.//生成一个新节点的函数39.NodeLink *newnode(NodeLink *TEM, int depth, int flag)40.{41.NodeLink *temp = new NodeLink;42.for (int i = 0;i <= 8;i++)43.temp->jiedian.shuzu[i] = TEM->jiedian.shuzu[i];44.switch (flag)45.{46.case 1:47.{48.temp->jiedian.shuzu[0]--;49.temp->jiedian.shuzu[sgoal[temp->jiedian.shuzu[0]]]++; //向左移50.break;51.}52.case 2:53.{54.temp->jiedian.shuzu[0]++;55.temp->jiedian.shuzu[sgoal[temp->jiedian.shuzu[0]]]--; //向右移56.break;57.}58.case 3:59.{60.temp->jiedian.shuzu[0] -= 3;61.temp->jiedian.shuzu[sgoal[temp->jiedian.shuzu[0]]] += 3; //向上移62.break;63.}64.case 4:65.{66.temp->jiedian.shuzu[0] += 3;67.temp->jiedian.shuzu[sgoal[temp->jiedian.shuzu[0]]] -= 3; //向下移68.break;69.}70.}71.temp->jiedian.d = depth + 1;72.setboard(sgoal, temp->jiedian.shuzu, 1);73.temp->jiedian.f = temp->jiedian.d + calvalue(sgoal);74.temp->jiedian.e = flag;75.temp->parent = TEM;76.return temp;77.}78.//把新节点加入OPEN队列79.NodeLink *addnode(NodeLink *head, NodeLink *node) //把node插入到head链中80.{81.NodeLink *TEM;82.TEM = head;83.head = node;84.head->next = TEM;85.head->previous = NULL;86.if (TEM)87.TEM->previous = head; //TEM已为空,无需操作88.return head;89.}90.91.//求启发值最小的结点92.NodeLink *minf(NodeLink *head)93.{94.NodeLink *min, *forward;95.min = head;96.forward = head;97.while (forward)98.{99.if (min->jiedian.f>forward->jiedian.f)100.min = forward;101.forward = forward->next;102.}103.return min;104.}105.106.int main()107.{108.int depth = 0;109.int source[9];110.int i, j;111.112.NodeLink *OPEN = new NodeLink;113.NodeLink *TEMP, *TEM;114.115.printf("请输入初始状态:\n");116.for (i = 0;i<9;i++)117.scanf_s("%d", &source[i]);118.119.setboard(source, OPEN->jiedian.shuzu, 0);120.OPEN->jiedian.d = depth;121.OPEN->jiedian.e = 0;122.OPEN->jiedian.f = depth + calvalue(source);123.OPEN->next = NULL;124.OPEN->previous = NULL;125.OPEN->parent = NULL;126.127.while (OPEN)128.{129.TEMP = minf(OPEN); //求具有最小启发值的节点130.setboard(sgoal, TEMP->jiedian.shuzu, 1); //写棋盘131.if (!calvalue(sgoal))132.break;133.if (TEMP != OPEN) //如果不是第一个节点134.{135.TEMP->previous->next = TEMP->next;136.TEMP->next->previous = TEMP->previous;137.}138.else //是第一个节点139.{140.if (OPEN->next) //如果还有节点141.{142.OPEN = OPEN->next;143.OPEN->previous = NULL;144.}145.else OPEN = NULL; //否则置为空146.}147.148.if (TEMP->jiedian.shuzu[0] - 1 >= 0 && TEMP->jiedian.e != 2) //防止棋子回到原状态149.OPEN = addnode(OPEN, newnode(TEMP, depth, 1));150.if (TEMP->jiedian.shuzu[0] + 1 <= 8 && TEMP->jiedian.e != 1)151.OPEN = addnode(OPEN, newnode(TEMP, depth, 2));152.if (TEMP->jiedian.shuzu[0] - 3 >= 0 && TEMP->jiedian.e != 4)153.OPEN = addnode(OPEN, newnode(TEMP, depth, 3));154.if (TEMP->jiedian.shuzu[0] + 3 <= 8 && TEMP->jiedian.e != 3)155.OPEN = addnode(OPEN, newnode(TEMP, depth, 4));156.depth++;157.}158.159.if (OPEN) //如有解,则打印出解的步骤160.{161.TEMP->path = NULL;162.while (TEMP->parent) //每次回溯父节点,生成路径163.{164.TEMP->parent->path = TEMP;165.TEMP = TEMP->parent;166.}167.j = 0;168.while (TEMP->path)169.{170.setboard(sgoal, TEMP->jiedian.shuzu, 1);171.printf("第%d步:\n", j);172.for (i = 0;i <= 2;i++)173.printf(" %d", sgoal[i]);174.printf(" \n");175.for (i = 3;i <= 5;i++)176.printf(" %d", sgoal[i]);177.printf("\n");178.for (i = 6;i <= 8;i++)179.printf(" %d", sgoal[i]);180.printf("\n");181.TEMP = TEMP->path;182.j++;183.}184.setboard(sgoal, TEMP->jiedian.shuzu, 1);185.printf("第%d步:\n", j);186.for (i = 0;i <= 2;i++)187.printf(" %d", sgoal[i]);188.printf("\n");189.for (i = 3;i <= 5;i++)190.printf(" %d", sgoal[i]);191.printf("\n");192.for (i = 6;i <= 8;i++)193.printf(" %d", sgoal[i]);194.printf("\n");195.}196.else197.printf("无法求解!");198.}(1)以上代码估价函数为深度+错放棋子个数(2) 若估价函数为深度+每个棋子与其目标位置之间的距离总和,则函数改为int calvalue(int a[]) //不在位棋子数{int c = 0;int b=0;for (int i = 0;i <= 8;i++)for (int j = 0;j <= 8;j++)if (a[i] = goal[j])if (goal[j] != 0)c=c+abs(i%3-j%3)+abs((i- i%3)/3+(j- j%3)/3);return c;}(3)宽度搜索采用OPEN->jiedian.f = depth;(4) 深度搜索采用OPEN->jiedian.f = -depth;6.输出结果:(输入为:)目标状态为:(1)估价函数为深度+错放棋子个数(2) 估价函数为深度+每个3棋子与其目标位置之间的距离总和(3)宽度搜索采用OPEN->jiedian.f = depth;(4) 深度搜索采用OPEN->jiedian.f = -depth;精品文档。