Ch4 搜尋策略
搜尋的含義
搜尋問題一般包括兩個重要的問題:
- 搜尋什麼:通常指目標
- 在哪裡搜尋:即搜尋空間,通常指一系列狀態的彙集
搜尋過程實際上是根據初始條件和擴充套件規則構造一棵解答樹並尋找符合目標狀態的節點的過程
啟發式搜尋:依靠經驗,利用已有知識,根據問題的實際情況,不斷尋找可利用知識,從而構造一條代價最小的推理路線,使問題得以解決的過程
典型問題:八數碼
將每一個序列看作一個狀態節點,將所有可能的狀態節點看作一個狀態空間,所有的操作構成一個算符空間,操作可以使狀態轉變,透過搜尋演算法在狀態空間中尋找目標狀態節點
- 要成功地設計和實現搜尋演算法, 必須把下列疑點弄清楚: (評價準則)
- 問題求解器是否一定能找到一個解? 【完備性】
- 問題求解器是否能終止執行, 或是否會陷入一個死迴圈? 【可解性】
- 當問題求解器找到解時, 找到的是否是最好的解?【最優性】
- 搜尋過程的時間與空間複雜性如何?
- 怎樣才能最有效地降低搜尋的複雜性?
簡化搜尋問題——問題規約
問題規約的目的是將一個問題轉化為另一個問題, 使得新問題的解也是原問題的解
端節點:不再有子節點的節點
可解節點:
- 任何終止節點都是可解節點
- 對於 或 節點, 如果它的某個子節點是可解節點,則它也是可解節點
- 對於 與 節點, 如果它的所有子節點都是可解節點,則它也是可解節點
狀態空間的一般搜尋
狀態空間的搜尋可以對應與圖的搜尋,搜尋過程可以看作是在圖中尋找一條從初始狀態到目標狀態的路徑
符號約定:
- Open 表:存放剛生成的節點
- Close 表:存放已經檢查過的節點或將要檢查的節點
- \(S_0\): 初始狀態
- \(G\): 搜尋圖
- \(M\): 表示當前擴充套件節點新生成的且未檢查過的節點集
def Search(S0, goal):
Open = [S0]
Close = []
while Open != []:
N = Open.pop(0)
if N == goal:
return N
M = Expand(N)
for m in M:
if m not in Close:
Open.append(m)
Close.append(N)
spec_sort(Open) # 按某種策略對Open表進行排序
return None
搜尋策略的關鍵在於如何選擇下一個節點,即如何對 Open 表進行排序,DFS 使新生成的節點優先,BFS 使舊節點優先。
DFS 不是最優的,不是完備的!
代價樹搜尋
代價樹搜尋是一種啟發式搜尋,它是一種有向圖搜尋,每個節點都有一個代價值,搜尋的目標是找到代價最小的路徑,典型的代價樹搜尋問題是最短路徑問題。
對於\(n_1\) 擴充套件的出的 \(n_2\), 他的代價值為 \(g(n_1) + c(n_1, n_2)\)
代價樹廣度優先搜尋(按照節點的代價排序):
def CostSearch(S0, goal):
Open = [(S0, 0)]
Close = []
while Open != []:
N = Open.pop(0)
if N == goal:
return N
M = Expand(N)
for m in M:
if m not in Close:
Open.append((m, N[1] + c(N, m)))
Close.append(N)
sort(Open, key=lambda x: x[1]) # 按cost對Open表進行排序
return None
代價樹深度優先搜尋(按照邊的代價排序)
狀態樹的啟發式搜尋
在問題本身的定義之外還利用問題的特定知識的策略
在對問題空間進行搜尋時,提高搜尋效率需要與問題相關的控制性資訊作為搜尋的指導
控制資訊反映在估價函式中,估價函式的任務就是估計待搜尋結點的重要程度,即估計從初始狀態到目標狀態的代價,代價包括兩部分:從初始狀態到當前狀態的代價和從當前狀態到目標狀態的代價,代價越小,說明越能以最小的代價找到目標狀態
估價函式\(f(n)\)定義為:\(f(n) = g(n) + h(n)\), 其中 \(g(n)\) 是從初始狀態到節點 \(n\) 的代價,\(h(n)\) 是從節點 \(n\) 到目標狀態的代價的估計值,\(f(n)\) 是從初始狀態經過節點 \(n\) 到目標狀態的代價的估計值
如果 h(n)=0,g(n)=d(n) 時,就是廣度優先搜尋法。一般講在 f(n) 中,g(n)的比重越大,越傾向於廣度優先搜尋;h(n)的比重越大,越傾向於深度優先搜尋
A 演算法
在圖演算法的每一步利用估價函式 \(f(n)\) 對 open 表排序,則稱為 A 演算法
- 全域性擇優:從 open 表中選取 f(n) 最小的節點進行擴充套件
- 區域性擇優:從剛擴充套件的節點的子節點中選取 f(n) 最小的節點進行擴充套件(Gradient 上升是一種區域性擇優)
例子:八數碼問題
估價函式為不在位的數碼個數
A* 演算法
是對 A 演算法的估價函式加上某些限制的演算法
假設 \(f^\star(n)\)是經過節點 \(n\) 到目標狀態的最小代價,\(f(n)\)是估計值,且\(f^\star(n) = g^\star(n) + h^\star(n)\)
需要保證 \(h(n) \leq h^\star(n)\),實際上這樣的限制能夠有良好的性質, 即是可採納的。\(h(n)\)越小,擴充套件的節點就越多,效率就越低
如果 \(h(n) = 0\), 那麼可以看作是 Dijkstra 演算法
因此就有了如下的限制
-
\(h(n) \leq h^\star(n)\)
-
\(g(n) = g^\star(n), g(n) > 0\)
-
可採納性:當從初始節點到目標節點有路徑存在,如果搜尋演算法總能在有限步驟內找到最佳路徑,並在該條路徑上結束,則稱搜尋演算法是可採納的
A* 演算法的可採納性可以證明
例:給定 4L 和 3L 水壺各一個。水壺上沒有刻度。可以向水壺中加水。如何在 4L 的壺中準確的得到 2L 水?
思考:
(1)為什麼 h(n)≡0 使搜尋過程接近於寬度優先搜尋?(因為擴充套件節點優先從到達的節點代價最小處擴充套件)
(2)為什麼 g(n)≡0 使搜尋過程接近於深度優先搜尋?(因為擴充套件結點優先從到達最優解代價最小的節點擴充套件)
程式碼講解來自 以及 RedBlobGames
def a_star_search(graph, start, goal):
open_list = PriorityQueue()
open_list.put(start, 0)
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0
while not open_list.empty():
current = open_list.get()
if current == goal:
break
for next in graph.neighbors(current):
new_cost = cost_so_far[current] + graph.cost(current, next)
# 如果新的代價更小或者下一個節點不在代價表中
if next not in cost_so_far or new_cost < cost_so_far[next]:
cost_so_far[next] = new_cost
priority = new_cost + heuristic(goal, next)
open_list.put(next, priority)
came_from[next] = current
return came_from, cost_so_far
與或樹搜尋
例: 證明兩個四邊形全等
博弈樹搜尋
來自於博弈論
而二人零和、全資訊、非偶然是最簡單的博弈
在零和的條件下,兩個玩家的利益是完全對立的,一個玩家的收益就是另一個玩家的損失
因此,博弈樹的搜尋就是在兩個玩家之間進行的,每個玩家都希望自己的收益最大,而對方的收益最小,這就是一個 min-max 問題
而博弈樹的搜尋就是在一個操作與或樹的搜尋
博弈過程中,設我方為 A 方,則可供 A 方選擇的若干行動方案之間是“或”關係;在 A 方行動方案基礎上,B 方也有若干個可供選擇的行動方案,則這些方案對 A 方來說就是“與”關係。
例:
(1)對於或結點(表示主方走),在它的全部後繼結點中,若有一個為 S 結點,則標為 S;若全為 F 結點,則標為 F,否則為 H。
(2)對於與結點(表示對方走),在它的全部後繼結點中,若全為 S 結點,則標為 S;若有一個為 F 結點,則標為 F,否則為 H。
而對於葉節點的估值方法:使用不同的得分表示結果,如勝利為 1,失敗為 -1,平局為 0, 對於 non-leaves 節點,使用 min-max 演算法向上倒退
例:分硬幣遊戲