【數學模型】商人們怎樣過河?

這篇博文中,同樣是一個很簡單的數學問題,但是解決起來比上一個的問題要複雜一些。在這次模型求解中,我會使用兩種方法,一種是純粹的數學方法,另一種是通過計算機程序來計算,通過計算機求解我們可以求解一些規模更大的問題。由於這篇文章篇幅我預計會比較長,爲了不混淆,上一篇文章《椅子能在不平的地面上放平嗎?》中的延伸問題我會再寫一篇文章單獨解答。

問題引出

問題: 三名商人各帶一個隨從過河,一隻小船隻能容納兩個人,隨從們約定,只要在河的任何一岸,一旦隨從人數多於商人人數就殺人越貨,但是商人們知道了他們的約定,並且如何過河的大權掌握在商人們手中,商人們該採取怎樣的策略才能安全過河呢?

這次的問題是一個很經常遇到的過河問題,其實對於該類問題,我們經過邏輯思考就可以得到答案。但是通過數學模型的建立,我們可以得到一個通用的解答,並且通過計算機的計算我們可以大大擴大問題的規模。

問題分析

因爲這個問題已經理想化了,所以我們無需對模型進行假設,該問題可以看作一個多步決策問題

每一步,船由此岸劃到彼岸或者由彼岸劃回此岸,都要對船上的人員進行決策(此次渡河船上可以有幾名商人和幾名隨從),在保證安全(兩岸的隨從都不比商人多)的前提下,在有限次的決策中使得所有人都到對岸去。

因此,我們要做的就是要確定每一步的決策,達到渡河的目標。

建立模型

記第 k 次過河前此岸的商人數爲 xk , 隨從數爲 yk , k = 1, 2, 3…, xk ,yk = 0, 1, 2, 3

定義狀態: 將二維向量 sk = ( xk , yk ) 定義爲狀態

將安全渡河狀態下的狀態集合定義爲允許狀態集合, 記爲

S = {(x,y) | x=0,y=0,1,2,3; x=y=1; x=y=2; x=3,y=0,1,2,3}

記第 k 次渡河船上的商人數爲 uk , 隨從數爲 vk

定義決策: 將二維向量 dk = (uk , vk) 定義爲決策

允許決策集合 記作

D = {(u,v) | 1 ≤ u+v ≤ 2, u,v = 0,1,2}

因爲小船容量爲2,所以船上人員不能超過2,而且至少要有一個人划船,由此得到上式。

由我們定義的狀態 sk 和決策 dk ,我們可以發現它們之間是存在聯繫的:

  • k 爲奇數是表示船由此岸划向彼岸,k 爲偶數時表示船由彼岸劃回此岸

  • 狀態 sk 是隨着決策 dk 變化的,規律爲:

sk+1 = sk + (-1)kdk

我們把上式稱爲狀態轉移律,因此渡河方案可以抽象爲如下的多步決策模型

求決策 dk ∈ D(k = 1,2,…,n) , 使狀態 sk ∈ S 按照轉移率,初始狀態 s1 = (3,3) 經有限步 n 到達狀態 sn+1 = (0,0)

到這裏,整個數學模型就已經非常清晰了,接下來要做的就是求解模型得出結果。

求解模型

在這個模型的求解中,我將會使用兩種方法,一種是數學圖解法,用於解決和當前題目一樣的規模比較小的問題,優點是比較簡便,但是對於規模比較大的問題就無能爲力了,比如說有50個商人攜帶50個隨從過河,第二種方法是通過計算機編程,使用程序來解決該問題,即使問題規模增大,我們也可以利用計算機強大的計算能力來解決。

數學圖解法

我們首先在 xOy 平面座標系中畫出如下方格,方格中的點表示狀態 s = (x,y)

image

起始狀態(下圖綠色點) s1 = (3,3) , 終止狀態(下圖紅色點) sn+1 = (0,0)

image

允許決策 dk 表示的是在方格中的移動,根據允許決策 dk 的定義,它每次的移動範圍爲1~2格,並且 k 爲奇數時向左或下方或左下方移動,k 位偶數時向右或上方或右上方移動。

於是,這個問題就變成了,根據允許決策 dk ,在方格中在狀態(方格點)之間移動,找到一條路徑,使得能從起始狀態(上圖綠色點) s1 = (3,3) ,到達終止狀態(上圖圖紅色點) sn+1 = (0,0)

在下圖中,我們給出了一種方案,我們可以很清楚的看到該方案絕對不是最佳方案(渡河次數最少),它只是給出了一種方案,而且我們看來是一種極其不優化的方案,但是可以很清楚地看出圖解法是如何工作的。

image

根據上圖,我們得出的方案如下:

  • d1:兩個隨從劃到對岸
  • d2:一個隨從劃回來
  • d3:兩個隨從劃到對岸
  • d4:一個隨從劃回來
  • d5:兩個商人劃到對岸
  • d6:一個商人和一個隨從劃回來
  • d7:兩個商人劃到對岸
  • d8:一個隨從劃回來
  • d9:兩個隨從劃到對岸
  • d10:一個商人劃回來
  • d11:一個商人和隨從劃到對岸

最終商人們安全渡河

程序求解

我們看到上面介紹的圖解法對於小規模問題很直觀也很簡單,但是無法應對大規模的問題,於是我們採用編程的方法來再次解決上述問題,這次我使用的編程語言爲Python.

創建允許狀態集合

對於允許狀態集合,我們要去使用算法對其進行計算,所謂允許狀態無非就是,河岸兩邊的商人們都是安全的:

  • 一種情況是:兩岸的商人人數都比隨從人數多(對於隨從和商人人數相同的情況就是河的任一岸,商人人數等於隨從人數)
  • 另一情況爲:所有商人都在河的任何一岸,此時另一岸沒有任何商人,對於隨從的人數在河的任一岸的數量不論是多少,此時都是安全的

按照以上方法編程如下:

'''創建允許狀態集合'''    
def allowset(self):
    allowset = []
    for i in range(self.merchants + 1):
        for j in range(self.servants + 1):
            if i == 0:
                allowset.append([i,j])
            elif i == self.merchants:
                allowset.append([i,j])
            elif (i >= j and ((self.merchants-i) >= (self.servants-j))):
                allowset.append([i,j])
    return allowset

創建允許決策集合

對於創建允許決策集合,它和船的容量是相關的,只要每次渡河的商人數量和隨從數量小於等於船的容量即可,代碼如下:

'''創建允許決策集合'''
def allowaction(self):
    allowactionset = []
    for i in range(self.capacity + 1):
        for j in range(self.capacity + 1):
            if (i+j) <= self.capacity and (i + j) != 0:
                allowactionset.append([i,j])
    return allowactionset

如何渡河

對於如何渡河問題我採取的是一種隨機的方法,對於當前安全狀態,隨機選擇一種決策進行試探,如果採取該決策可以到達安全狀態,則採用,如此循環,直到到達目的地。如果採取該策略不能到達安全狀態,則再次隨機選擇一種策略。

代碼如下:

def solve(self,allowactionset,allowstate):
    count = 1;
    current = (self.merchants,self.servants)
    while current != [0,0]:
        move = allowactionset[random.randint(0,len(allowactionset)-1)]
        temp = [current[0]+((-1)**count)*move[0],current[1]+((-1)**count)*move[1]]
        if(temp in allowstate):
            current = [current[0]+((-1)**count)*move[0],current[1]+((-1)**count)*move[1]]
            if(count % 2 == 1):
                print "[%d]個商人,[%d] 個隨從從此岸劃到對岸" %(move[0],move[1])
            elif(count % 2 == 0):
                print "[%d]個商人,[%d] 個隨從從對岸劃回此岸" %(move[0],move[1]) 
        count = count + 1

完整代碼

有了以上算法之後,我們就可以使用計算機來解決一些較大規模的問題了,完整代碼如下:

# -*- coding: utf-8 -*-
# Copyright (c) 2015 Jason Luo @ SDU

"""解決商人安全過河問題"""

import random

class Boat(object):
    def __init__(self, merchants, servants, capacity):
        self.merchants = merchants
        self.servants = servants
        self.capacity = capacity

        print "Initialize: [%d] merchants and [%d] servants" %(merchants, servants)

    '''創建允許狀態集合'''    
    def allowset(self):
        allowset = []
        for i in range(self.merchants + 1):
            for j in range(self.servants + 1):
                if i == 0:
                    allowset.append([i,j])
                elif i == self.merchants:
                    allowset.append([i,j])
                elif (i >= j and ((self.merchants-i) >= (self.servants-j))):
                    allowset.append([i,j])
        return allowset

    '''創建允許決策集合'''
    def allowaction(self):
        allowactionset = []
        for i in range(self.capacity + 1):
            for j in range(self.capacity + 1):
                if (i+j) <= self.capacity and (i + j) != 0:
                    allowactionset.append([i,j])
        return allowactionset

    '''渡河'''
    def solve(self,allowactionset,allowstate):
        count = 1;
        current = (self.merchants,self.servants)
        while current != [0,0]:
            move = allowactionset[random.randint(0,len(allowactionset)-1)]
            temp = [current[0]+((-1)**count)*move[0],current[1]+((-1)**count)*move[1]]
            if(temp in allowstate):
                current = [current[0]+((-1)**count)*move[0],current[1]+((-1)**count)*move[1]]
                if(count % 2 == 1):
                    print "[%d]個商人,[%d] 個隨從從此岸劃到對岸" %(move[0],move[1])
                elif(count % 2 == 0):
                    print "[%d]個商人,[%d] 個隨從從對岸劃回此岸" %(move[0],move[1])

            count = count + 1


'''主方法'''            
def main():
    boat = Boat(3,3,2)
    allowstate = boat.allowset()
    print "允許狀態集合爲:"
    print allowstate

    actionset = boat.allowaction()
    print "允許決策集合爲:"
    print actionset

    boat.solve(actionset,allowstate)


if __name__ == '__main__':
    main()

運行結果

爲了縮短運行結果的篇幅,我們同樣採取小規模問題來驗證算法的正確性,這裏還是採用原問題規模,運行結果如下:

Initialize: [3] merchants and [3] servants
允許狀態集合爲:
[[0, 0], [0, 1], [0, 2], [0, 3], [1, 1], [2, 2], [3, 0], [3, 1], [3, 2], [3, 3]]
允許決策集合爲:
[[0, 1], [0, 2], [1, 0], [1, 1], [2, 0]]
[1]個商人,[1] 個隨從從此岸劃到對岸
[2]個商人,[0] 個隨從從此岸劃到對岸
[2]個商人,[0] 個隨從從對岸劃回此岸
[2]個商人,[0] 個隨從從此岸劃到對岸
[0]個商人,[2] 個隨從從此岸劃到對岸

算法評價

該算法可以解決問題,但是有很多的不足,首先,由此算法得到的結果是隨機的,它只是一個可行解,並不是最優解,並且其中很可能存在重複的步驟,對於一些超大規模的問題,它會產生許多重複的計算,其中會存在許多重複與環,還有許多可以改進的方法。這裏我提出一種改進方法的思路,留待思考:我們可以藉助圖論中的深度優先算法來改進該問題從而得到最優解。如果不一定需要最優解的話,我們還可以在該算法上應用一個隊列的數據結構,記錄曾經在當前狀態採取的策略,從而避免採取重複決策。

參考資料


本文的版權歸作者 羅遠航 所有,採用 Attribution-NonCommercial 3.0 License。任何人可以進行轉載、分享,但不可在未經允許的情況下用於商業用途;轉載請註明出處。感謝配合!

發佈了105 篇原創文章 · 獲贊 237 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章