三個水桶等分8升水的問題 -《演算法的樂趣》

wjaning發表於2021-09-09

智力題目

有三個容積分別為3升、5升、8升的水桶,其中容積為8升的水桶中裝滿了水,容積為3升和容積為5升的水桶都是空的。三個水桶都沒有刻度,現在需要將大水桶中的8升水等分成兩份,每份都是4升水,附加條件是隻能這三個水桶,不能借助其他輔助容器。

“恩,是的,這是一個很經典的問題。”

“然而,我們並不能想全,不信請繼續往下看。”

答案

”廢話不多說,直接看方法吧。“

第一種(7步)

  1. 將8L的水桶中的水,倒滿5L的水桶,這時:8L水桶為3L、5L水桶為5L、3L水桶為0L
  2. 將5L的水桶中的水,倒滿3L的水桶,這時:8L水桶為3L、5L水桶為2L、3L水桶為3L
  3. 將3L的水桶中的水,倒入8L的水桶,這時:8L水桶為6L、5L水桶為2L、3L水桶為0L
  4. 將5L的水桶中的水,倒入3L的水桶,這時:8L水桶為6L、5L水桶為0L、3L水桶為2L
  5. 將8L的水桶中的水,倒入5L的水桶,這時:8L水桶為1L、5L水桶為5L、3L水桶為2L
  6. 將5L的水桶中的水,倒滿3L的水桶,這時:8L水桶為1L、5L水桶為4L、3L水桶為3L
  7. 將3L的水桶中的水,倒入8L的水桶,這時:8L水桶為4L、5L水桶為4L、3L水桶為0L

第二種(8步)

  1. 將8L的水桶中的水,倒滿3L的水桶,這時:8L水桶為5L、5L水桶為0L、3L水桶為3L
  2. 將3L的水桶中的水,倒入5L的水桶,這時:8L水桶為5L、5L水桶為3L、3L水桶為0L
  3. 將8L的水桶中的水,倒滿3L的水桶,這時:8L水桶為2L、5L水桶為3L、3L水桶為3L
  4. 將3L的水桶中的水,倒滿5L的水桶,這時:8L水桶為2L、5L水桶為5L、3L水桶為1L
  5. 將5L的水桶中的水,倒入8L的水桶,這時:8L水桶為7L、5L水桶為0L、3L水桶為1L
  6. 將3L的水桶中的水,倒入5L的水桶,這時:8L水桶為7L、5L水桶為1L、3L水桶為0L
  7. 將8L的水桶中的水,倒滿3L的水桶,這時:8L水桶為4L、5L水桶為1L、3L水桶為3L
  8. 將3L的水桶中的水,倒入5L的水桶,這時:8L水桶為4L、5L水桶為4L、3L水桶為0L

我相信答案肯定不止兩個,到底有多少種答案?

帶著這個疑問,我們來設計一個演算法吧。

問題分析

人的思維

解決這個問題的關鍵是怎麼透過倒水湊出確定的1升水或能容納1升水的空間。

例如,當8L水桶或5L水桶或3L水桶有1L水時,都能快速倒出4L水。

計算機思維

“窮舉法”

水桶初始狀態:8L水桶裝滿水,3L和5L的水桶為空。
水桶最終狀態:3L水桶為空,5L和8L的水桶各4L水。

假設將每個狀態下三個水桶中的水的體積作為status。

從 $status = array(8,0,0) 得到 $status = array(4,4,0)。

當然還會有一些限制:

1.各個水桶的都有最大值:

0 <= status[0] <= 8;

0 <= status[1] <= 5;

0 <= status[2] <= 3;

2.當前倒水之後各個水桶的狀態,與歷史倒水之後各個水桶的狀態,不能相同。

3.當前水桶為空時,不能倒給其他水桶。

4.當前水桶為最大容積時,其他水桶不能再向這個水桶倒水。

程式程式碼(PHP)

/**
 * 三個水桶等分8升水的問題,程式碼示例(php)
 * @author 訢亮
 */

$bucket_limit = [8, 5, 3];
$bucket_value = [8, 0, 0];
$bucket = new Bucket($bucket_value, $bucket_limit, []);
$result = $bucket->getResult();

echo "一共有 ".count($result)." 種倒水方法,方法如下:<br> <pre>";
print_r($result);

class Bucket
{

    static protected $_change_bucket_path = []; //倒水的過程記錄

    protected $_bucket_values;  //每個水桶的當前容積
    protected $_bucket_limit;   //每個水桶的容積閾值
    protected $_history_status; //所有歷史水桶容積狀態的集合

    public function __construct($bucket_value = [], $bucket_limit = [], $history_status = [])
    {
        $this->_bucket_values  = $bucket_value;
        $this->_bucket_limit   = $bucket_limit;
        $this->_history_status = array_merge($history_status, [$this->_bucket_values]);

        $this->run();
    }


    public function run() {
        for ($i=0; $i<count($this->_bucket_values); $i++) {
            for ($j=$i+1; $j<count($this->_bucket_values); $j++) {
                $this->changeBucketValue($i, $j);
                $this->changeBucketValue($j, $i);
            }
        }
    }

    public function getResult() {
        return self::$_change_bucket_path;
    }


    /**
     * 倒水
     * @param int $target_idx  目標水桶(被倒水的水桶)
     * @param int $current_idx 當前水桶(倒水的水桶)
     * @return bool
     */

    protected function changeBucketValue($target_idx = 0, $current_idx = 0) {
        $value = $this->_bucket_values;
        if ($target_idx == $current_idx ||
            $this->_bucket_values[$current_idx] == 0 ||
            $this->_bucket_values[$target_idx] == $this->_bucket_limit[$target_idx]
        ) {
            return false;
        }

        if (($this->_bucket_limit[$target_idx] - $this->_bucket_values[$target_idx]) <= $this->_bucket_values[$current_idx]) {
            $water = $this->_bucket_limit[$target_idx] - $this->_bucket_values[$target_idx];
        } else {
            $water = $this->_bucket_values[$current_idx];
        }

        $value[$target_idx] += $water;
        $value[$current_idx] -= $water;

        if ($value === [4,4,0]) {
            self::$_change_bucket_path[] = array_merge($this->_history_status, [$value]);
        } else {
            if (!$this->checkBucketStatus($value)) {
                new Bucket($value, $this->_bucket_limit, $this->_history_status);
            }
        }
    }

    /**
     * 驗證當前水桶狀態是否存在過
     * @param array $current_status 當前水桶狀態
     * @return bool
     */
    protected function checkBucketStatus($current_status = []) {
        foreach ($this->_history_status as $k) {
            if ($current_status === $k) {
                return true;
            }
        }
        return false;
    }
}

執行結果

一共有 16 種倒水方法,方法如下:

(16種方法,貼上去太長了,大家在本地嘗試下。)

小結

執行程式碼之後,一共找到了 16 種倒水的方法,最快的方法需要 7 個步驟。

“怎麼樣,是不是沒想到會有這麼多方法吧,去考考你身邊的小夥伴吧。”

本文歡迎轉發,轉發請註明作者和出處,謝謝!

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2983/viewspace-2824014/,如需轉載,請註明出處,否則將追究法律責任。

相關文章