@@ -1522,7 +1522,7 @@ \subsubsection{跳棋趣题}
15221522 \label {fig:pegrules}
15231523\end {figure}
15241524
1525- 这是跳棋的一种特殊形式。并不一定限制为6 颗棋子,也可以是8 或者更大的偶数。\cref {fig:pegpuzzles}是这类问题的变化形式\footnote {图片来源:\url {http://www.robspuzzlepage.com/jumping.htm}}。
1525+ 这是跳棋的一种特殊形式。并不一定限制为6颗棋子,也可以是8或者更大的偶数。\cref{fig:pegpuzzles}是这类问题的变化形式\footnote{图片来源:\url{http://www.robspuzzlepage.com/jumping.htm}}。从左向右的石头位置为1, 2, ..., 7。每次有4种移动可能。例如开始时,第3块石头上的青蛙可以移动到空石头上。对称地,第5块石头上的青蛙也可以左移一步。第2块石头上的青蛙可以向右越过一只青蛙跳到空石头上,对称地,第6块石头上的青蛙,也可以向左越过一只。每次记录下7块石头和青蛙的状态,尝试4种方案。如果走不下去了,就回溯尝试其它方案。由于左侧的青蛙只能向右,右侧的只能向左,所有移动都是不可逆的。和走迷宫不同,这里不存在重复情况。我们记录移动的步骤用于最后输出结果。状态$L$是$s$的某种排列。$L[i]$的值是$0, \pm 1$表示第$i$个石头是空的、有一只向左、向右的青蛙。令空石头的位置为$p$,4种移动方案为:
15261526
15271527\begin {figure}[htbp]
15281528 \centering
@@ -1533,16 +1533,14 @@ \subsubsection{跳棋趣题}
15331533 \label {fig:pegpuzzles}
15341534\end {figure}
15351535
1536- 从左向右的石头位置为1 , 2 , ..., 7 。每次有4 种移动可能。例如开始时,第3 块石头上的青蛙可以移动到空石头上。对称地,第5 块石头上的青蛙也可以左移一步。第2 块石头上的青蛙可以向右越过一只青蛙跳到空石头上,对称地,第6 块石头上的青蛙,也可以向左越过一只。每次记录下7 块石头和青蛙的状态,尝试4 种方案。如果走不下去了,就回溯尝试其它方案。由于左侧的青蛙只能向右,右侧的只能向左,所有移动都是不可逆的。和走迷宫不同,这里不存在重复情况。我们记录移动的步骤用于最后输出结果。状态$ L$ 是$ s$ 的某种排列。$ L[i]$ 的值是$ \pm 1, 0$ 表示第$ i$ 个石头是空的、有一只向左、向右的青蛙。令空石头的位置为$ p$ ,4 种移动方案为:
1537-
15381536\begin {enumerate}
15391537\item 向左跳跃:$ p < 6$ ,且$ L[p+2] > 0$ ,交换$ L[p] \leftrightarrow L[p+2]$ ;
15401538\item 向左移动:$ p < 7$ ,且$ L[p+1] > 0$ ,交换$ L[p] \leftrightarrow L[p+1]$ ;
15411539\item 向右跳跃:$ p > 2$ ,且$ L[p-2] < 0$ ,交换$ L[p-2] \leftrightarrow L[p]$ ;
15421540\item 向右移动:$ p > 1$ ,且$ L[p-1] < 0$ ,交换$ L[p-1] \leftrightarrow L[p]$ 。
15431541\end {enumerate}
15441542
1545- 定义4 个函数$ leap_l$ 、$ hop_l$ 、$ leap_r$ 、和 $ hop_r$ ,变化状态$ L \mapsto L'$ 。若不能移动,则返回同样的$ L$ 不变。使用栈$ S$ 记录已做过的尝试。开始的时候,栈中只有一个列表,列表中只有开始状态。列表$ M$ 记录所有找到的解。我们不断取出栈顶。如果状态$ L = e$ 则找到了一个解。我们将移动步骤记录到$ M$ 中。否则我们在$ L$ 上尝试4 种移动,如果可行就入栈以备继续搜索。
1543+ 定义4 个函数$ leap_l$ 、$ hop_l$ 、$ leap_r$ 、$ hop_r$ ,变化状态$ L \mapsto L'$ 。若不能移动,则返回同样的$ L$ 不变。使用栈$ S$ 记录已做过的尝试。开始的时候,栈中只有一个列表,列表中只有开始状态。列表$ M$ 记录所有找到的解。我们不断取出栈顶。如果状态$ L = e$ 则找到了一个解。我们将移动步骤记录到$ M$ 中。否则我们在$ L$ 上尝试4 种移动,如果可行就入栈以备继续搜索。
15461544
15471545\be
15481546solve\ [[-1 , -1 , -1 , 0 , 1 , 1 , 1 ]]\ [\ ]
@@ -1610,7 +1608,7 @@ \subsubsection{跳棋趣题}
16101608\hline
16111609\etab
16121610
1613- 每侧有3 只青蛙时需要15 步左右互换。扩展上述解法可以得到步数和青蛙数目的一个关系表 :
1611+ 每侧有3 只青蛙时需要15 步左右互换。扩展上述解法可以得到步数和青蛙数目的一个关系 :
16141612
16151613\btab {c|c|c|c|c|c|c}
16161614每侧青蛙数$ n$ & 1 & 2 & 3 & 4 & 5 & ... \\
@@ -1625,7 +1623,7 @@ \subsubsection{跳棋趣题}
16251623
16261624观察上面3 个趣题,它们的解法有着类似的结构。它们都从某种状态开始。走迷宫从入口开始,八皇后问题从空棋盘开始,跳跃青蛙问题从[-1 , -1 , -1 , 0 , 1 , 1 , 1 ]开始。解的过程是一种搜索。每次尝试都有若干种可能的选项。走迷宫时,每步有上下左右四个方向选择;八皇后问题中,每次摆放都有八列选择;跳跃青蛙趣题中,每次有4 种不同的跳跃方式选择。虽然每次选择都不知道能继续走多远。但我们始终清楚地知道最终状态是什么。走迷宫的最终状态是出口;八皇后问题的最终状态是八个皇后都摆在棋盘上;跳跃青蛙趣题的最终状态是左右青蛙位置互换。
16271625
1628- 我们使用相同的策略来解决这些问题:不断尝试可能的选项,记录已经达到的状态,如果无法继续就回溯并尝试其它选项。通过这样的方法,我们或者找到解,或者穷尽所有可能而发现问题无解。当然,这类解法存在一些变化,当找到一个解后,我们可以停下结束或者继续寻找所有可能的解。如果以起始状态为根,画出一棵树,每个树枝代表不同的选择。搜索过程是一个不断深入的过程。只要能继续,我们先不考虑同一深度上的其它选项。直到失败后回溯到树的上一层。如\cref {fig:dfs-tree}中的搜索顺序。箭头描述了如何先向下,在向上回溯的过程 。节点上的数字是访问顺序。
1626+ 我们使用相同的策略来解决这些问题:不断尝试可能的选项,记录已经达到的状态,如果无法继续就回溯并尝试其它选项。通过这样的方法,我们或者找到解,或者穷尽所有可能而发现问题无解。当然,这类解法存在一些变化,当找到一个解后,我们可以停下结束或者继续寻找所有可能的解。如果以起始状态为根,画出一棵树,每个树枝代表不同的选择。搜索过程是一个不断深入的过程。只要能继续,我们先不考虑同一深度上的其它选项。直到失败后回溯到树的上一层。如\cref {fig:dfs-tree}中的搜索顺序。箭头描述了如何先向下,再向上回溯的过程 。节点上的数字是访问顺序。
16291627
16301628\begin {figure}[htbp]
16311629 \centering
@@ -1646,7 +1644,7 @@ \subsubsection{跳棋趣题}
16461644c(h, f).
16471645\end {lstlisting}
16481646
1649- 其中,断言 $ c(X, Y)$ 表示位置$ X$ 和$ Y$ 连通。这一断言是有方向性的 。如果要让$ Y$ 和$ X$ 连通,我们可以增加一条对称的断言,或者建立一条无方向性的断言。\cref {fig:directed-graph}给出了一个有向图。任给位置$ X$ 和$ Y$ ,Prolog可以通过下面的程序判定它们之间是否有通路。
1647+ 其中断言 $ c(X, Y)$ 表示位置$ X$ 和$ Y$ 连通。断言是有方向性的 。如果要让$ Y$ 和$ X$ 连通,我们可以增加一条对称的断言,或者建立一条无方向性的断言。\cref {fig:directed-graph}给出了一个有向图。任给位置$ X$ 和$ Y$ ,Prolog可以通过下面的程序判定它们之间是否有通路。
16501648
16511649\begin {figure}[htbp]
16521650 \centering
@@ -1697,16 +1695,16 @@ \subsubsection{狼、羊、白菜过河问题}
16971695
16981696由于狼不会吃掉白菜,农夫可以安全地将羊运到河对岸并返回。接下来无论将狼或白菜中的任何一样运过河,他必须将某一样运回以避免有东西被吃掉。为了寻找最快的渡河方法,我们并发检查所有的选项,比较哪个更快。不考虑渡河的方向,每渡过一次算做一步,往返算两步。我们检查渡河一次后的所有可能、两次后所有可能、三次后的所有可能……直到某次后,所有的东西都到达了对岸结束。并且这一渡河方法在所有可能中胜出,是最快的解法。
16991697
1700- 如何“并发”检查所有可能的解法?考虑一个抽奖游戏。参与者闭着眼睛从箱子里摸出一个球。箱子里只有一个黑球,其余的球都是白色的。摸到黑球获胜,摸到白球则放回箱子,然后等待下次摸球。为了使游戏公平,可以制定这样一个规则:必须等所有人都摸过之后才能再摸第二次。参与游戏的人站成一队。每次站在队伍前面的人摸球,如果他没有摸到黑球获胜 ,就站到队尾等待下次摸球。这一队列可以保证游戏的公平。
1698+ 如何“并发”检查所有可能的解法?考虑一个抽奖游戏。参与者闭着眼睛从箱子里摸出一个球。箱子里只有一个黑球,其余的球都是白色的。摸到黑球获胜,摸到白球则放回箱子,然后等待下次摸球。为了使游戏公平,可以制定这样一个规则:必须等所有人都摸过之后才能再摸第二次。参与游戏的人站成一队。每次站在队伍前面的人摸球,如果他没有摸到黑球 ,就站到队尾等待下次摸球。这一队列可以保证游戏的公平。
17011699
17021700\begin {figure}[htbp]
17031701 \centering
17041702 \includegraphics [scale=0.5 ]{img/luckydraw-queue}
1705- \caption {第$ i$ 个人出队摸球。如果没有摸到黑球就站到队尾}
1703+ \caption {第$ i$ 个人出队摸球。如果没有摸到黑球就站到队尾。 }
17061704 \label {fig:luck-draw}
17071705\end {figure}
17081706
1709- 用类似的思路来解决渡河问题。用集合$ A$ 、$ B$ 代表河两岸。开始时,集合$ A = \{ w, g, c, p\} $ 包含狼、羊、白菜、农夫,集合$ B = \nil $ 。每次将农夫和另外一个元素在集合间移动。如果集合中不存在农夫,则不能含相互冲突的东西 。目标是用最少的次数交换$ A$ 、$ B$ 的内容。使用一个队列$ Q$ 包含起始状态$ A = \{ w, g, c, p\} $ 、$ B=\nil $ 。只要队列不空,我们就取出头部元素,扩展所有可能的选择。然后将扩展后的状态放回队尾。如果队列头部等于$ A=\nil $ 、$ B=\{ w, g, c, p\} $ ,我们就找到了解。\cref {fig:bfs-tree}描述了搜索顺序。同一深度上的所有可能都被检查了,无需进行回溯。
1707+ 用类似的思路来解决渡河问题。用集合$ A$ 、$ B$ 代表河两岸。开始时,集合$ A = \{ w, g, c, p\} $ 包含狼、羊、白菜、农夫,集合$ B = \nil $ 。每次将农夫和另外一个元素在集合间移动。如果集合中不存在农夫,则不能含有相互冲突的东西 。目标是用最少的次数交换$ A$ 、$ B$ 的内容。使用一个队列$ Q$ 包含起始状态$ A = \{ w, g, c, p\} $ 、$ B=\nil $ 。只要队列不空,我们就取出头部元素,扩展所有可能的选择。然后将扩展后的状态放回队尾。如果队列头部等于$ A=\nil $ 、$ B=\{ w, g, c, p\} $ ,我们就找到了解。\cref {fig:bfs-tree}描述了搜索顺序。同一深度上的所有可能都被检查了,无需进行回溯。
17101708
17111709\begin {figure}[htbp]
17121710 \centering
@@ -1820,7 +1818,7 @@ \subsubsection{倒水问题}
18201818 \label {fig:jugs-r2}
18211819\end {figure}
18221820
1823- 大小两个瓶子$ B, A$ ,每次有6 种操作:(1 ) 小瓶子 $ A$ 装满水;(2 ) $ B$ 装满水;(3 ) 倒空$ A$ ;(4 ) 倒空$ B$ ;(5 ) 将$ A$ 中的水倒入$ B$ ;(6 ) 将$ B$ 倒入$ A$ 。下表是一系列倒水动作,假设容积$ a < b < 2a$ 。
1821+ 大小两个瓶子$ B, A$ ,每次有6 种操作:(1 ) $ A$ 装满水;(2 ) $ B$ 装满水;(3 ) 倒空$ A$ ;(4 ) 倒空$ B$ ;(5 ) 将$ A$ 中的水倒入$ B$ ;(6 ) 将$ B$ 倒入$ A$ 。下表是一系列倒水动作,假设容积$ a < b < 2a$ 。
18241822
18251823\btab {l|l|l}
18261824$ A$ & $ B$ & 操作 \\
@@ -1910,17 +1908,17 @@ \subsubsection{倒水问题}
19101908丢番图方程$ g = x a + b y$ 有无穷多个解,$ |x| + |y|$ 越小,所需步骤越少。我们可以采用类似“过河问题”的思路。在6 种操作中(倒满$ A$ 、倒满$ B$ 、将$ A$ 倒入$ B$ ……)“并行”尝试出最优解。使用一个队列来安排所有的尝试。队列中的元素是一系列值对$ (p, q)$ ,$ p$ 、$ q$ 分别是两瓶中水的体积,记录了从开始到最后的倒水操作。开始时队列内容为:$ \{ [(0, 0)]\} $ 。
19111909
19121910\be
1913- solve\ a\ b\ g = bfs \{ [(0 , 0 )] \}
1911+ solve\ a\ b\ g = \textit { bfs} \{ [(0 , 0 )] \}
19141912\ee
19151913
19161914只要队列不空,我们从头部取出一操作序列。如果序列中的最后状态包含$ g$ 升水,我们就找到了一个解。我们将序列逆序输出;否则我们扩展最后的状态,尝试6 种可能,去掉重复的并入队。
19171915
19181916\be
19191917\begin {array}{rcl}
1920- bfs\ \nil & = & [\ ] \\
1921- bfs\ Q & = & \begin {cases}
1918+ \textit { bfs} \ \nil & = & [\ ] \\
1919+ \textit { bfs} \ Q & = & \begin {cases}
19221920 p \text {或} q = g: & \textit {reverse}\ s, \text {其中} (p, q) = head\ s, (s, Q') = pop\ Q \\
1923- \text {否则}: & bfs\ (pushAll\ (map\ (:s)\ (try\ s))\ Q')
1921+ \text {否则}: & \textit { bfs} \ (pushAll\ (map\ (:s)\ (try\ s))\ Q')
19241922 \end {cases}
19251923\end {array}
19261924\ee
@@ -2061,7 +2059,7 @@ \subsubsection{华容道}
20612059 \label {fig:klotski-jp}
20622060\end {figure}
20632061
2064- 我们用$ 5 \times 4$ 矩阵来代表棋盘,行列从0 开始。1 到10 个数字代表棋子。0 代表空位置。矩阵$ M$ 给出了华容道的初始状态。值为$ i$ 的格子表示有棋子占据。布局用字典$ L$ 代表。$ L[i]$ 棋子 $ i$ 覆盖的位置集合。例如$ L[4] = \{ (2, 1), (2, 2)\} $ 表示第4 个棋子覆盖了位置$ (2, 1)$ 、$ (2, 2)$ 。我们可以把棋盘上的20 个位置编号为0 到19 ,把行列转化为编号:$ c = 4y + x$ 。这样第四个棋子占据$ L[4] = \{ 9, 10\} $ 。
2062+ 我们用$ 5 \times 4$ 矩阵来代表棋盘,行列从0 开始。1 到10 个数字代表棋子。0 代表空位置。矩阵$ M$ 给出了华容道的初始状态。值为$ i$ 的格子表示有棋子占据。布局用字典$ L$ 代表。$ L[i]$ 是棋子 $ i$ 覆盖的位置集合。例如$ L[4] = \{ (2, 1), (2, 2)\} $ 表示第4 个棋子覆盖了位置$ (2, 1)$ 、$ (2, 2)$ 。我们可以把棋盘上的20 个位置编号为0 到19 ,把行列转化为编号:$ c = 4y + x$ 。这样第四个棋子占据$ L[4] = \{ 9, 10\} $ 。
20652063
20662064\[
20672065\begin {array}{cc}
@@ -2259,7 +2257,7 @@ \subsubsection{华容道}
22592257 \label {fig:dfs-bfs-tree}
22602258\end {figure}
22612259
2262- 由于我们无法真正的“并行”搜索,广度优先搜索使用队列来记录尝试。从队列头部不断取出步骤较少的候选项,从队列尾部不断加入步骤较多的新候选项。广度优先搜索提供了一种简单的方法寻找最少步骤解,但它不能直接搜索其它最优解。考虑如\cref {fig:weighted-dag}的有向图,每段路径长度不同,我们无法用广度优先搜索找出两个城市之间的最短路径。注意从城市$ a$ 到城市$ c$ 之间的最短路径并非经过最少城市的$ a \to b \to c$ 。这条路径的总长度为22 ;而是经过更多城市的路径$ a \to e \to f \to c$ ,他的总长度只有 20 。
2260+ 由于我们无法真正的“并行”搜索,广度优先搜索使用队列来记录尝试。从队列头部不断取出步骤较少的候选项,从队列尾部不断加入步骤较多的新候选项。广度优先搜索提供了一种简单的方法寻找最少步骤解,但它不能直接搜索其它最优解。考虑如\cref {fig:weighted-dag}的有向图,每段路径长度不同,我们无法用广度优先搜索找出两个城市之间的最短路径。注意从城市$ a$ 到城市$ c$ 之间的最短路径并非经过最少城市的$ a \to b \to c$ 。这条路径的总长度为22 ;而是经过更多城市的路径$ a \to e \to f \to c$ ,它的总长度只有 20 。
22632261
22642262\begin {figure}[htbp]
22652263 \centering
0 commit comments