启发式搜索
一、 实验目的

  1. 理解人工智能系统中搜索策略的含义。
  2. 熟悉盲目搜索和启发式搜索算法的实际应用。
  3. 结合八数码问题的建模、求解及编程语言的应用,掌握启发式搜索算法的应用。
    二、 实验原理
    启发式搜索(Heuristically Search)又称为有信息搜索(Informed Search),
    它是利用问题拥有的启发信息来引导搜索,达到减少搜索范围、降低问题复杂度的目的。启发式策略可以通过指导搜索向最有希望的方向前进,降低了复杂性。通过删除某些状态及其延伸,启发式算法可以消除组合爆炸,并得到令人能接受的解(通常并不一定是最优解)。
    然而,启发式策略是极易出错的。在解决问题的过程中启发仅仅是下一步将
    要采取措施的一个猜想,常常根据经验和直觉来判断。由于启发式搜索只有有限的信息(比如当前状态的描述),要想预测进一步搜索过程中状态空间的具体行为则很难。这是启发式搜索固有的局限性,一般说来,启发信息越强,扩展的无用节点就越少。引入强的启发信息,有可能大大降低搜索工作量,但不能保证找到 最小耗散值的解路径(最佳路径)。
    用于评价节点重要性的函数称为估价函数,其一般形式为
    f(x) = g(x) + h(x)
    式中:g(x)为从初始节点到节点x付出的实际代价;h(x)为从节点x到目标
    节点的最优路径的估计代价。启发性信息主要体现在h(x)中,其形式要根据问 题的特性来确定。在启发式搜索中,估计函数的定义是十分重要的。如定义不当, 则上述搜索算法不一定能找到问题的解,即使找到解,也不一定是最优的。
    三、 实验内容
    八数码问题也称为九宫问题。在3×3的方格棋盘上,摆放着1到8这八个数码,
    其中有1个方格是空的,初始状态如图1所示,要求对空格执行空格左移、右移、上移和下移这四个操作使得棋盘从初始状态到目标状态。
    例如:
    1 2 3
    8 0 4
    7 6 5
    (a) 初始状态 (b) 目标状态
    图1 八数码问题示意图
    四、 实验过程
    (1) 算法描述
    对启发式搜索算法,又可根据搜索过程中选择扩展节点的范围,将其分为全局择优搜索算法和局部择优搜索算法。
    在全局择优搜索中,每当需要扩展节点时,总是从 Open 表的所有节点中选择一个估价函数值最小的节点进行扩展。其搜索过程可能描述如下:
    ( 1 )把初始节点 S0 放入 Open 表中, f(S0)=g(S0)+h(S0) ;
    ( 2 )如果 Open 表为空,则问题无解,失败退出;
    ( 3 )把 Open 表的第一个节点取出放入 Closed 表,并记该节点为 n ;
    ( 4 )考察节点 n 是否为目标节点。若是,则找到了问题的解,成功退出;
    ( 5 )若节点 n 不可扩展,则转到第 (2) 步;
    ( 6 )扩展节点 n ,生成子节点 ni ( i =1,2, …… ) ,计算每一个子节点的估价值 f( ni ) ( i =1,2, …… ) ,并为每一个子节点设置指向父节点的指针,然后将这些子节点放入 Open 表中;
    ( 7 )根据各节点的估价函数值,对 Open 表中的全部节点按从小到大的顺序重新进行排序;
    ( 8 )转第 (2) 步。
    (2) 算法分析
    在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)的近似,也就是用g(n)代替g’(n),h(n)代替h’(n)。这样必须满足两个条件:(1)g(n)>=g’(n)(大多数情况下都是满足的,可以不用考虑),且f必须保持单调递增。(2)h必须小于等于实际的从当前节点到达目标节点的最小耗费h(n)<=h’(n)。第二点特别的重要。可以证明应用这样的估价函数是可以找到最短路径的。
    A算法的步骤
    A
    算法基本上与广度优先算法相同,但是在扩展出一个结点后,要计算它的估价函数,并根据估价函数对待扩展的结点排序,从而保证每次扩展的结点都是估价函数最小的结点。
    A算法的步骤如下:
    1)建立一个队列,计算初始结点的估价函数f,并将初始结点入队,设置队列头和尾指针。
    2)取出队列头(队列头指针所指)的结点,如果该结点是目标结点,则输出路径,程序结束。否则对结点进行扩展。
    3)检查扩展出的新结点是否与队列中的结点重复,若与不能再扩展的结点重复(位于队列头指针之前),则将它抛弃;若新结点与待扩展的结点重复(位于队列头指针之后),则比较两个结点的估价函数中g的大小,保留较小g值的结点。跳至第五步。
    4)如果扩展出的新结点与队列中的结点不重复,则按照它的估价函数f大小将它插入队列中的头结点后待扩展结点的适当位置,使它们按从小到大的顺序排列,最后更新队列尾指针。
    5)如果队列头的结点还可以扩展,直接返回第二步。否则将队列头指针指向下一结点,再返回第二步。
    八数码问题的A
    算法的估价函数
    估价函数中,主要是计算h,对于不同的问题,h有不同的含义。那么在八数码问题中,h的含意是各什么?八数码问题的一个状态实际上是数字0~8的一个排列,用一个数组p[9]来存储它,数组中每个元素的下标,就是该数在排列中的位置。例如,在一个状态中,p[3]=7,则数字7的位置是3。如果目标状态数字3的位置是8,那么数字7对目标状态的偏移距离就是3,因为它要移动3步才可以回到目标状态的位置。
    八数码问题中,每个数字可以有9个不同的位置,因此,在任意状态中的每个数字和目标状态中同一数字的相对距离就有9*9种,可以先将这些相对距离算出来,用一个矩阵存储,这样只要知道两个状态中同一个数字的位置,就可查出它们的相对距离,也就是该数字的偏移距离:
    0 1 2 3 4 5 6 7 8
    0 0 1 2 1 2 3 2 3 4
    1 1 0 1 2 1 2 3 2 3
    2 2 1 0 3 2 1 4 3 2
    3 1 2 3 0 1 2 1 2 3
    4 2 1 2 1 0 1 2 1 2
    5 3 2 1 2 1 0 3 2 1
    6 2 3 4 1 2 3 0 1 2
    7 3 2 3 2 1 2 1 0 1
    8 4 3 2 3 2 1 2 1 0
    例如在一个状态中,数字8的位置是3,在另一状态中位置是7,那么从矩阵的3行7列可找到2,它就是8在两个状态中的偏移距离。
    估价函数中的h就是全体数字偏移距离之和。
    显然,要计算两个不同状态中同一数字的偏移距离,需要知道该数字在每个状态中的位置,这就要对数组p[9]进行扫描。由于状态发生变化,个数字的位置也要变化,所以每次计算h都沿线扫描数组,以确定每个数字在数组中的位置。为了简化计算,这里用一个数组存储状态中各个数字的位置,并让它在状态改变时随着变化,这样就不必在每次计算h时,再去扫描状态数组。
    例如,某个状态中,数字5的位置是8,如果用数组r[9]存储位置,那么就有r[5]=8。
    现在用数组r[9]存储当前状态的数字位置,而用s[9]存储目标状态的数字位置,那么当前状态数字i对目标状态的偏移距离就是矩阵中r[i]行s[i]列对应的值。
    可解性分析:八数码问题的一个状态实际上是0~9的一个排列,对于任意给定的初始状态和目标,不一定有解,也就是说从初始状态不一定能到达目标状态。因为排列有奇排列和偶排列两类,从奇排列不能转化成偶排列或相反。
    如果一个数字0~8的随机排列871526340,用F(X)表示数字X前面比它小的数的个数,全部数字的F(X)之和为Y=∑(F(X)),如果Y为奇数则称原数字的排列是奇排列,如果Y为偶数则称原数字的排列是偶排列。
    例如871526340这个排列的
    Y=0+0+0+1+1+3+2+3+0=10
    10是偶数,所以他偶排列。871625340
    Y=0+0+0+1+1+2+2+3+0=9
    9是奇数,所以他奇排列。
    因此,可以在运行程序前检查初始状态和目标状态的窘是否相同,相同则问题可解,应当能搜索到路径。否则无解。
    (3) 程序流程图

(4) 搜索树与对应的OPEN表和CLOSED表

五、 实验结果

六、源代码

#include<iostream>
#include<stdio.h>
#include<cmath>
using namespace std;
int open_cnt=0; //记录open表中每一个扩展的节点
int open_node_cnt;//open表节点个数 struct Node{int a[3][3];int x,y;int f,g,h;    //启发式搜索的函数 int flag; //上一次移动方向 Node *father;
}start,end;struct Open_Close{int f;Node *np;
}open[10000],close[10000];bool isable(){/*判断是否有解,逆序数之和奇偶性相同,有解  用F(X)表示数字X前面比它小的数的个数,全部数字的F(X)之和为Y=∑(F(X)),如果Y为奇数则称原数字的排列是奇排列,如果Y为偶数则称原数字的排列是偶排列。*/ int s[9],e[9];int tf=0,ef=0;int k=0;for(int i=0;i<3;i++){for(int j=0;j<3;j++){s[k]=start.a[i][j];   //将初始状态存入s[]数组中 e[k]=end.a[i][j];  //将目标状态存入e[]数组中  k++;}}for(int i=0;i<9;i++){for(int j=0;j<i;j++){if(s[i]>s[j]&&s[j]!=0) tf+=1;  //记录对应位置上前面比他小 的数的个数if(e[i]>e[j]&&e[j]!=0) ef+=1;}}if((tf%2==1&&ef%2==1)||(tf%2==0&&ef%2==0)) return true;    //奇偶相同,则有解 else return false;}int a_start_h(Node *node){  //求 h() 计算每个数字当前状态与最终状态的曼哈顿距离 之和作为代价 int old_x,old_y,end_x,end_y;int h=0;for(int k=1;k<9;k++){for(int i=0;i<3;i++){for(int j=0;j<3;j++){if(node->a[i][j]==k){    //找到每个数在对数组中的对应位置 old_x=i;old_y=j;}if(end.a[i][j]==k){  //目标状态的数在数组中的对应位置 end_x=i;end_y=j;}}}h+=abs(old_x-end_x)+abs(old_y-end_y); //计算和目标状态偏移量之和 }   return h;
}void input(){ printf("请输入初始状态:\n");              //输入 for(int i=0;i<3;i++){for(int j=0;j<3;j++){cin>>start.a[i][j];if(start.a[i][j]==0){ //记录0位置,即空位置 start.x=i;start.y=j;}}}printf("请输入目标状态:\n"); for(int i=0;i<3;i++){for(int j=0;j<3;j++){cin>>end.a[i][j];if(end.a[i][j]==0){ //记录0位置,即空位置 end.x=i;end.y=j;}}}start.g=0;    //函数g start.h=a_start_h(&start);   //函数h start.f=start.g+start.h;    //函数f
}int show(Node *node){    //显示 Node *p = node;if(p==&start) return 1; else show(p->father);for(int i=0;i<3;i++){for(int j=0;j<3;j++){cout<<p->a[i][j]<<" ";}printf("\n");}cout<<"====================================\n";
}bool isend(Node *node){         //判断是否为目标节点 for(int i=0;i<3;i++){for(int j=0;j<3;j++){if(node->a[i][j]!=end.a[i][j])   //对应位置元素不等于目标状态的数,返回false return false;}}return true;
} void sort(Open_Close *open){      //open表排序 int min=99999,min_flag=0; Open_Close temp;for(int i=0;i<=open_cnt;i++){if(min>open[i].f&&open[i].f>0){ //找出最小节点 min=open[i].f;min_flag=i;    }}temp=open[min_flag]; //调整亲子关系 open[min_flag]=open[0];open[0]=temp;
}void move(int flag,Node *node){   //向四个方向扩展 int temp;if(flag==1&&node->x>0){   //靠右边,向左移动 Node *n = new Node();for(int i=0;i<3;i++){for(int j=0;j<3;j++){n->a[i][j]=node->a[i][j];  //n接收node矩阵 } }n->a[node->x][node->y]=node->a[node->x-1][node->y];   //左移 n->a[node->x-1][node->y]=0; //对应位置置0,即置空 n->x=node->x-1;   //改变x的值 n->y=node->y;    //改变y的值 n->flag=3;  //左边n->father=node;n->g=node->g+1;             //  求 g() n->h=a_start_h(n);n->f=n->g+n->h;open_cnt++;    //扩展的节点 open_node_cnt++;open[open_cnt].np=n;     //添加到open表open[open_cnt].f=n->f;  //  求 f() }else if(flag==2&&node->y<2){ //靠下,上移 Node *n = new Node();for(int i=0;i<3;i++){for(int j=0;j<3;j++){n->a[i][j]=node->a[i][j];}}n->a[node->x][node->y]=node->a[node->x][node->y+1];    //下移n->a[node->x][node->y+1]=0;n->x=node->x;n->y=node->y+1;n->flag=4; n->father=node;n->g=node->g+1;             //  求 g() n->h=a_start_h(n);n->f=n->g+n->h;open_cnt++;open_node_cnt++;open[open_cnt].np=n;        //添加到open表open[open_cnt].f=n->f;  //  求 f()}else if(flag==3&&node->x<2){   //靠左边,向右移 Node *n = new Node();for(int i=0;i<3;i++){for(int j=0;j<3;j++){n->a[i][j]=node->a[i][j];}}n->a[node->x][node->y]=node->a[node->x+1][node->y];  //右移n->a[node->x+1][node->y]=0; n->x=node->x+1;n->y=node->y;   n->flag=1; n->father=node;n->g=node->g+1;             //  求 g() n->h=a_start_h(n);n->f=n->g+n->h;open_cnt++;open_node_cnt++;open[open_cnt].np=n;        //添加到open表open[open_cnt].f=n->f;  //  求 f() }else if(flag==4&&node->y>0){  //靠上,下移 Node *n = new Node();for(int i=0;i<3;i++){for(int j=0;j<3;j++){n->a[i][j]=node->a[i][j];}}n->a[node->x][node->y]=node->a[node->x][node->y-1];n->a[node->x][node->y-1]=0;n->x=node->x;n->y=node->y-1;     n->flag=2; n->father=node;n->g=node->g+1;             //  求 g() n->h=a_start_h(n);n->f=n->g+n->h;open_cnt++;open_node_cnt++;open[open_cnt].np=n;        //添加到open表open[open_cnt].f=n->f;  //  求 f() }
} void expand(Node *node){    //节点扩展    for(int i=1;i<5;i++){ //向4个方向扩展 if(i!=node->flag) move(i,node);}
}int main(){input();open[0].np = &start;   //start放入open表 open_node_cnt=1;    //从开始状态 if(isable()){while(true){   //open表不为空 if(isend(open[0].np)){printf("\n路径:\n");show(open[0].np);break;}expand(open[0].np);//扩展最优节点的子节点 open[0].np=NULL;open[0].f=-1;open_node_cnt--; //open表数量-1 sort(open);   //open表排序}}else cout<<"无解";system("pause");
}

人工智能----八数码问题(启发式搜索)相关推荐

  1. 八数码问题a*算法c语言,八数码问题的A*算法求解

    A*算法是启发式搜素算法中较为出名和高效的算法之一,其关键是对于启发式函数的实际,启发式函数h(x)需要尽可能的接近实际的 .下面是人工智能八数码问题使用A*算法求解的源码放在博客上记录一下.程序使用 ...

  2. 人工智能作业 八数码启发式搜索与bfs比较

    问题描述 3×3九宫棋盘,放置数码为1 -8的8个棋牌,剩下一个空格,只能通过棋牌向空格的移动来改变棋盘的布局. 要求:根据给定初始布局(即初始状态)和目标布局(即目标状态),如何移动棋牌才能从初始布 ...

  3. A*算法解决八数码问题 人工智能原理实验报告 启发式搜索 python

    目录 一.实验主要步骤 ①.设计界面输入规则 ②.判断是否有解 ③.求解 二.实验结果展示 三.附录 完整实验程序代码: 一.实验主要步骤 ①.设计界面输入规则 有且仅有9位数字代表数码和空格,从左到 ...

  4. 【八数码问题】基于状态空间法的知识表示与状态搜索:无信息搜索(BFS/DFS) 启发式搜索(A*)

    前言 一.问题引入 二.状态空间法 1. 知识及其表示 2. 状态空间法定义 3. 问题求解 三.基于状态空间搜索法解决八数码问题 1. 八数码问题的知识表示 2. 状态空间图搜索 1. 无信息搜索 ...

  5. 《人工智能》实验二——搜索技术(八数码问题)

    必须记住下一步还可以走哪些点--OPEN表(记录还没有扩展的点) 必须记住哪些点走过了--CLOSED表(记录已经扩展的点 广度优先搜索 在应用BFS算法进行八数码问题搜索时需要open和closed ...

  6. 人工智能作业 - A*算法程序求解八数码

    文章目录 A*算法 简介 思路 A*伪代码 八数码问题 题目描述 注意事项 实验过程 解决思路 伪代码 二维压缩为一维 检查是否有解 其他 代码实现 h1和h2的对比 关于曼哈顿距离 参考链接 A*算 ...

  7. 人工智能导论(2)——启发式算法(八数码问题)

    实验要求 用A*算法来解决八数码问题. 代码实现 #include<iostream> #include<stdio.h> #include<cmath> usin ...

  8. 【人工智能】八数码问题:广度搜索、深度搜索

    应用广度搜索BFS和深度搜索DFS解决八数码问题,广度搜索和深度搜索都是盲目搜索,相关理论知识,算法过程:问题求解:状态空间图和盲目搜索. 参考:7种方法求解八数码问题 Python实现A*算法解决N ...

  9. 人工智能:(C语言)采用状态空间法求解八数码问题

    实验要求:八数码难题也称九宫问题,它是在3×3的方格棋盘上,分别放置了表有数字1.2.3.4.5.6.7.8的八张牌,初始状态S0,目标状态Sg,要求程序能输入任意的初始状态和目标状态,要求通过空格来 ...

最新文章

  1. 2021年大数据HBase(三):HBase数据模型!!!【建议收藏】
  2. 底盘编码数据解算ROS的odom数据
  3. UVA 1366 Martian Mining DP
  4. 天线的基本原理和概念
  5. Python-GUI编程PyQT5案例:注册界面扇形菜单动画及相关事件处理
  6. 关于memcached的收集
  7. 十五、导航,头部,CSS基础
  8. 【华为云技术分享】区块链与数据库如何结合?
  9. 关于C#的GetHashCode
  10. Coursera, Big Data 3, Integration and Processing (week 1/2/3)
  11. wps 宏 禁用_WPS表格如何解除宏禁用
  12. 相比android原生更好看,而且功能齐全的框架,XUI框架的配置和使用?
  13. gamit数据处理—5.遇到的问题
  14. linux取得redis数据库中hash类型的所有feild或者所有value
  15. 解决android studio打包后安装APK提示“签名不一致,该应用可能已被修改。“
  16. [附源码]java+ssm计算机毕业设计个人所得税服务系统0dol2(源码+程序+数据库+部署)
  17. pixi.js 制作 流星雨效果
  18. List去重 排序 多list 穿插筛选 (java 8)
  19. MacDroid for Mac(安卓手机文件传输助手)
  20. c语言循环左移程序,C语言中关于循环左移和循环右移

热门文章

  1. 计算机应用 胡丹,正高级
  2. 优秀的项目经理需要具备哪些品质?
  3. 苹果手机用android,用习惯了苹果手机还能从苹果换到安卓吗?
  4. 玖逸云黑v1.4-更高级的云黑系统
  5. 用前端代码编写一个动态的罗盘时钟
  6. C++ fabs和abs区别
  7. 定时任务二:No thread-bound request found
  8. mysql恢复drop的表_mysql恢复drop表
  9. linux 移动磁盘空间,如何将Linux下移动磁盘空间?
  10. 软件分享系列之【AE 下载安装】并持续分享中...