资源下载地址:https://download.csdn.net/download/sheziqiong/86776612
资源下载地址:https://download.csdn.net/download/sheziqiong/86776612
N数码问题又称为重拍拼图游戏,以8数码为例:在 3X3 的方格棋盘上,放置8个标有1、2、3、4、5、6、7、8数字的方块和1个空白块,空白块可以上下左右移动,游戏要求通过反复移动空白格,寻找一条从某初始状态到目标状态的移动路径。
令 W , H W, H W,H为棋盘的宽高,有 N = W × H − 1 N = W \times H - 1 N=W×H−1,维度越高求解难度也就越高。
N数码问题的求解策略有广度优先搜索和启发式搜索等,启发式搜索能够利用N数码信息,加快搜索速度。使用广度优先搜索和A*搜索算法都可以求得最优解,即总开始状态到结束状态的最短路径。
首先给出逆序数的定义:对于串中相邻的一对数,如果位置较前的数大于位置靠后的数,则成为一对逆序,一个串的逆序总数成为逆序数。例如 21432 21432 21432 的逆序数为3,逆序对包括 21 21 21, 43 43 43, 32 32 32
在N数码中,并不是所有状态都是可解的。将一个状态表示为一维数组的形式,计算除了空位置(0)之外的所有数字的逆序数之和,如果初始状态和结束状态的逆序奇数的偶性相同,则相互可达,否则相互不可达。
简单证明上述结论的必要性:当空块左右移动是不改变逆序数,上下移动时,相当于一个非空块向前或向后移动2格子,如果跨过的两个数字都大于/小于移动的数字,则逆序数可能增减2,如果两个数字一个大一个小,逆序数不变。
因此在设计测试样例时需要考虑逆序数为偶数,否则程序不会终止。例如:[1,2,3,4,5,6,8,7,0] 就是一个不可解情况。
A*搜索算法利用启发性信息来引导搜索,动态地确定搜索节点的排序,每次选择最优的节点往下搜索,可以高效地减少搜索范围,减少求解的问题的时间。
A*算法用估价函数来衡量一个节点的优先度,优先度越小的节点越应该被优先访问。估价函数用如下公式表示: f ( n ) = h ( n ) + d ( n ) f(n) = h(n) + d(n) f(n)=h(n)+d(n), h ( n ) h(n) h(n)是对于当前状态的估计值,例如曼哈顿距离, d ( h ) d(h) d(h)是已经付出的搜索代价,例如节点的深度。
A*算法和A搜索算法的不同之处在于,A*保证对于所有节点都有: h ( n ) ≤ h ∗ ( n ) h(n) \le h^*(n) h(n)≤h∗(n),其中 h ∗ ( n ) h^*(n) h∗(n)为当前状态到目的状态的最优路径的代价。
在本次实验中,我们选择曼哈顿距离为 h ( n ) h(n) h(n),节点深度为 d ( n ) d(n) d(n),在实际考虑中:所有放错位的数码个数、后续节点不正确的数码个数都可以作为估价方法,也可以选择多个估价方法并给予适当权重来加快搜索速度。
原程序在搜索基础上使用了估价函数来进行优化,但无法求得最优解。原程序在每一个状态的下一个可行状态中选择评估函数最小的一个状态作为下一步,搜索的节点为链状结构(可能有环,而一般的A*搜索结果是树状结构)。
程序步骤:
原程序没有充分利用 closeTable,因此可能会出现死循环(未证明):
要修改程序,必须修改2-1、2-2 步骤
我们在源程序的基础上修改程序,使其能够实现最优求解。
正确的A*搜索算法步骤:
State类:表示一个状态
def solve(self)
方法:将搜索职责转移到Solution类上,State类只负责管理当前的状态。self.d
:状态的深度,并在nextStep()
方法中维护,起始状态的深度为0,终点状态的深度为最右搜索路径长度。getFunctionValue(self)
方法:返回值为哈夫曼距离和状态深度的和,这是一个满足A*搜索的评估函数。Solution类:负责求解N数码问题
getBestState
方法:遍历待检查状态链表 explore_list,返回估价函数最小的状态isVisited
方法:遍历已检查状态链表 visited_list, 返回 True 如果该状态已经存在于 visited_list 中getPath
方法:获取最终解路径,保存在 path_list 中AStarSolve
方法:使用A*搜索策略求解问题BFSSolve
方法:使用BFS策略求解问题,用于测试A*算法是否正确由于我们使用普通的链表来记录记录待检查状态链表(explore_list
)、已检查状态链表(visited_list
)和最优路径(path_list
):
explore_list
,时间复杂度为 O ( n ) O(n) O(n)visited_list
,时间复杂度为 O ( n ) O(n) O(n)设最终所搜的节点数目为 M M M,有复杂度为 O ( M 2 ) O(M^2) O(M2)
在修改程序的基础上对待检查状态链表(explore_list
)和已检查状态链表(visited_list
)进行优化:
explore_list
,用一个哈希表(字典)来实现 visited_list
,可以将每次获取估价函数最小的状态和每次判断一个状态是否已经被搜索过的复杂度都降为 O ( 1 ) O(1) O(1)explore_list
插入节点并维护优先队列结构的复杂度为 O ( log ( n ) ) O(\log(n)) O(log(n))从而让整个算法复杂度降低到 O ( M log ( M ) ) O(M\log(M)) O(Mlog(M)),从下面的测试中可以看到优化的运行速度优于优化前的运行速度。
最后,可以通过优化估价函数来缩短搜索次数,例如使用错误码数、曼哈顿距离的加权。
对于8数码测试样例:
begin = State(np.array([[1, 5, 2],[7, 0, 4],[6, 3, 8]]))
end = State(np.array([[1, 2, 3],[4, 5, 6],[7, 8, 0]]))
原程序测试结果:(非最优路径)
...
Total steps is 28
BFS结果:耗时长,访问节点数多,求得最短路径及其长度 14 。
...
Total search node is 5905
Total steps is 14
Totally cost is 105.18757700920105 s
优化前的结果:正确求得最短路径,耗时0.02秒
...
Total search node is 53
Total steps is 14
Totally cost is 0.020945310592651367 s
优化后的结果:正确求得最短路径,耗时0.01秒
...
Total search node is 52
Total steps is 14
Totally cost is 0.010965108871459961 s
对于下面八数码测试样例:
begin = State(np.array([[1, 3, 2],[4, 5, 6],[8, 7, 0]]))
end = State(np.array([[1, 2, 3],[4, 5, 6],[7, 8, 0]]))
优化前的结果:需要 9 秒才能求出最优解
...
Total search node is 1664
Total steps is 20
Totally cost is 9.302738666534424 s
优化后的结果:只需要 0.6 秒就能求出最优解
...
Total search node is 2730
Total steps is 20
Totally cost is 0.641481876373291 s
对于以下24数码的测试样例:
begin = State(np.array([[ 1, 2, 3, 4, 5],
[ 6, 12, 8, 9, 10],
[11, 7, 13, 14, 15],
[16, 19, 18, 17, 20],
[21, 22, 23, 24, 0]]))
end = State(np.array([[ 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 0]]))
AStarSearchOptimized 运行输出:求得最短路径及其长度为26,此时非优化A*与BFS无法在可接受时间内完成求解。
...
->
26:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 0 24
->
27:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 0
->
Total search node is 91640
Total steps is 26
Totally cost is 36.743870973587036 s
资源下载地址:https://download.csdn.net/download/sheziqiong/86776612
资源下载地址:https://download.csdn.net/download/sheziqiong/86776612