由于年代久远,此文本地已经无存,网上临时找到一篇备份,录在下面。
收录的网站本身已经没有图片了,真是遗憾,以后有空再补充下。
概述:本文描述了利用广度优先算法求解“华容道”问题的基本思路,以及根据最短路径原理对深度优先算法进行优化,从而提高了算法的执行效率,本文所提出的优化算法同样可以利用于同样类型的问题求解。
1、问题的提出1.1“华容道”简介 “华容道”是一种中国古代的智力游戏玩具,在一个宽为4,长为5的矩形框中,有10个棋子,包括一个曹操,五虎上将,四个小卒,要求在各个棋子不重叠的情况下进行移动,最有将曹操从棋盘上方移动到下边中央为成功。由于五员大将可以横放也可以竖放,有许多种排列方法,因而可以形成非常复杂的棋局,人们还给常用的棋局起了很多好听的名字,例如下图就是“横刀立马”的布局图。
图1.1 “横刀立马”的布局关于此问题的求解方法,现在已经有许多文章详细描述,本文不再一一描述,专门针对“广度优先算法”方式解题进行讨论。 1.2广度优先算法的讨论 我们知道,对于类似于“华容道”的问题,例如迷宫问题等,都可以归结为图论中图的遍历和最短路径问题。也就是说,从某一个具体状态开始作为图的起点,每走出任意一步以后得到的状态作为这个节点的延伸,只要保证每次走出的状态不和原来已经走过的状态相重复,那么就可以遍历所有由此原始状态可能到达的所有状态,从而形成一张完整的倒立放置的树图,如下图所示意。
图1.2 状态转化示意图针对“华容道”问题,其实质在于如何快速构建这样一张状态树图,在数据量未知的情况下,保证快速找到最终的结果。要做到这一点,有许多算法,广度优先是其中比较流行的一种算法。其具体思路是: 由起点出发,先构建第一层的节点,然后依次构建第二层,第三层节点,在构建的过程中,为了保证数据不重复出现,需要对每一个新节点都和原来已经生成的所有节点进行比较,保证其不重复出现在图中。 问题则由此而产生,由于在每次增加新节点时,都需要和原来所有的节点进行比较以保证此节点不重复出现,随着节点数量的增加,每次需要进行的比较也不断增加,这样就需要进行大量的时间用于比较状态是否重复,从而形成算法效率的一个瓶颈。这也是有人认为广度优先算法效率低下的一个重要原因。 下面将讨论如何优化广度优先算法以提高效率。 2、广度优先算法的优化 我们现在假设已经找到了一条从起始点状态到最终结果状态的一条最短路径,那么我们显然可以得到如下的推论: 从起始点到此最短路径上的每一个具体状态,所走的路径都是针对此节点状态的最短路径。 也就是说,我们要找到从起点到终点的最短路径,只能够通过行走每个节点的最短路径来得到。 我们现在给图中的每一个节点,都标示上其对应的最短路径步数,形成如同下图的一张带有路径步数的节点状态树图。
图2.1 带有最短路径步数的状态树图结合上图,我们可以很容易得到如下的结论: 在最短路径树图中,与任何级别为n的节点相连的节点,其级别必然在[n-1,n+1]之间。(结论1) 那么,从一个级别为n的节点出发,得到的所有节点,其级别也只能在(n-1,n+1)之间。因此,得到如下的结论: 要判断从一个原始级别为n的节点产生的节点是否在图中已经存在,仅需要判断图中[n-1,n+1]级别的节点集合中是否有此节点即可,如果没有,那么此节点就是新节点。(结论2) 请注意上面所描述的结论2,根据结论2,在对于新产生节点是否重复的判断问题上,仅需要由本级节点上溯到上二极即可,而不需要一直上溯到最开始的节点。 这样,通过缩小对于新产生节点是否存在的判断范围,我们达到了对于广度优先算法的优化目的。 3、结论 在广度优先算法的搜索过程中,如果按照最短路径的规则进行搜索,那么对于每次搜索产生的新节点状态,只需要在其上两层进行回溯判断,就可以判断新节点状态是否重复,从而达到快速搜索的目的。这种对于广度优先算法的优化同样可以用于其它类似问题的求解,例如“八皇后问题”,迷宫行走问题,最短交通路线问题等等。 -----------------------------------------------------------------
附录:源程序代码 “华容道解题大师1。0”采用Visual Basic6.0开发。下述代码都以VB书写,经调试通过。可从“华军软件园”下载全部源代码。 全局变量定义
Type HRDState '华容道的棋局表示 state(1 To 12) As Long '棋盘上的12个棋子的当前位置 Superid As Long '上一步棋盘的位置编号,0代表无上一步 Level As Long '这一不棋局的级别,0代表是开始状态 End Type Public G_Next As CHRDNext Public G_Save As CHRDSave Public G_State As HRDState |
应用程序启动
Sub Main() frmHRDMAIN.Show '显示主窗口 End Sub CHRDNext封装计算下一步算法的类 Dim bs(1 To 12) As Long '棋子的开始状态,接收输入值 Dim ES(1 To 12) As Long '棋子的计算结束状态,生成输出值,中间变量 Dim hnum As Long '横放的将军的数量,输入值 Public iEndNum As Long '计算结束的下一步的数量,输出值 Dim SaveEnd(1 To 240) As Long '最后生成的存放结果数组,输出值 Public Function getid(id As Long) As Long getid = SaveEnd(id) End Function Public Sub GetNext(BEGINSTATE() As Long, BEGINHNUM As Long) Dim i As Long Dim MoveType As Long '移动方向 Dim iend As Long '记录移动结果 For i = 1 To 12 bs(i) = BEGINSTATE(i) '初始状态 Next i hnum = BEGINHNUM '横放的将军数量 iEndNum = 0 '初始化结果数量为0 If MoveCaoCao() = 0 Then AddEnd For i = 2 To hnum + 1 '移动横放的将军 For MoveType = 1 To 4 If MoveHtiger(MoveType, i) = 0 Then AddEnd Next MoveType Next i For i = hnum + 2 To 6 '移动竖放的将军 For MoveType = 1 To 4 If MoveVtiger(MoveType, i) = 0 Then AddEnd Next MoveType Next i For i = 7 To 10 '移动小卒 For MoveType = 1 To 4 If MoveFighter(MoveType, i) = 0 Then AddEnd Next MoveType Next i End Sub Private Sub AddEnd() '将End数组中的数据添加到SaveEnd中去,最后将iendnum的值加1 Dim i As Long For i = 1 To 12 SaveEnd(iEndNum * 12 + i) = ES(i) Next i iEndNum = iEndNum + 1 End Sub Private Sub SortEnd(BeginId As Long, EndId As Long) '将输出结果进行排序,保证小者在前,大者在后 Dim i As Long Dim j As Long Dim Swap As Long i = BeginId Do While i <= EndId - 1 j = i + 1 Do While j <= EndId If ES(i) > ES(j) Then Swap = ES(i): ES(i) = ES(j): ES(j) = Swap End If j = j + 1 Loop i = i + 1 Loop End Sub Private Function MoveFighter(move_type As Long, id As Long) As Long '初始化下一步的数据 Dim i As Long For i = 1 To 12 ES(i) = bs(i) Next i MoveFighter = -1 '初始化返回值 Select Case move_type Case 1 'up If ES(11) = ES(id) - 4 Then ES(id) = ES(id) - 4: ES(11) = ES(11) + 4 MoveFighter = 0: GoTo Sort End If If ES(12) = ES(id) - 4 Then ES(id) = ES(id) - 4: ES(12) = ES(12) + 4 MoveFighter = 0: GoTo Sort End If Case 2 'down If ES(11) = ES(id) + 4 Then ES(id) = ES(id) + 4: ES(11) = ES(11) - 4 MoveFighter = 0: GoTo Sort End If If ES(12) = ES(id) + 4 Then ES(id) = ES(id) + 4: ES(12) = ES(12) - 4 MoveFighter = 0: GoTo Sort End If Case 3 'left If ES(11) = ES(id) - 1 And ES(11) Mod 4 <> 0 Then ES(id) = ES(id) - 1: ES(11) = ES(11) + 1 MoveFighter = 0: GoTo Sort End If If ES(12) = ES(id) - 1 And ES(12) Mod 4 <> 0 Then ES(id) = ES(id) - 1: ES(12) = ES(12) + 1 MoveFighter = 0: GoTo Sort End If Case 4 'right If ES(11) = ES(id) + 1 And ES(11) Mod 4 <> 1 Then ES(id) = ES(id) + 1: ES(11) = ES(11) - 1 MoveFighter = 0: GoTo Sort End If If ES(12) = ES(id) + 1 And ES(12) Mod 4 <> 1 Then ES(id) = ES(id) + 1: ES(12) = ES(12) - 1 MoveFighter = 0: GoTo Sort End If End Select Sort: If MoveFighter = 0 Then SortEnd 7, 10 '对小卒排序 SortEnd 11, 12 '对空格排序 End If End Function Private Function MoveCaoCao() As Long 'step1初始化下一步的数据 Dim i As Long For i = 1 To 12 ES(i) = bs(i) Next i MoveCaoCao = -1 '初始化返回值,-1代表不成功 'up按照规则,限制曹操不能向上移动 'If ES(11) = ES(1) - 8 And ES(12) = ES(11) + 1 Then ' ES(1) = ES(1) - 4: ES(11) = ES(11) + 8: ES(12) = ES(12) + 8 ' MoveCaoCao = 0 'end if 'down If ES(11) = ES(1) + 8 And ES(12) = ES(11) + 1 Then ES(1) = ES(1) + 4: ES(11) = ES(11) - 8: ES(12) = ES(12) - 8 MoveCaoCao = 0: GoTo Sort End If 'left If ES(11) = ES(1) - 1 And ES(12) = ES(11) + 4 And (ES(11) Mod 4) <> 0 Then ES(1) = ES(1) - 1: ES(11) = ES(11) + 2: ES(12) = ES(12) + 2 MoveCaoCao = 0: GoTo Sort End If 'right If ES(11) = ES(1) + 2 And ES(12) = ES(11) + 4 And (ES(11) Mod 4) <> 1 Then ES(1) = ES(1) + 1: ES(11) = ES(11) - 2: ES(12) = ES(12) - 2 MoveCaoCao = 0: GoTo Sort End If '移动曹操以后,不需要重新进行排序 Sort: 'Do nothing End Function Private Function MoveHtiger(MoveType As Long, id As Long) As Long '初始化下一步的数据 Dim i As Long For i = 1 To 12 ES(i) = bs(i) Next i MoveHtiger = -1 '设置初始值 Select Case MoveType Case 1 'up If ES(11) = ES(id) - 4 And ES(12) = ES(11) + 1 Then ES(id) = ES(id) - 4: ES(11) = ES(11) + 4: ES(12) = ES(12) + 4 MoveHtiger = 0: GoTo Sort End If Case 2 'down If ES(11) = ES(id) + 4 And ES(12) = ES(11) + 1 Then ES(id) = ES(id) + 4: ES(11) = ES(11) - 4: ES(12) = ES(12) - 4 MoveHtiger = 0: GoTo Sort End If Case 3 'left If ES(11) = ES(id) - 1 And ES(11) Mod 4 <> 0 Then ES(id) = ES(id) - 1: ES(11) = ES(11) + 2 MoveHtiger = 0: GoTo Sort End If If ES(12) = ES(id) - 1 And ES(12) Mod 4 <> 0 Then ES(id) = ES(id) - 1: ES(12) = ES(12) + 2 MoveHtiger = 0: GoTo Sort End If Case 4 'right If ES(11) = ES(id) + 2 And ES(11) Mod 4 <> 1 Then ES(id) = ES(id) + 1: ES(11) = ES(11) - 2 MoveHtiger = 0: GoTo Sort End If If ES(12) = ES(id) + 2 And ES(12) Mod 4 <> 1 Then ES(id) = ES(id) + 1: ES(12) = ES(12) - 2 MoveHtiger = 0: GoTo Sort End If End Select Sort: If MoveHtiger = 0 Then SortEnd 2, hnum + 1 '横放将领排序 SortEnd 11, 12 '空格排序 End If End Function Private Function MoveVtiger(MoveType As Long, id As Long) As Long '初始化下一步的数据 Dim i As Long For i = 1 To 12 ES(i) = bs(i) Next i MoveVtiger = -1 Select Case MoveType Case 1 'up If ES(11) = ES(id) - 4 Then ES(id) = ES(id) - 4: ES(11) = ES(11) + 8: MoveVtiger = 0: GoTo Sort End If If ES(12) = ES(id) - 4 Then ES(id) = ES(id) - 4: ES(12) = ES(12) + 8: MoveVtiger = 0: GoTo Sort End If Case 2 'down If ES(11) = ES(id) + 8 Then ES(id) = ES(id) + 4: ES(11) = ES(11) - 8: MoveVtiger = 0: GoTo Sort End If If ES(12) = ES(id) + 8 Then ES(id) = ES(id) + 4: ES(12) = ES(12) - 8: MoveVtiger = 0: GoTo Sort End If Case 3 'left If ES(11) = ES(id) - 1 And ES(12) = ES(11) + 4 And ES(11) Mod 4 <> 0 Then ES(id) = ES(id) - 1: ES(11) = ES(11) + 1: ES(12) = ES(12) + 1 MoveVtiger = 0: GoTo Sort End If Case 4 'right If ES(11) = ES(id) + 1 And ES(12) = ES(11) + 4 And ES(11) Mod 4 <> 1 Then ES(id) = ES(id) + 1: ES(11) = ES(11) - 1: ES(12) = ES(12) - 1 MoveVtiger = 0: GoTo Sort End If End Select Sort: If MoveVtiger = 0 Then SortEnd hnum + 2, 6 '竖放将领排序 SortEnd 11, 12 '空格排序 End If End Function |
CHRDSave 保存已经走过的节点记录类
Option Explicit Dim SaveState(1 To 300000) As HRDState '最多走3万步 Public iCurrentNum As Long '当前位置的指针 Private Function IsExist(NewState() As Long, ilevel As Long) As Boolean IsExist = False Dim i As Long For i = iCurrentNum To 1 Step -1 If SaveState(i).Level < ilevel - 2 Then i = 0: Exit Function End If If SaveState(i).state(1) = NewState(1) And _ SaveState(i).state(2) = NewState(2) And _ SaveState(i).state(3) = NewState(3) And _ SaveState(i).state(4) = NewState(4) And _ SaveState(i).state(5) = NewState(5) And _ SaveState(i).state(6) = NewState(6) And _ SaveState(i).state(7) = NewState(7) And _ SaveState(i).state(8) = NewState(8) And _ SaveState(i).state(9) = NewState(9) And _ SaveState(i).state(10) = NewState(10) Then IsExist = True: i = 0: Exit Function End If Next i End Function Public Sub AddState(NewState() As Long, isuperid As Long, ilevel As Long) Dim i As Long If Not IsExist(NewState, ilevel) Then iCurrentNum = iCurrentNum + 1 For i = 1 To 12 SaveState(iCurrentNum).state(i) = NewState(i) Next SaveState(iCurrentNum).Superid = isuperid SaveState(iCurrentNum).Level = ilevel End If End Sub Private Sub Class_Initialize() iCurrentNum = 0 End Sub Public Function GetState(id As Long) If id > 0 Then G_State = SaveState(id) End If End Function |
主界面窗体的代码
Private Sub ShowId(id As Long, deep As Long) Label1.Caption = "节点数:" & CStr(id) & " 测试深度:" & CStr(deep) End Sub Private Function isvalid(state() As Long, ByVal hnum As Long) Dim bs(1 To 20) As Integer Dim i As Integer Dim k As Integer 'init For i = 1 To 20 bs(i) = 1 Next 'check For i = 1 To 12 k = state(i) Select Case i Case 1 '曹操 bs(k) = 0 bs(k + 1) = 0 bs(k + 4) = 0 bs(k + 5) = 0 Case 2, 3, 4, 5, 6 If i <= hnum + 1 Then '横放的将军 bs(k) = 0 bs(k + 1) = 0 Else '竖放的将军 bs(k) = 0 bs(k + 4) = 0 End If Case 7, 8, 9, 10, 11, 12 '小卒和空格 bs(k) = 0 End Select Next i isvalid = True For i = 1 To 20 If bs(i) > 0 Then isvalid = False Exit Function End If Next i End Function Private Sub cmdStart_Click() Dim BEGINSTATE(1 To 12) As Long Dim i As Long Dim j As Long Dim k As Long Dim iHnum As Long Dim time1 As Date Dim time2 As Date Dim ifile As Integer ifile = FreeFile() time1 = Now() For i = 1 To 12 BEGINSTATE(i) = Int(Mid(TextBegin.Text, i * 2 - 1, 2)) Next i iHnum = CLng(txtNum.Text) If Not isvalid(BEGINSTATE, iHnum) Then MsgBox "初始状态不合法,请检查!" Exit Sub End If Set G_Next = New CHRDNext Set G_Save = New CHRDSave G_Save.AddState BEGINSTATE, 0, 0 '记录到最终的记录中去 i = 1 Do While i <= G_Save.iCurrentNum '堆栈尚未完成 '读入当前记录 G_Save.GetState i ShowId i, G_State.Level '判断是否可以结束循环 If G_State.state(1) = 14 Then G_Save.iCurrentNum = i Exit Do End If '计算所有下级步骤 G_Next.GetNext G_State.state, iHnum j = 1 Do While j <= G_Next.iEndNum '下一步赋值 For k = 1 To 12 BEGINSTATE(k) = G_Next.getid(j * 12 - 12 + k) Next k '存入队列之中 G_Save.AddState BEGINSTATE, i, G_State.Level + 1 j = j + 1 Loop i = i + 1 If i Mod 19 = 0 Then DoEvents Loop time2 = Now() i = (time2 - time1) * 3600 * 24 G_Save.GetState G_Save.iCurrentNum If G_State.state(1) = 14 Then MsgBox "行走步数:" & G_Save.iCurrentNum & "用时: " & i, vbOKOnly, "恭喜恭喜,行走成功" Else MsgBox "行走步数:" & G_Save.iCurrentNum & "用时: " & i, vbOKOnly, "抱歉,行走失败" End If i=i+1 End Sub Private Sub Command1_Click() List1.Clear Dim i As Long i = G_Save.iCurrentNum G_Save.GetState i If G_State.state(1) <> 14 Then MsgBox "没有找到合理的解" Exit Sub End If Dim strtemp(1 To 1000) As String Dim k As Long j = 1 Do While G_State.Level > 0 strtemp(j) = "" For k = 1 To 12 strtemp(j) = strtemp(j) & CStr(G_State.state(k)) & "_" Next k strtemp(j) = strtemp(j) & "----" & CStr(G_State.Level) i = G_State.Superid G_Save.GetState i j = j + 1 Loop strtemp(j) = "" For k = 1 To 12 strtemp(j) = strtemp(j) & CStr(G_State.state(k)) & "_" Next k strtemp(j) = strtemp(j) & "----" & CStr(G_State.Level) For k = j To 1 Step -1 List1.AddItem strtemp(k) Next k End Sub Private Sub Form_Load() Set G_Next = New CHRDNext Set G_Save = New CHRDSave End Sub Private Sub mnuAbout_Click() frmAbout.Show End Sub Private Sub mnuExit_Click() End'退出程序 End Sub |