A*算法与8数字谜题(参见《算法》P226习题2.5.32)
A*算法的目的是找到一条从起始状态到最终状态的最短路径。
在A*算法中,需要在每个点计算启发函数:f(S) = g(S) + h(S),其中g(S)是从起点到S点的距离,h(S)是对从S点到终点的最短距离的估计值。
如果把g(S)当作深度,又令h(S) = 0,则A*算法就变成了BFS。
A*算法对BFS的改进在于把BFS所用到的队列改成按启发函数值排列的优先队列。
假设从状态S抵达终点的最短距离为H(S),那么启发函数中的h(S)必须满足h(S)<= H(S)。在此基础上,h和实际的距离越接近,需要计算的节点就越少,效果就越好。如果h(S) = 0,此时A*算法就变成了BFS,效果最差。
在解8数字谜题时,如果按照BFS的思路,我们每次扩展四个节点,分别是空格上移、下移、左移和右移(当然,大多数节点是无法完全扩展这四个方向的),一层层搜索之后,若发现了目标状态,则从目标状态节点开始向父节点回溯,由此就可以得到一个步数最少的解法了。但是BFS是盲目的,因此在搜索过程中它搜索了很多“意义不大”的节点。于是我们开始设计一个加入了一定启发信息的A*算法(其思路类似于分支限界),代码如下(参见P223 2.5.4.6小节):
A*() { // open表,用优先队列实现,用来保存待扩展的节点(改进自BFS中的队列)。 PriorityQueue open; // closed表,用HASH表或者其他能够高效检索的数据结构实现,用来保存已经扩展过的节点(可以考察一个状态是否已经被产生过)。 SymbolTable closed; open.Add(起始节点); while(!PriorityQueueEmpty(open)) { // 从open表中弹出一个最优的待扩展节点 S = PriorityQueueDeleteMax(open)); closed.Insert(S); // 如果S就是目标状态则结束 if(S == GOAL) { return OVER; } // 尝试扩展S的所有子节点 while(childS = S.nextChild()) { // 如果当前节点尚未被产生过 if(!closed.isInclude(childS)) { // 则将这个节点添加到open表中 PriorityQueueInsert(open,childS); } } } }