A*和IDA*算法

A*算法(A-Star)

为了赋予计算机“智慧”,我们必须更有智慧(:-P),计算机可以通过一个“估价函数”确定一个状态的“前途”的量,人呢……必须找到这个估价函数。
通常,估价函数计为f^(n),f^中,^在f上方,读作f-hat(帽子)。估价函数的定义如下:
f^(n)=g^(n)+h^(n)

n是状态的表示,通常是状态的编号之类的。在编程中,可以写作f_hat()或者直接写成f()即可。

g^(n)表示从初始状态到n 总共花费的代价。

h^(n)表示从n到目标状态估计需要花费的代价,对于一个正确的搜索,h^(n)<=h(n),h(n)表示从n到目标状态的实际代价(没有算出来的时候,当然是求不出来h(n)的,但是可以找到一个h^(n)<=h(n)哦),在这个范围内,h^(n)越接近h(n),搜索的启发效果越好。
对于h^1(n)和h^2(n),如果存在h^1(n)

f^(n)的值就是对n这个状态的估价。这个估价主要体现在h^(),因为g^()是已知的;g^()体现了广度优先,当h^()>g^()时,可以省略g^()从而提高效率。

搜索中,每到新的状态,计算它的f^()值,在目前所有的状态中,优先扩展f^()最小的,如果f^()最小的中有很多,则优先扩展其中g^()最大的。

这么说好像十分抽象,我们用一个例子理解它:


对于迷宫
#############
#a#_#___#___#
#_#_#_#_#_#_#
#_#_#_###_###
#__________b#
#############
a表示起点,b表示终点,#表示墙,_表示路。
求最短路时,若用广度优先搜索,则会同时搜索(即互不干扰)各条路径,如果是人,一定不会这样做,因为用“瞪眼法”可以看出下、右使最好的方法,但是计算机不能,所以我们要写一个函数来启发它。
本例中g^就是到当前状态走了多少步,那么,估计值h^怎么办呢?我们知道,在迷宫上不能走斜路,所以说,从某个个点到终点,假设到终点都是路,那么最少的步数就是从该点到终点的欧几里得距离(横坐标差的绝对值+纵坐标差的绝对值),如果有墙,最少的步数有可能比欧几里得距离要大,所以说,用欧几里得距离作为估价函数是可以的。
h^(n)=point[n].Euclid(target)
那么,在搜索时,到达了(4,3)的时候,会扩展向上的和向右的,
向上的:
f^() = g^() + h^() = 6 + 8 = 14
向右的:
f^() = g^() + h^() = 6 + 7 = 13
所以向右的状态先被搜索(和扩展的顺序无关)。
为了实现每次获取当前状态中最有前途的状态,我们需要一个“优先队列”,每次取出最有前途的,这用堆实现。
显然,当此h^()=0时,已经找到答案。

伪代码:
class Status
{
     private: int steps; Point point;
     public:
         Status(){}
         Status *goUp();Status *goDown();Status *goLeft();Status *goRight();
         int g_hat() { return steps; }
         int h_hat() { return point.Euclid(target); }
         int f_hat() { return g_hat() + h_hat(); }
};
//heap用f_hat()(小在前),相同的,用g_hat()(大在前)。
while (!heap.IsNull())
{
     Status *sta = heap.TakeFirst();
     if (sta->h_hat() == 0) 找到答案;
     if (map[sta->point.goUp()] != 墙 && !visit[sta->point.goUp()])
         heap.Add(sta->goUp());
     if (map[sta->point.goDown()] != 墙 && !visit[sta->point.goDown()])
         heap.Add(sta->goDown());
     if (map[sta->point.goLeft()] != 墙 && !visit[sta->point.goLeft()])
         heap.Add(sta->goLeft());
     if (map[sta->point.goRight()] != 墙 && !visit[sta->point.goRight()])
         heap.Add(sta->goRight());
}

另外,八数码问题可以用A*算法实现,一种估价函数是“不在正确位置上的数字的个数”,另一个更灵通的估价函数是所有数字(包括空格)到正确位置的欧几里得距离的一半。(思考:为什么是一半?答案:因为一次相当于交换了两个数字,如果把空格看作数字0,所以说应该是一半,如果不一半,则搜索可能不是最短路)

广度优先搜索是A*算法的一个特例,它的g^(n)=0,没有任何启发信息。


IDA*算法(迭代加深的A*算法)
引例:
The Rotation Game(POJ 2286)
这个游戏中,你有一个这样的东西:
[图片]
A~H表示所在的行或者列向其方向循环移动一格。
目标是使得中央的八个格子的数相同。
输入保证数字只有1、2、3,且分别8个。求最少步骤的方式(A~H表示),并求出此时中央的数字。

这个问题用A*不是好的选择,因为状态扩展太多,判重不易,所以我们用迭代加深的A*算法。

IDA*的基本步骤:
1、设置最大的深度maxf = f^(初始状态)
2、IDS搜索,弃去f^()>maxf的情况,如果找到答案,则结束
3、如果没有答案:若搜索中出现了比maxf更大的f^(),则令maxf = 其中最小的值,重复2;如果没有,则说明没有答案

那么,刚才的问题可以表示为:(h^() = 8 - 中间的八个方格中出现次数最多的数出现的次数)
int f_hat() { return steps + h_hat(); }
bool idastar()
//返回:是否找到答案
{
     if (f_hat() > maxf)
     {
         if (f_hat() < nextMaxf) nextMaxf = f_hat();
         return false;
     }
     if (h_hat() == 0) return true;
     ++steps;
     foreach(变换)
     {
         变换;
         if (idastar()) return true;
         还原回去;
     }
     --steps;
     return false;
}
int main()
{
     输入;
     maxf= f_hat();
     nextMaxf = INT_MAX;
     while (~idastar()) maxf = nextMaxf, nextMaxf = INT_MAX;
     输出答案;
     return 0;
}

同样,IDS是一个特殊的IDA*算法,它的h^()=0,也没有启发信息。

本次的练习:
完成A*的走迷宫算法、八数码问题,完成IDA*的The Rotation Game。

最后的练习:(我表示这个还是有点小难度的……做出来的一定要和我交流一下哦~)
完成一个程序,输入推箱子的局面,输出最短的解决步骤。(话说此程序需要强力剪枝,否则速度行你崩溃!)

转载自 http://tieba.baidu.com/p/1000086851?see_lz=1

你可能感兴趣的:(A*,IDA*)