動態程式設計(DynamicProgramming)

雪飛鴻發表於2018-12-15

本文素材來自視訊,請自備梯子觀看:What Is Dynamic Programming and How To Use It

Dynamic Programming:動態程式設計分為如下幾步:

  1. 將複雜問題拆分成多個較簡單的子問題
  2. 對每個子問題只計算一次,然後使用資料結構(陣列,字典等)在記憶體中儲存計算結果
  3. 子問題的計算結果按照一定規則進行排序(如,基於輸入引數)
  4. 當需要再次運運算元問題時直接使用已儲存的計算結果而非再次運算以提升求解效能

這種儲存計算結果以備再次使用稱之為:Memoization(這個詞,不知道怎麼翻譯好)

以斐波那契數列為例來說明:

1、使用遞迴實現:

def fib(n):
    if n < 1:
        raise ValueError(`引數n必須為大於0的整數`)
    if n == 1 or n == 2:
        return 1
    return fib(n-2)+fib(n-1)

這種方法是經典的遞迴運算。以fib(5)為例,整個求解過程可以拆分為:
圖片來自Youtube
我們可以看出,fib(2)被計算三次,fib(3)與fib(1)各被計算2次,時間複雜度為O(2^n)。

2、對遞迴進行改進

def fib_memory(n):
    d = dict()
    _fib_memory(n, d)


def _fib_memory(n, temp_dict):
    if n < 1:
        raise ValueError(`引數n必須為大於0的整數`)
    if type(temp_dict) is not dict
        raise TypeError(`引數temp_dict必須為dict型別`)
    if n in temp_dict:
        return temp_dict[n]
    if n == 1 or n == 2:
        result = 1
    else:
        result = fib_memory(n-1, temp_dict)+fib_memory(n-2, temp_dict)
    temp_dict[n] = result
    return result

圖片來自Youtube
優化後,時間複雜度降為O(n)。優化後的演算法依然使用了遞迴,當引數較大時(如,1000)會導致棧溢位:
RecursionError: maximum recursion depth exceeded in comparison

3、脫離遞迴:

def fib_bottom_up(n):
    l = [None]*(n+1)
    return _fib_bottom_up(n, l)

def _fib_bottom_up(n, temp_list):
    if n < 1:
        raise ValueError(`引數n必須為大於0的整數`)
    if type(temp_list) is not list:
        raise TypeError(`引數temp_list必須為list型別`)
    if temp_list[n] is not None:
        return temp_list[n]
    if n == 1 or n == 2:
        return 1
    temp_list[1] = 1
    temp_list[2] = 1
    for i in range(3, n+1):
        temp_list[i] = temp_list[i-1]+temp_list[i-2]
    return temp_list[n]

圖片來自Youtube

改進之後的演算法不再使用遞迴,時間複雜度依然是O(n)。


對以上三種實現編寫測試用例:

# coding=utf-8

import temp
import unittest


class TestDif(unittest.TestCase):
    def test_fib_0_throw_value_error(self):
        with self.assertRaises(ValueError):
            temp.fib(0)

    def test_fib_1_return_1(self):
        result = temp.fib(1)
        self.assertEqual(1, result)

    def test_fib_10_return_false(self):
        result = temp.fib(10)
        self.assertFalse(result == 10)

    def test_fib_memory_10_return_false(self):
        result = temp.fib_memory(10)
        self.assertNotEqual(result, 10)

    def test_fib_bottom_up_1000_return_true(self):
        result = temp.fib_bottom_up(1000)
        print(result)
        self.assertTrue(result > 100000)


if __name__ == "__main__":
    unittest.main()

小結

無意中在Youtube上看到這段視訊,就翻譯整理下來與大家共享。


相關文章