启发式搜索
启发式搜索
启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无谓的搜索路径,提高了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。
在启发式搜索中,我们每次找到当前“最有希望是最短路径”的状态进行扩展。对于每个状态的我们用函数F来估计它是否有希望。F包含两个部分:
F = G + H
G:就是普通宽度优先搜索中的从起始状态到当前状态的代价,
H:是一个估计的值,表示从当前状态到目标状态估计的代价。
H是由我们自己设计的,H函数设计的好坏决定了启发式算法的效率。H值越大,算法运行越快。
但是在设计评估函数时,需要注意一个很重要的性质:评估函数的值一定要小于等于实际当前状态到目标状态的代价。
否则虽然你的程序运行速度加快,但是可能在搜索过程中漏掉了最优解。相对的,只要评估函数的值小于等于实际当前状态到目标状态的代价,就一定能找到最优解
F:评估值和状态值的总和。
同时在启发式搜索中将原来的一个队列变成了两个队列:openlist和closelist。
在openlist中的状态,其F值还可能发生变化。而在closelist中的状态,其F值一定不会再发生改变。
整个搜索解的流程变为:
- 计算初始状态的F值,并将其加入openlist
- 从openlist中取出F值最小的状态u,并将u加入closelist。若u为目标状态,结束搜索;
- 对u进行扩展,假设其扩展的状态为v:若v未出现过,计算v的f值并加入openlist;若v在openlist中,更新v的F值,取较小的一个;若v在closelist中,抛弃该状态。
- 若openlist为空,结束搜索。否则回到2。
利用这个方法可以避免搜索一些明显会远离目标状态的状态,从而缩小搜索空间,早一步搜索到目标结果。
在启发式搜索中,最重要的是评估函数的选取,一个好的评估函数能够更快的趋近于目标状态。
启发式搜索在某些情况下并不一定好用,一方面取决于评估函数的选取,另一个方面由于在选取状态时也会有额外的开销。而快速趋近目标结果所减少的时间,能否弥补这一部分开销也是非常关键的。
所以根据题目选取合适的搜索方法才是最重要的。
问题:八数码求解的步数
伪代码:
search(status): start.status = status start.g = 0 // 实际步数 start.h = evaluate(start.status) start.f = start.g + start.h openlist.insert(start) While (!openlist.isEmpty()) u = openlist.getMinFStatus() closelist.insert(u) For v is u.neighborStatus If (v in openlist) Then // 更新v的f值 If (v.f > v.h + u.g + 1) Then v.f = v.h + u.g + 1 End If Else If (v in closelist) continue Else v.g = u.g + 1 v.h = evaluate(v.status) v.f = v.g + v.h openlist.insert(v) End If End For End While
源码:
#include <cstring> #include <cmath> #include <queue> #include <stack> #include <algorithm> #include <iostream> using namespace std; struct node{ int f,h,g; int x,y; char map[3][3]; friend bool operator< (const node &a,const node &b){ if(a.f==b.f) return a.g<b.g; return a.f>b.f; } }; node start; int Hash[15]; // Hash[i] 存 i! 1~9 int pos[][2]= {{0,0},{0,1},{0,2},{1,0},{1,1},{1,2},{2,0},{2,1},{2,2}}; // 0~8 的目标位置的坐标 int to[4][2]={0,-1,0,1,-1,0,1,0}; bool vis[500000]; //判断不可能的状况 bool check(){ int s[20]; int cnt = 0; for(int i = 0; i<3; i++){ for(int j = 0; j<3; j++){ s[3*i+j] = start.map[i][j]; if(s[3*i+j] == 'x') continue; for(int k = 3*i+j-1; k>=0; k--){ if(s[k] == 'x') continue; if(s[k]>s[3*i+j]) cnt++; } } } if(cnt%2) return false; return true; } //康托 int solve(node a){ int s[20]; int ans = 0; for(int i = 0; i<3; i++){ for(int j = 0; j<3; j++){ s[3*i+j] = a.map[i][j]; int cnt = 0; for(int k = 3*i+j-1; k>=0; k--){ if(s[k]>s[3*i+j]) cnt++; } ans = ans+Hash[i*3+j]*cnt; } } return ans; } //得到 H值 ,曼哈顿距离之和 int get_h(node a){ int ans = 0; for(int i = 0; i<3; i++){ for(int j = 0; j<3; j++){ if(a.map[i][j] == 'x') continue; int k = a.map[i][j]-'1'; ans+=abs(pos[k][0]-i)+abs(pos[k][1]-j); } } return ans; } int bfs(){ memset(vis,0,sizeof(vis)); // queue<node> Q; priority_queue<node> Q; start.g = 0; start.h = get_h(start); start.f = start.h; vis[solve(start)]=true; if(solve(start)==0) return 0; Q.push(start); node next; while(!Q.empty()){ node a = Q.top(); Q.pop(); // node a = Q.front(); int k_s = solve(a); vis[k_s]=true; for(int i = 0; i<4; i++){ next = a; next.x+=to[i][0]; next.y+=to[i][1]; if(next.x < 0 || next.y < 0 || next.x>2 || next.y > 2) continue; next.map[a.x][a.y] = a.map[next.x][next.y]; next.map[next.x][next.y] = 'x'; next.g+=1; next.h = get_h(next); next.f = next.g+next.h; int k_n = solve(next); if(vis[k_n]) continue; Q.push(next); if(k_n == 0) return next.g; } } } int main(){ Hash[0] = 1; for(int i = 1; i<=9; i++) Hash[i] = Hash[i-1]*i; int t=0; cin>>t; char a=0; while(t--){ for (int i=0;i<3;i++){ for (int j=0;j<3;j++){ cin>>a; start.map[i][j]=a; if(a=='0'){ start.map[i][j]='x'; start.x=i; start.y=j; } } } if(!check()){ cout<<"No Solution!"<<endl; } else cout<<bfs()<<endl; } return 0; }