N0.76——Astar算法解決傳教士野人過河問題Python實現

問題描述

有三個傳教士和三個野人一起來到河邊準備渡河,河邊有一條空船,且傳教士和野人都會划船,但每次最多可供兩人乘渡。河的任何一岸以及船上一旦出現野人人數超過傳教士人數,野人就會把傳教士喫掉。爲安全 地渡河,傳教士應該如何規劃渡河方案?試給出該問題的狀態圖表示,並編程求解之。

若傳教士和野人的數碼均爲 5 人,渡船至多可乘 3 人,請定義一個啓發函數,並給出相應的搜索樹。

解決思路

設 M 爲傳教士總人數,C 爲野人總人數,K 爲每條船乘坐人數,船的狀態表示爲 b。

在 M=3,C=3,K=2 情況下:

令 m 表示在左岸的傳教士人數,c 表示在左岸的野人數,b=1 表示船在左岸, b=0 表示船在右岸。

因此可用(m, c, b)表述當前狀態。

則,初始狀態爲(3, 3, 1), 目標狀態爲(0, 0, 0)。

渡河時,爲確保安全,需滿足:

0 ≤ m ≤ 3,0 ≤ c ≤ 3, b ∈ {0,1}, m > c(m 不爲 0 時), 3-m > 3-c(m 不爲 3 時)

啓發函數:h=m+c-kb

代碼示例:

# -*- coding: utf-8 -*-
import operator

__metaclass__ = type

M = 5  # 傳教士
C = 5  # 野人
K = 3  # 每船乘坐人數
child = []  # child用來存所有的拓展節點
open_list = []  # open表
closed_list = []  # closed表


class State:
    def __init__(self, m, c, b):
        self.m = m  #左岸傳教士數量
        self.c = c  #左岸野人數量
        self.b = b  # b = 1: 船在左岸;b = 0: 船在右岸
        self.g = 0
        self.f = 0  #f = g+h
        self.father = None
        self.node = [m, c, b]

init = State(M, C, 1)  # 初始節點
goal = State(0, 0, 0)  # 目標

#0 ≤ m ≤ 3,0 ≤ c ≤ 3, b ∈ {0,1}, 左岸m > c(m 不爲 0 時), 右岸3-m > 3-c(m 不爲 3 時)
def safe(s):
    if s.m > M or s.m < 0 or s.c > C or s.c < 0 or (s.m != 0 and s.m < s.c) or (s.m != M and M - s.m < C - s.c):
        return False
    else:
        return True


# 啓發函數
def h(s):
    return s.m + s.c - K * s.b
    # return M - s.m + C - s.c

def equal(a, b):
    if a.node == b.node:
        return True

# 判斷當前狀態與父狀態是否一致
def back(new, s):
    if s.father is None:
        return False
    return equal(new, s.father)

# 將open_list以f值進行排序
def open_sort(l):
    the_key = operator.attrgetter('f')  # 指定屬性排序的key
    l.sort(key=the_key)


# 擴展節點時在open表和closed表中找原來是否存在相同mcb屬性的節點
def in_list(new, l):
    for item in l:
        if new.node == item.node:
            return True, item
    return False, None


def A_star(s):
    global open_list, closed_list
    open_list = [s]
    closed_list = []
    # print ('open list:')
    # print ('closed list:')  # 選擇打印open表或closed表變化過程
    print(s.node)
    while open_list:  # open表非空
        get = open_list[0]  # 取出open表第一個元素get
        if get.node == goal.node:  # 判斷是否爲目標節點
            return get
        open_list.remove(get)  # 將get從open表移出
        closed_list.append(get)  # 將get加入closed表

        # 以下得到一個get的新子節點new並考慮是否放入openlist
        for i in range(M):  # 上船傳教士
            for j in range(C):  # 上船野人
                # 船上非法情況
                if i + j == 0 or i + j > K or (i != 0 and i < j):
                    continue
                if get.b == 1:  # 當前船在左岸,下一狀態統計船在右岸的情況
                    new = State(get.m - i, get.c - j, 0)
                    child.append(new)
                else:  # 當前船在右岸,下一狀態統計船在左岸的情況
                    new = State(get.m + i, get.c + j, 1)
                    child.append(new)
                #優先級:not>and>ture。如果狀態不安全或者要拓展的節點與當前節點的父節點狀態一致。
                if not safe(new) or back(new, get):  # 狀態非法或new折返了
                    child.pop()
                #如果要拓展的節點滿足以上情況,將它的父親設爲當前節點,計算f,並對open_list排序
                else:
                    new.father = get
                    new.g = get.g + 1  #與起點的距離
                    new.f = get.g + h(get)  # f = g + h

                    # 如果new在open表中(只關注m,c,b的值)
                    if in_list(new, open_list)[0]:
                        old = in_list(new, open_list)[1]
                        if new.f < old.f:  # new的f<open表相同狀態的f
                            open_list.remove(old)
                            open_list.append(new)
                            open_sort(open_list)
                        else:
                            pass

                    # 繼續,如果new在closed表中
                    elif in_list(new, closed_list)[0]:
                        old = in_list(new, closed_list)[1]
                        if new.f < old.f:
                            # 將old從closed刪除,並重新加入open
                            closed_list.remove(old)
                            open_list.append(new)
                            open_sort(open_list)
                        else:
                            pass
                    else:
                        open_list.append(new)
                        open_sort(open_list)
                        # 打印open表或closed表
                        for o in open_list:
                        # for o in closed_list:
                            print(o.node)
                        print

# 遞歸打印路徑
def printPath(f):
    if f is None:
        return
    printPath(f.father)
    #注意print()語句放在遞歸調用前和遞歸調用後的區別。放在後實現了倒敘輸出
    print(f.node )


if __name__ == '__main__':
    print ('有%d個傳教士,%d個野人,船容量:%d' % (M, C, K))
    final = A_star(init)
    if final:
        print ('有解,解爲:')
        printPath(final)
    else:
        print ('無解!')

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章