实验内容
- 对九宫重排问题,建立图的启发式搜索求解方法。
- 用A*算法求救九宫重排问题。
实验要求
3x3九宫棋盘,放置数码为1~8的8个棋子,棋盘中留有一个空格,空格周围的棋子可以移动到空格中,从而改变棋盘的布局。根据给定初始布局和目标布局,移动棋子从初始布局到达目标布局,求解移动步骤并输出。请设计算法,使用合适的搜索策略,在较少的空间和时间代价下找到最短路径。
编程语言和开发环境
编程语言:java
开发环境JDK11
实验思路
1.A*算法
-
原理
1. 把起点加入 open list 。
2. 重复如下过程:
a. 遍历open list ,查找F值最小的节点,把它作为当前要处理的节点,然后移到close list中
b. 对当前方格的 8 个相邻方格一一进行检查,如果它是不可抵达的或者它在close list中,忽略它。否则,做如下操作:
□ 如果它不在open list中,把它加入open list,并且把当前方格设置为它的父亲
□ 如果它已经在open list中,检查这条路径 ( 即经由当前方格到达它那里 ) 是否更近。如果更近,把它的父亲设置为当前方格,并重新计算它的G和F值。如果你的open list是按F值排序的话,改变后你可能需要重新排序。
c. 遇到下面情况停止搜索:
□ 把终点加入到了 open list 中,此时路径已经找到了,或者
□ 查找终点失败,并且open list 是空的,此时没有路径。
3. 从终点开始,每个方格沿着父节点移动直至起点,形成路径。 -
伪代码
function A*(start, goal)
// The set of nodes already evaluated.
closedSet := {}
// The set of currently discovered nodes still to be evaluated. Initially, only the start node is known.
openSet := {start}
// For each node, which node it can most efficiently be reached from.
// If a node can be reached from many nodes, cameFrom will eventually contain the most efficient previous step.
cameFrom := the empty map
// For each node, the cost of getting from the start node to that node.
gScore := map with default value of Infinity
// The cost of going from start to start is zero.
gScore[start] := 0
// For each node, the total cost of getting from the start node to the goal
// by passing by that node. That value is partly known, partly heuristic.
fScore := map with default value of Infinity
// For the first node, that value is completely heuristic.
fScore[start] := heuristic_cost_estimate(start, goal)
while openSet is not empty
current := the node in openSet having the lowest fScore[] value
if current = goal
return reconstruct_path(cameFrom, current)
openSet.Remove(current)
closedSet.Add(current)
for each neighbor of current
if neighbor in closedSet
continue // Ignore the neighbor which is already evaluated.
// The distance from start to a neighbor
tentative_gScore := gScore[current] + dist_between(current, neighbor)
if neighbor not in openSet // Discover a new node
openSet.Add(neighbor)
else if tentative_gScore >= gScore[neighbor]
continue // This is not a better path.
// This path is the best until now. Record it!
cameFrom[neighbor] := current
gScore[neighbor] := tentative_gScore
fScore[neighbor] := gScore[neighbor] + heuristic_cost_estimate(neighbor, goal)return failure
function reconstruct_path(cameFrom, current)
total_path := [current]
while current in cameFrom.Keys:
current := cameFrom[current]
total_path.append(current)
return total_path
2.A*算法解决九宫格问题
- 问题描述
八数码问题作为一个经典的问题被大家所熟知,该问题是求解如何从开始的一个状态(布局)到达目标状态所需步数最少的问题。 - 问题分析
将每一个状态作为一个结点容易想到可以用广搜的方法解决,这种方法简单,但是就算是加入哈希判重也会搜索很多的无用结点。
我们打算用A算法解决这个问题,既然确定了用A算法,那么我们首先应该确定估价函数h(x),估价函数的选取直接决定A*算法的效率,一般对于八数码问题有三种估价函数的选法:
以不在位的数码的个数为估价函数
以不在位的数码归位所需的最短距离和即曼哈顿距离为估价函数
将逆序对数作为估价函数
可以证明前两种都是乐观估计,最后一种不是,因此前两种都可以作为八数码问题的估价函数,但是你的估计值与真实值越近所需要搜索的状态越少,很明显第一种方法太乐观了(估价函数的选取直接决定算法的效率),因此我们采用第二种方法作为八数码问题的估价函数
解决了估价函数的问题以后,第二个需要解决的问题就是判重,我们首先想到的是用集合set,这种方法最简单,但是很不幸这种方法耗时也是最多的,如果时间要求比较高的话,这种情况很容易超时。这里我们不用这种方法,判重问题自然而然想到的是哈希表,好了现在问题又来了,如何创建哈希表,也就是哈希函数怎么写,这个东西比较有技巧,还好对于这种问题有一种现成的方法解决,那就是康托展开 ,还有一个问题就是有些问题是无解的,这种情况我们不希望进行很大力气的搜索之后发现无解,最好是能提前预知,值得庆幸的是八数码无论怎么移动逆序的奇偶性不变,因此我们可以直接通过O(1)的时间判断开始和目标结点的逆序奇偶性是否相同就可以了。