博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
实用算法实现-第 14 篇 启发式搜索
阅读量:6273 次
发布时间:2019-06-22

本文共 8536 字,大约阅读时间需要 28 分钟。

14.1    A*搜索

A*(A star)算法是一种很好的树搜索策略,在许多人工智能的书籍中有所介绍。比如《人工智能,一种现代方法》和《人工智能,复杂问题求解的结构和策略》。

文介绍到,A*算法使用估价函数F(N)来估算从初始状态S到当前状态N,再到目标状态T需要的最小步数。有公式:

F(N) = G(N) + H(N)

其中,G(N)是起始状态S到当前状态的实际代价G(N),它比最佳路径的代价G*(N)大。

H(N)是从当前状态到目标状态T的实际代价的估算值H*(N),它比实际代价H(N)大。

对于一个起始状态到当前状态的实际路径,G(N)是已经决定的。H(N)的选择则至关重要。有两个限制:

1. H(N)一定要小于等于当前结点N至目标结点T的实际代价。

2. 任意结点的F值必须大于其父辈状态的F值,即F单调递增。

当估价函数F满足以上两个限制条件,则启发式搜索算法一定能得出问题的最优解。

14.1.1   实例

PKU JudgeOnline, 1077, Eight.

14.1.2   问题描述

15数码游戏就是完成类似下面的转换的一种游戏:

1  2 3  4    1 2  3  4   1  2  3 4    1  2 3  4

5  6 7  8    5 6  7  8   5  6  7 8    5  6 7  8

9  x 10 12   9 10  x 12    9 10 11 12    9 10 11 12

1314 11 15   13 14 11 15   13 14 x 15   13 14 15  x

           r->           d->           r->

    8数码游戏类似,只不过只有8个数码而已。

    给出8数码游戏的一个初始状态,求达到目标状态的一个解,不要求是最优解。如果没有解,输出“unsolvable”。

14.1.3   输入

2 3  4  1 5  x  7 6  8

1.1.4   输出

ullddrurdllurdruldr

14.1.5   分析

《人工智能,一种现代方法》对使用A*算法解决八数码问题进行了深入的分析。提出了两种启发函数的策略:

1. h1 = 不在位的棋子的总数。

2. h2 = 所有棋子到其目标位置的曼哈顿距离的总和。

并且通过实验,得到h2的性能总是优于h1。

具体实现中,用到最小堆实现的优先队列。

这里还需要用到一个排列到整数的映射方法,以判断一种状态是不是已经遍历过。一个具体的映射方法如下:

把9看成插到8的排列中,那么有9种插法,可以把362880等分成9段,分别代表一个位置,每段递归处理。

令pos(a)为数字a在排列中排在a前面并且小于a的数字的个数,那么有伪代码:

int n=1;

for (int i=1; i<10; i++)

{

     n+=pos(i)*(i-1)!;

}

     下面程序中int calPos1(node *temp)和int calPos(node *temp)就是两种映射方法。

也许中介绍的排列的“反序”概念,能很好地用来解释上面这个算法。

14.1.6   程序

#include <iostream> #include <stdio.h> #include <string.h> using namespace std; bool visited[362880]; #define maxNum 10000000 #define MAX 0x7F7F7F7F struct tile{ int x; int y; }; #define maxLength 100//貌似最数码游戏的解长度为... struct node{ tile pos[3][3]; int key; charop[maxLength]; int blankX; int blankY; }; nodeminHeap[maxNum]; int heapSize; void minHeapify(int i) { int l; int r; int min; node temp; l = i * 2; r = i * 2 + 1; min = i; if((l <=heapSize)&& (minHeap[l].key < minHeap[min].key)){ min = l; } if((r <=heapSize)&& (minHeap[r].key < minHeap[min].key)){ min = r; } if(min !=i) { temp = minHeap[min]; minHeap[min] = minHeap[i]; minHeap[i] = temp; minHeapify(min); } } void buildMinHeap() { int i; for(i =heapSize / 2; i > 0; i--){ minHeapify(i); } } nodeheapExtractMin() { node min; if(heapSize< 1) { cout << "ERROR:no more" << endl; } min = minHeap[1]; minHeap[1] = minHeap[heapSize]; heapSize--; minHeapify(1); return min; } void heapIncreaseKey(int i, int key) { node temp; if(key >minHeap[i].key) { cout << "ERROR:new key is smaller than current key" << endl; } minHeap[i].key = key; while(i> 1 && minHeap[i/2].key > minHeap[i].key) { temp = minHeap[i]; minHeap[i] = minHeap[i/2]; minHeap[i/2] = temp; i = i / 2; } } void minHeapInsert(node temp) { heapSize++; minHeap[heapSize] = temp; minHeap[heapSize].key = MAX; heapIncreaseKey(heapSize, temp.key); } inline int solved(node *temp) { int i, j; for(i = 0;i < 3; i++){ for(j =0; j < 3; j++){ if((*temp).pos[i][j].y!= i || (*temp).pos[i][j].x != j) { return0; } } } return 1; } int adj[4][2] = {
{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; char operation[4] = {'l', 'r', 'u', 'd'}; int calKey(node *temp, int blankX2, int blankY2) { int key; int x1; int y1; int x2; int y2; int blankX1= (*temp).blankX; int blankY1= (*temp).blankY; key = 1; x1 = abs((*temp).pos[blankY1][blankX1].x -blankX1); y1 = abs((*temp).pos[blankY1][blankX1].y -blankY1); x2 = abs((*temp).pos[blankY2][blankX2].x -blankX2); y2 = abs((*temp).pos[blankY2][blankX2].y -blankY2); key += - (x1 + y1 + x2 + y2); x1 = abs((*temp).pos[blankY2][blankX2].x -blankX1); y1 = abs((*temp).pos[blankY2][blankX2].y -blankY1); x2 = abs((*temp).pos[blankY1][blankX1].x -blankX2); y2 = abs((*temp).pos[blankY1][blankX1].y -blankY2); key += x1 + y1 + x2 + y2; return key; } int factorial[9] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320}; int calPos1(node *temp) { int i; int j; int x, y; int x1, y1; int temp1,temp2; int num; int sum; sum = 0; for(i = 0;i < 9; i++) { y = i / 3; x = i % 3; temp1 = (*temp).pos[y][x].x +(*temp).pos[y][x].y * 3; num = 0; for(j =0; j < i; j++) { y1 = j / 3; x1 = j % 3; temp2 = (*temp).pos[y1][x1].x +(*temp).pos[y1][x1].y * 3; if(temp2< temp1) { num++; } } sum += (temp1 - num) * factorial[9 - i- 1]; } return sum; } int calPos(node *temp) { inti,j,k=0,b[9],ret=0,num=0; for(i=0;i<3;i++) for(j=0;j<3;j++) b[k++]=(*temp).pos[i][j].x +(*temp).pos[i][j].y * 3; for(i=0;i<9;i++) { num=0; for(j=0;j<i;j++) if(b[j]>b[i]) num++; ret+=factorial[i]*num; } return ret; } int main() { int i; int j; int x; int y; int blankX; int blankY; char c; int length; node insertNode; node temp; int pos; bool find; insertNode.key = 0; for(i = 0;i < 3; i++){ for(j =0; j < 3; j++){ cin >> c; if(c== 'x'){ insertNode.blankY = i; insertNode.blankX = j; insertNode.pos[i][j].x = 2; insertNode.pos[i][j].y = 2; }else{ x = c - '1'; y = x / 3; x = x % 3; insertNode.pos[i][j].x = x; insertNode.pos[i][j].y = y; } insertNode.key +=abs(insertNode.pos[i][j].x - j); insertNode.key +=abs(insertNode.pos[i][j].y - i); } } heapSize = 0; for(i = 0;i < maxLength; i++) { insertNode.op[i] = '\0'; } memset(visited, 0, sizeof(visited)); pos = calPos(&insertNode); visited[pos] = 1; minHeapInsert(insertNode); find = 0; int count =0; while(heapSize> 0){ if(heapSize> maxNum) { cout <<endl<< "Too large" << endl; return-1; } temp = heapExtractMin(); if(solved(&temp)== 1) { find = 1; cout << temp.op <<endl; break; } for(i =0; i < 4; i++){ blankX = temp.blankX; blankY = temp.blankY; y = blankY + adj[i][0]; x = blankX + adj[i][1]; count++; if(x< -1 || x > 3 || y < -1 || y > 3) { while(1) {} } if(x< 0 || y < 0 || x > 2 || y > 2) { continue; } insertNode = temp; insertNode.pos[y][x].x =temp.pos[blankY][blankX].x; insertNode.pos[y][x].y =temp.pos[blankY][blankX].y; insertNode.pos[blankY][blankX].x =temp.pos[y][x].x; insertNode.pos[blankY][blankX].y =temp.pos[y][x].y; pos = calPos(&insertNode); if(visited[pos]!= 0){ continue; } visited[pos] = 1; insertNode.key +=calKey(&temp, x, y); insertNode.blankX = x; insertNode.blankY = y; length = strlen(temp.op); insertNode.op[length] =operation[i]; minHeapInsert(insertNode); } } if(find ==0) { printf("unsolvable\n"); } return 1; }
14.2    IDA*搜索

A*算法减少存储需求的最简单的方法就是将迭代深入的思想用在启发式搜索上,形成迭代深入A*算法,即IDA*。

使用BFS搜索需要实用一个队列,这个队列如果过大就可能超出内存限制。用DFS代替BFS,可以减少存储需求。

IDA*算法实际上是在迭代加深的深度优先搜索的基础上进行的一种改进。迭代加深的深度优先搜索使用当前搜索深度来判断是否停止搜索,而IDA*算法则使用A*算法的估价函数F(N) = G(N) + H(N)来判断是否停止搜索。可以知道,此时的G(N)就是当前搜索深度,H(N)则是对于从当前结点N至目标结点T的实际代价一种乐观估计。说H(N)乐观,是因为在设计估价函数的时候,H(N)被约束为小于等于实际代价。由此可知,相对于朴素的迭代加深的深度优先搜索,IDA*算法省去了一些不必要的搜索。

14.2.1   实例

PKU JudgeOnline, 2286, The Rotation Game.

14.2.2   问题描述

如上图一种游戏,给定初始状态,通过拉动A到H的,对该行或列进行旋转。目标是使得中间的八个数字相等。

输出字典序最小的最优解的旋转过程,及其最终状态时中间的数字。

14.2.3   输入

11 1 1 3 2 3 2 3 1 3 2 2 3 1 2 2 2 3 1 2 1 3 3

11 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3

0

14.2.4   输出

AC

2

DDHH

2

14.2.5   分析

对搜索的深度给一个步数限制。每次在状态空间里进行DFS搜索的时候,如果遇见当前状态决定了已经不可能在限制内找到解,就返回,而不再搜索其子状态。如果这次DFS没找到,就继续放宽限制,重新对状态空间进行DFS。如此反复直到找到一个解。

显然这个解是最优的解。

确保解字典序最小的办法是DFS中顺次地从A到H中选择子状态。

14.2.6   程序

#include <iostream> using namespace std; int a[25]; int res[1000]; int ans; int limit; int state[8][7][2] = { { {1,23}, {3,1}, {7,3}, {12,7}, {16,12},{21,16}, {23,21} }, { {2,24}, {4,2}, {9,4}, {13,9}, {18,13},{22,18}, {24,22} }, { {11,5}, {10,11}, {9,10}, {8,9}, {7,8},{6,7}, {5,6} }, { {20,14}, {19,20}, {18,19}, {17,18},{16,17}, {15,16}, {14,15} }, { {24,2}, {22,24}, {18,22}, {13,18},{9,13}, {4,9}, {2,4} }, { {23,1}, {21,23}, {16,21}, {12,16},{7,12}, {3,7}, {1,3} }, { {14,20}, {15,14}, {16,15}, {17,16},{18,17}, {19,18}, {20,19} }, { {5,11}, {6,5}, {7,6}, {8,7}, {9,8},{10,9}, {11,10} } }; int isSuccess() { int ll =a[7]; if(a[8]!=ll|| a[9]!=ll || a[12]!=ll || a[13]!=ll || a[16]!=ll || a[17]!=ll || a[18]!=ll) return0; return ans= ll; } int getval() { int i; int cnt[4]; memset(cnt,0,sizeof(cnt)); for(i = 7;i <=9 ;i++)cnt[a[i]]++; for(i = 12;i <=13 ;i++)cnt[a[i]]++; for(i = 16;i <=18 ;i++)cnt[a[i]]++; return8-max(cnt[1],max(cnt[2],cnt[3])); } int dfs(int t) { inttemp[25]; if(t==limit) returnisSuccess(); if(t +getval() > limit)return 0; for(int i = 0 ; i < 8 ; i++) { res[t] = i; memcpy(temp,a,sizeof(a)); for(int j = 0 ; j < 7 ; j++) a[state[i][j][1]] =temp[state[i][j][0]]; int ret= dfs(t+1); memcpy(a,temp,sizeof(temp)); if(ret!=0)return ret; } return 0; } int main() { while(true) { scanf("%d",&a[1]); if(!a[1]) break; for(int i = 2; i <=24; i++) scanf("%d",&a[i]); if(isSuccess()) { cout<<"No moves needed"<<endl<<a[7]<<endl; continue; } limit = 1; while(dfs(0)== 0)limit++; for(int i = 0 ; i < limit ;i++)putchar((char)(res[i]+'A')); printf("\n%d\n",ans); } system("pause"); return 0; }
本文章欢迎转载,请保留原始博客链接http://blog.csdn.net/fsdev/article


实用算法的分析与程序设计。吴文虎,王建德。

The Art of Computer Programming, Volume 3, Sorting and Searching.Donald E. Knuth.

转载于:https://www.cnblogs.com/jpa2/archive/2011/10/23/2527909.html

你可能感兴趣的文章
安霸Alberto Broggi :计算机视觉技术驱动自动驾驶的发展 | 2019 AI+智能汽车创新峰会 ...
查看>>
top sql(oracle)
查看>>
125.53亿元!融创收购泛海北京泛海国际项目及上海董家渡项目 ...
查看>>
阿里云RPA(机器人流程自动化)干货系列之六:客户端安装及激活
查看>>
我最喜欢的快速排序算法之一
查看>>
5G将为农村地区做些什么?
查看>>
【翻译】Sklearn 与 TensorFlow 机器学习实用指南 —— 第11章 训练深层神经网络(下)...
查看>>
SQLflow:基于python开发的分布式机器学习平台, 支持通过写sql的方式,运行spark, 机器学习算法, 爬虫...
查看>>
机器学习可行性与VC dimension
查看>>
Nacos 发布 1.0.0 GA 版本,可大规模投入到生产环境
查看>>
关于ovirt主机即做存储又兼虚拟机主机的官方文档说明
查看>>
grep匹配结尾字符串的特殊情况
查看>>
第三方农资电商平台大丰收获华创资本数亿元C轮融资
查看>>
“虎鲸跳跃” 完成300万美元Pre-A轮融资,投资方为蓝湖资本及险峰长青
查看>>
JSON简介
查看>>
深圳安泰创新完成数千万新一轮融资,贝森资本领投
查看>>
当 Kubernetes 遇到阿里云
查看>>
MongoDB与Java 经典面试题、课程,好资源值得收藏
查看>>
标普全球获准进入中国市场,本土评级机构压力山大!
查看>>
阿里云基础产品技术月刊 2019年1月
查看>>