动态规划

动态规划

example1 Fibonacci
1 2 3 4 5 6 7 8
1 1 2 3 5 8 13 21 …

1 n=1,2

fib(n)
fib(n) = fib(n-1) + fib(n-2) other // overlap sub-problem

fibonacci问题的备忘录模式:保存已经计算出来的答案,无需再重新计算

example2
有八个任务,每个任务只能在指定的时间段做,红色数字代表任务的价值,要求所选的任务时间上不能重叠,求所有任务组合中价值最大的组合

i prev(i) opt(i)
1 [1-4] 0 5 [1]
2 [3-5] 0 5 [1]
3 [0-6] 0 8 [3]
4 [4-7] 1 9 [1, 4]
5 [3-8] 0 9 [1, 4]
6 [5-9] 2(选和它最接近的) 9 [1, 4]
7 [6-10] 3 10 [3, 7]
8 [8-11] 5 13 [1, 4, 8]

选 vi + opt(prev(i))

opt(i) max:
不选 opt(i-1)

选 4+opt(5)
opt(8)
不选 opt(7)

若第一次做8 ,前面还可以做prev(8) = 5
若第一次做7, 前面还可以做prev(7) = 3
prev(6) = 1
.
.
.
prev(2) = 0

example3 选出不相邻的最大和
arr 0 1 2 3 4 5 6
1 2 4 1 7 8 3

选 arr[i] + opt[i-2]
opt(i) max:
不选 arr[i-1]

递归出口:
opt(0) = arr[0]
opt(1) = arr[1] // 前两个只能选一个,选最大的

// python递归实现 和fibonacci一样,递归会产生很多重叠子问题

arr = [1,  2, 4, 1, 7, 8, 3]
def rec_opt(arr, i):
	if i== 0:
		return arr[0]
	elif i == 1:
		return max(arr[0], arr[1])
	else:
		A = rec_opt(arr , i-2) + arr[i]
		B = rec_opt(arr, i-1)
		return max(A, B)

rec_opt(arr, 6) 	

// python非递归实现

import numpy as np
def dp_opt(arr):
	opt = np.zeros(len(arr))
	opt[0] = arr[0]
	opt[1] = max(arr[0], arr[1])
	for i in range(2,  len(arr)):
		A = opt[i-2]  + arr[i]
		B = opt[i-1]
		opt[i] = max(A, B)
	return opt[ len(arr) -1 ]

example4
判断数组中是否存在和为s的组合

arr = [3, 34, 4, 12, 5, 2]

// 递归实现

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:
		A = rec_subset(arr, i-1, s-arr[i])
		B = rec_subset(arr, i-1, s)
		return A or B

rec_subset(arr, len(arr)-1), 9)

//非递归实现

import numpy as np

def dp_subset(arr, S):
	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:
				A = subset[i-1, s-arr[i]]
				B = subset[i-1, s]
				subset[i, s] = A or B
	r, c = subset.shape
	return subset[r-1, c-1]

dp_subset(arr, 9)

example5 硬币问题
现有若干枚硬币,硬币面值分别为1,5,10,20,50,100
要凑出价值为w,至少需要多少枚硬币。比如w = 666

思路一:每次选取面值最大的硬币(贪心思维,只考虑眼前情况)
666 = 6100 + 150 + 110 + 15 + 1*1
共需要6 + 1 + 1 + 1 + 1 = 10

但是当 硬币面值分别为 1、5、 11 问至少需要多少枚硬币,才能凑出15
按照贪心思维(每次选取价值最大的硬币)
15 = 111 + 41 至少需要5枚

然后,仅仅需要3枚价值5的硬币就可以凑出15
所以 ,贪心思维不靠谱

解决方案:
1、贪心算法、存在局限
2、暴力枚举
3、DP

DP分析:
若要凑出w=15,首先面临三种情况的选择
1、当选取了价值为1的硬币时,将面临凑出价值为14的情况
2、当选取了价值为5的硬币时,将面临凑出价值为10的情况
3、当选取了价值为11的硬币时,将面临凑出价值为4的情况
记凑出价值n 所需要的最少硬币数为f(n)
最优解 1 5 11
F(0) 0 x x x
F(1) 1 1+f(0)=1 x x
F(2) 2 1+f(1)=2 x x
F(3) 3 1+f(2)=3 x x
F(4) 4 1+f(3)=4 x x
F(5) 1 1+f(4)=5 1+f(0)=1 x
F(6) 2 1+f(5)=2 1+f(1)=2 x
F(7) 3 1+f(6)=3 1+f(2)=3 x
F(8) 4 1+f(7)=4 1+f(3)=4 x
F(9) 5 1+f(8)=5 1+f4)=5 x
F(10) 2 1+f(9)=6 1+f(5)=2 x
F(11) 1 1+f(10)=3 1+f(6)=3 1+f(0)=1
F(12) 2 1+f(11)=2 1+f(7)=4 1+f(1)=2
F(13) 3 1+f(12)=3 1+f(8)=5 1+f(2)=3
F(14) 4 1+f(13)=4 1+f(9)=6 1+f(3)=4
F(15) 3 1+f(14)=5 1+f(10)=3 1+f(4)=5
原理:
f(n)只与f(n-1), f(n-5), f(n-11)有关
f(n)=min{1 + f(n-1), 1+f(n-5), 1+f(n-11)}
总结:
最优子结构:大问题的最优解可以由小问题的最优解推出
无后效性:一旦f(n)确定,那么之后直接调用它的值就可以,不要再去关心f(n)的计算过程(就是无需再计算,可以由以前求得的值直接代入)

代码(C)

#include<cstdio>
#include<cstdlib>
using namespace std;

int min(int a, int b)
{
	return a<b?a:b;
}

int main()
{
	int dp[100], i;
	dp[0]=0;
	int cost;
	for(i=1;i<=15;i++)
	{
		cost=999999;
		if(i-1>=0) cost=min(cost, dp[i-1]+1);
		if(i-5>=0) cost=min(cost, dp[i-5]+1);
		if(i-11>=0) cost=min(cost, dp[i-11]+1);
		dp[i]=cost;
		printf(“dp[%d]=%d\n”, i, dp[i]);
	}
	return 0;
}

example6最长上升子序列(LIS)
给定长度为n的序列,从中选取一个子序列,这个子序列需要单调递增
求最长上升子序列的长度

eg:1, 5, 2, 3, 11, 7, 9
则LIS序列为:1,2,3,7,9,长度为5

设计状态
记f(x)为以a[x]结尾的LIS长度,那么LIS=max{f(x)}
如何导出f(x),f(x)从哪里来?
考虑比x小的每一个p,如果a[x]>a[p], 那么f(x)=f§+1

状态转移方程
f(x)=max{f§}+1
p<x,a[p]<a[x]

x: 1 2 3 4 5
a[x] 1 5 3 4 0

f(1)=1
f(2)=2
f(3)=2
f(4)=3
f(5)=1

LIS=3

#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;

int a[100];
int dp[100];
int main()
{
	int n;
	scanf(%d”, &n);
	for(int i=1; i<=n ;i++)
		scanf(%d”, &a[i]);
	
	for(int i=1;i<=n;i++)
	{
		dp[i]=1;
		for(int j=1;j<i; j++)
		{
			if(a[j]<a[i])
				dp[i]  =max(dp[i], dp[j]+1);
		}
		printf(“dp[%d]=%d\n”, i, dp[i]);
	}
	return 0;
}

总结:
哪些题目适合用DP算法解决?如何设计好的DP算法:
1、满足最优子结构
大问题可以由小问题推出,大问题与小问题求解思路一致。
2、满足无后效性
一旦f(n)确定,后续我们直接调用它的值就可以,而不用关心它是怎么过来的
3、设计好状态
想办法把当前局面表达出来
4、设计好状态转移方程
可以从两个方面考虑,我从哪里来,或者我到哪里去
5、代码实现

example7最长公共子序列
s1=abcdaf
s2=abcdf

a	b	c	d	a	f

a 1 1 1 1 1 1
b 1 2 2 2 2 2
c 1 2 3 3 3 3
d 1 2 3 4 4 4
f 1 2 3 4 4 5
s1 length n1
s2 length n2

if (s1.charAt(i) == s2.charAt(j)) {
	dp[i][j] = d[i-1][j-1] +1
} else {
	dp[i][j] = Math.max(d[i][j-1], d[i-1][j-1])
}

return dp[n1][n2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值