本博客為講解過河問題,具體問題如下:
你想運送五個動植物過河,分別是 (1) 花 (2)螞蚱(3)青蛙 (4) 蛇 (5) 鷹. 如果沒有人看着,老鷹會吃蛇, 蛇會吃青蛙, 青蛙吃螞蚱, 螞蚱破壞花。你的船一次最多能載除你之外的兩樣。
主要講解python代碼問題。
util.py
首先是對數據結構的創建,此為一個優先隊列,具體用到了heapq庫中的函數,具體我在Python標准庫之heapq有所介紹
import heaqp
相當於#include,可以調用這個庫中的函數
# Data structure for supporting uniform cost search.
class PriorityQueue:
def __init__(self):
self.DONE = -100000
self.heap = []
self.priorities = {} # Map from state to priority
# Insert |state| into the heap with priority |newPriority| if
# |state| isn't in the heap or |newPriority| is smaller than the existing
# priority.
# Return whether the priority queue was updated.
def update(self, state, newPriority):
oldPriority = self.priorities.get(state)
if oldPriority == None or newPriority < oldPriority:
self.priorities[state] = newPriority
heapq.heappush(self.heap, (newPriority, state))
return True
return False
# Returns (state with minimum priority, priority)
# or (None, None) if the priority queue is empty.
def removeMin(self):
while len(self.heap) > 0:
priority, state = heapq.heappop(self.heap)
if self.priorities[state] == self.DONE: continue # Outdated priority, skip
self.priorities[state] = self.DONE
return (state, priority)
return (None, None) # Nothing left...
在這段代碼中,相當於建立了一個PriorityQueue類,小姐姐將其封裝到util.py中,我們只需要import util就可以調用其中的函數。
def init(self)相當於對這個類進行了初始化。在初始化的過程中設置了Done、heap以及priorities,其中Done是用來做比較的,這個節點被遍歷過后,就會將其的優先級設置為Done。heap為一個列表,存儲的為此圖的優先隊列。priorities為一個字典,屬於python的一種數據結構,類似於map。
update函數,將一個狀態的優先級進行改變,如果這個狀態未在優先級隊列中,將其加入,如果在,則對比優先級,如果新的優先級小於久的優先級,則更新隊列中此狀態的優先級
removeMin是將優先隊列中的優先級最小的(不為Done)的節點以及優先級返回,並將其節點的優先級設為Done。
CrossRiverProblem.py
import util
首先調用剛剛寫好util這個文件,其中有我們想用的方法。
def allSubsets(s, limit):
if len(s) == 0 or limit == 0:
return [[]]
return allSubsets(s[1:], limit) \
+ [[s[0]] + r for r in allSubsets(s[1:], limit-1)]
allSubsets函數簡單的來說就是將一個集合返回其所有的子集(子集的長度小於limit)。其中這里的limit相當於船一次可以帶走的動物數量。
eg:s=(1,4), return [(1,4),(1),(4),()]
這個函數的寫法運用了遞歸,首先進行了判斷,如果s這個集合的長度等於零或者limit為0的話,那么返回一個空的列表。如果不為0的話則繼續調用這個函數,這里的''是代表着連接下一行的意思。[1:]是切片,選取[start:end]end默認為最后一個,start默認為第0個。也就是說,選出了除第一個外的所有元素,相應的limit進行減1,也就是在[1:]選取了長度limit-1或者長度小於limit-1的子集。然后再去除一個元素進行limit-1的篩選。也就是用s[0]對其篩選的元素進行一個合並。在python中,列表相加,相當於合並,類似於c++中的string相加。舉個例子
nums1 = [1,2]
nums2 = [2,3]
nums = nums1+nums2
print(nums)
out:[1,2,2,3]
這個函數返回了農夫可以帶走動植物的集合。
class CrossRiverProblem:
# what is a state e.g. ((1,1,1,1,1), 0),((0,1,1,0,1), 0)
# state 狀態
def __init__(self, N, S):
self.N = N #N=5
self.S = S #S=2
# Return start state ((0,0,0,.....,0),0)
def startState(self):
return (tuple(0 for _ in range(self.N)), 0)
# Return TRUE if state is a goal state ((1,1,.....,1),1)
def isEnd(self, state):
return state == ((tuple(1 for _ in range(self.N))), 1)
# Return a list of successor states and costs
# (后繼節點和代價 of state)
def succAndCost(self, state):
print("expand: " + str(state))
animals = state[0]
ship = state[1]
result = []
for s in allSubsets([i for i in range(self.N) if animals[i] == ship], self.S):
temp = list(animals)
for i in s:
temp[i] = 1-ship
newState = (tuple(temp), 1-ship)
if self.isValidState(newState):
result.append((newState, 1))
return result
def isValidState(self, state):
animals = state[0]
ship = state[1]
for i in range(self.N - 1):
if animals[i] != ship and animals[i+1] != ship:
return False
return True
首先也是進行類的一個初始化,N為除農夫以外所有的動植物,S則為農夫一次可以帶過河的最大數量。(這道題中N為5S為2)
startState,返回一個初始狀態的元組,我們知道一開始農夫和所有的動植物並沒有過河,所以狀態全部為0,這里將農夫單獨設置為一個元素。所以返回的為((0,0,0,0,0),0)
isEnd是判斷是否結束,如果未結束的話,就是元組中含有0,於是這里就返回了與元組全為1比較的結果。
我們先說isValidState這個函數,判斷當前狀態是否符合安全狀態,狀態相鄰的動植物在一個河岸且農夫不在,則為不安全狀態。state[0]為動物的位置,假設初始狀態state[0]為(0,0,0,0,0),state[1]=0。
succAndCost函數挑選和農夫在一個對岸的動植物進行子集合選取,也就是一開始的allSubsets函數,將農夫的位置進行調換,(從一變零,從零變一,這里采取的是用一減),判斷是否符合安全狀態,如果符合就添加到result中,result.append((newState, 1))是將(newState, 1)元組添加到result中。
# 等代價搜索
# (TotalCost of optimal solution, history of solution)
def uniformCostSearch(problem):
state = problem.startState()
open = util.PriorityQueue()
open.update(state, 0)
while True:
state, pastCost = open.removeMin()
if problem.isEnd(state):
print("Total cost: " + str(pastCost))
return pastCost, []
for newState, cost in problem.succAndCost(state):
open.update(newState, pastCost + cost)
等代價搜索,open為在util中寫的優先隊列類,其為算法對應的open表。然后就是進行等代價搜索。