DP Dynamic Programming acquaintance

In front of the HMM parameter solution, will be used in dynamic programming, full of all kinds of probability formula, there are some abstract, decided today to give a simple wave of the chestnuts, to help understand the DP put a big issue, continue to be divided into smaller sub the problem in this way to solve, is the dynamic programming. this is the most intuitive and popular understanding.

DP vs recursive

I also often get confused before DP and recursive recursive, in fact, function calls itself . In a way, recursion and DP have their similarities. My understanding is that, DP reduces the recursive time complexity, specific say, DP solved recursively repeats calculation problem (Coverlap) problem. to illustrate the problem, to find a video paragraphs from station B to help understanding.

case: Fibonacci number

Known: 1, 1, 2, 3, 5, 8, 13, 21, 34 ......

Evaluation of the first number n.

In fact, this is a very typical of a class of problems expressed by recursion, recursive focus on two factors: the exit conditions, recurrence relations

"""
主题:
    动态规划 之 斐波那契数列
问题特点:
    类似递归, 有很多的重复子问题 overlap
描述:
    f(1) = 1
    f(2) = 1
    f(3) = f(2) + f(3)
    f(4) = f(3) + f(2)
    ...
    f(n) = f(n-1) + f(n-1)

"""
def rec_opt(n):
    """递归-求解斐波那契数列"""
    if n <= 2:
        return 1
    return rec_opt(n - 1) + rec_opt(n - 2)

Very well understood, but such an approach would bring any problem it is high time complexity is? \ (O (the n-2 ^) \) , for example, to calculate f (5):

f (5) = f (4) + f (3) is then divided into two forks

Left: F (. 4) = F (. 3) + F (2)

​ f(3) = f(2) + f(1)

Right side: F (. 3) = F (2) + F (. 1)

Ye eyes look, brother, there is not found, the left side in the calculation of f (4) when the calculated f (3), f (2 ), f (1); while the right side in calculating f (3) the time, and in the double counting f (2), f (1 ), as the larger the amount of double counting will grow exponentially , this is called overlap. this is recursive, at this time, it will greatly increase the first time complexity .

In fact, how we think, since (3) the time will be used in the calculation of f, f (1), f (2); f (4) the time will be used f (3) + f (2 ). .. well, why are we not think that these historical data to calculate a good save up, use it directly?

That is, you can use a list to save up to historical data, use the time to directly index ok, complexity is: \ (O (the n-) \) such uses historical data to estimate the latter method is dynamic programming.

  • Exit condition
  • The initial value
  • State transition equation
def dp_opt(n):
    """DP 求解斐波那契数列"""
    # 用来存历史数据的列表
    lst = [0 for _ in range(n)]
    # 退出条件
    if n < 2:
        return 1
    # 初始化数列的第 0,1 项
    lst[0], lst[1] = 1, 1
    for i in range(2, n):
        # 状态转移方程
        lst[i] = lst[i - 1] + lst[i - 2]
    return lst[n - 1]

Just to test the wave versus time

def calc_func_time(func, n):
    print(f"test 规模为{n}的函数{func}运行时间为:")
    start_time = time.time()
    func(n)
    end_time = time.time()

    print("time used", end_time - start_time, "s.")


if __name__ == '__main__':
    # 递归就是不容易使用装饰器写计时器来哦
    n = 40  # 规模
    calc_func_time(dp_opt, n)
    calc_func_time(rec_opt, n)
C:\Python\Python36\python.exe E:/Pytest/base_case/DP-斐波那契数列.py
test 规模为40的函数<function dp_opt at 0x00000271927B7840>运行时间为:
time used 0.0 s.
test 规模为40的函数<function rec_opt at 0x0000027190902E18>运行时间为:
time used 39.237488985061646 s.

You can see, the complexity of recursion is really terrible, and the DP is very calm ,

When the size of more than 1,000 recursion, my computer can not move ... and test wave of DP, bigger.

if __name__ == '__main__':
    # 递归就是不容易使用装饰器写计时器来哦
    n = 10000  # 1万规模
    calc_func_time(dp_opt, n)
test 规模为100000的函数<function dp_opt at 0x0000026675A67840>运行时间为:
time used 0.5376913547515869 s.

DP really rocks! After complexity \ (O (n) \) Well

DP entry-level case

Here is a simple process to solve the chestnuts to become familiar with a little complicated, I am a still studying, not arrogantly .....

Whole chapter to illustrate a core skill: selected or not selected .

case1: n steps of the stair climb

The fact is this: Chen students have a pair of very very envious of big legs, now, to climb a level of order n, each time either go up one step, to either go up three steps , I ask, Chen students from the bottom of the climb to the top step, how to plant a total of possible scenarios?

analysis:

First, the definition of a function f (n) indicates n-th class when there climb the maximum number of programs is

f (1) = 1 1 most kinds of programs, a step up

f (2) = 1 up to 1 in Scheme 1 step down, walk step 1

f (3) = 2 up to two kinds of programs, go one step was repeated three times; or go directly to Step 3

f (4) = f (1) + f (3) up to three kinds of programs, a step was repeated four times; the first step 1 and then step 3; step 3 first, and then a step

...

In fact, f (n-), has to reach before the n 2 choices: either selected from the f (n-1); selecting either f (n-3), and the two cases, is the total number of programs

f(n) = f(n-1) + f(n-3)

def dp_craw(n):
    # 1. 用list来存储历史数据,可理解为最优方案的函数值
    opt = [0 for _ in range(n+1)]
    # 2. 写退出条件
    if n <= 2:
        return 1
    elif n == 3:
        return 2
    # 3. 初始化历史数据
    opt[0] = 1
    opt[1] = 1
    opt[2] = 2
    # 4. 状态转移方程
    for i in range(2, n):
        opt[i] = opt[i-1] + opt[i - 3]
    return opt[n-1]

if __name__ == '__main__':
    print(dp_craw(50))
C:Python36\python.exe E:/Pytest/base_case/DP爬台阶.py
83316385

Of course, this write up with duplicate entries, the complexity does not \ (O (n) \) ah, do not want changed, mainly to clarify this line of thinking is like.

case2: selecting the maximum value given bunch of numbers (with constraints)

"""
描述:
    arr = [1, 2, 4, 1, 7, 8, 3]
    下标:  0, 1, 2, 3, 4, 5, 6
需求:
    目标: 从数组(列表) 中选择任意个元素, 求出其之和最大的值
    约束: 相邻元素不能选, 如选了8, 则不能选 7 和 3
思路:
    定义一个最优方案函数 opt(n) 表示前n个中最好的选择方案
    比如本例, opt(1) = 1, opt(2)=1
    tips: 选和不选
    抽象: 对于下标i, 有选和不选
        if 选择当前值arr[i]:
            值 = opt(i-2) + arr[i]  不能选相邻元素, 从前i-2个中选最好方案
        else:
            值 = opt(i-1)  不选,则中前i-1 个中选择最好方案
    最后比较取最大, 即: max( (opt(i-1) + arr[i]), opt(i) )
    递归出口:
        opt[0] = arr[0]  前下标0个, 即第一位, 只有一种最优方案,就是选第一个值arr[0]
        opt[1] = max( arr[0], arr[1]) 前两个,则从1,2中选最大

"""


def rec_opt(arr, i):
    """递归"""
    if i == 0:
        return arr[0]
    elif i == 1:
        return max(arr[0], arr[1])
    else:
        select_i = rec_opt(arr, i - 2) + arr[i]
        no_select_i = rec_opt(arr, i - 1)

        return max(select_i, no_select_i)


# 递归是产生很多的重叠子问题, overlap 之前演示过与DP的对比,复杂度高很多
def dp_opt(arr):
    """DP实现"""
    n = len(arr)
    # 用一个list来存储前 i 个最优方案的值, opt 比 lst 要更形象些
    opt = [0 for _ in range(n)]
    # 初始化前0, 前1的小标下的最优方案值
    opt[0] = arr[0]
    opt[1] = max(arr[0], arr[1])
    # 从第3个元素起, 后面项将前面的项作为其子问题
    for i in range(2, n):
        # 选择下标为i的值和不选, 比较取最大, 前面的值都有存opt
        select_i = opt[i - 2] + arr[i]
        no_select_i = opt[i - 1]
        opt[i] = max(select_i, no_select_i)

    return opt[n - 1]


if __name__ == '__main__':
    opt = [1, 2, 4, 1, 7, 8, 3]
    opt1 = [4, 1, 1, 9, 1]

    print(rec_opt(opt, 6))
    print(dp_opt(opt1))

case3: digital sum value for the

"""
描述:
    arr = [3, 34, 4, 12, 5, 12]
    S = 9
需求:
    从 arr 中选择数字, 使其值和等于定值 S=9, 如果可以返回 True, 否则 False
思路:
    跟之前一样的思路: 对于每个元素, 有两种选择,选 or 不选
    定义一个subset(arr, i, s)
    if 选择arr[i]:
        A =  subset(arr, i, s-arr[i])
    else:
        B = subset(arr, i-1, s)
    A or B 是True 则True

    退出条件:
    if sub_set(arr[i], s) 中 s=0 则 return True
    if sub_set(arr[0], s) 中 arr[0] != s  return False

    还约定, arr的每个元素都是正整数.即当 arr[i] > s, 只考虑不选, 即: sub_set(arr[i-1], s)


"""

def rec_subset(arr, i, s):
    if s == 0:
        return True
    elif i == 0:
        return arr[0] == s
    elif arr[i] > s:
        return rec_subset(arr, i - 1, s)
    else:
        # arr[i] < s, 有选和不选两种情况
        select_i = rec_subset(arr, i - 1, s - arr[i])
        no_select_i = rec_subset(arr, i - 1, s)
        # 二者其中一个满足条件即可
        return select_i or no_select_i
    
    
import numpy as np

# 用2维数组来记录,行代表arr[i], 列代表s=1, s=2...
def dp_subset(arr, s):
    """DP求解"""
    subset = np.zeros((len(arr), s + 1), dtype=bool)
    subset[:, 0] = True
    subset[0, :] = False
    subset[0, arr[0]] = True
    for i in range(1, len(arr)):
        for s in range(1, s + 1):
            if arr[i] > s:
                subset[i, s] = subset[i - 1, s]
            else:
                select_i = subset[i - 1, s - arr[i]]
                no_select_i = subset[i - 1, s]
                subset[i, s] = select_i or no_select_i

    row, col = subset.shape
    return subset[row - 1, col - 1]


if __name__ == '__main__':
    lst = [3, 34, 4, 12, 5, 12]
    print(rec_subset(lst, len(lst) - 1, 9))
    print(dp_subset(lst, 9))
True
True

Getting on to this bar, then followed a wave of complex study of DP. Still feel very exercise thinking ability. Ah, Zhashui it, I feel a bit hard for me, especially the multi-dimensional arrays, graphs, trees this piece of abstract .. in the words of my colleague that I, number theory may be, but the space does not work ... this feeling, God does not reward meal ah ....

Guess you like

Origin www.cnblogs.com/chenjieyouge/p/12142877.html