等代價搜索——過河問題


本博客為講解過河問題,具體問題如下:
你想運送五個動植物過河,分別是 (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表。然后就是進行等代價搜索。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM