常用动态规划举例(一)

动态规划类问题涉及三个概念:最优子结构,状态转移公式,边界条件。

解决动态规划类问题的首要任务是找到最优子结构,典型的有台阶的走法问题:有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上一级或两级台阶,要求计算一共有多少种走法。解决方法是:第一步,找到最优子结构:如果只差最后一步走到第十阶,这时有几种情况。然后根据最优子结构找到状态转移公式和边界条件,最后可以用动态规划求解。

以下是几个例子,用以练习动态规划类问题。

1.最长上升子序列

题目:输出最长上升子序列的个数,例如,给定数组{2,7,1,5,6,4,3,8,9},输出最长上升子序列的长度

思路:定义数组d[],d[i]表示以a[i]结尾的最长上升子序列,则所有d[i]中最大的值即为所求值。

代码:

#include 
using namespace std;
int main()
{
	int n;
	while(cin >> n)
	{
		int *p = new int[n];
		for (int i = 0; i < n; i++)
			cin >> p[i];
		int len = 1;
        // 定义d[i]
		int *d = new int[n];
        // 动态规划过程
		for (int i = 0; i < n; i++)
		{
			d[i] = 1;
			for (int j = 0; j < i; j++)
			{
				if (p[j] < p[i] && d[j]+1 > d[i])
					d[i] = d[j] + 1;
			}
			if (d[i] > len)
				len = d[i];
		}
		cout << len << endl;
		delete[]p;
		delete[]d;
	}
	system("pause");
	return 0;
}

2.0-1背包问题

题目:已知6种物品和一个可容纳60重量的背包,物品重量 w = {15,17,20,12,9,14},价值为 v = {32,37,46,26,21,30},问如何装包使得总价值最大。

思路:每个物品可以分为装入和不装入,由此可以得到最优子结构和迭代公式:

F(n,c) = \left\{\begin{matrix} F(n-1,c) & if:wi > c \\ max(F(n-1,c),F(n-1,c-wi)+vi) & otherwise \end{matrix}\right.

代码:

#include 
#include 
#include 
using namespace std;

int main()
{
	int n = 6;
	int c = 60;
	int w[] = {15,17,20,12,9,14};
	int v[] = {32,37,46,26,21,30};
	vector> f(7,vector(61,0));
	for (int i = 0; i <= 60; i++)
	{
		f[0][i] = 0;
	}
	for (int i = 0; i <= 6; i++)
	{
		f[i][0] = 0;
	}

	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= 60; j++)
		{
			if(w[i-1] > j)
				f[i][j] = f[i-1][j];
			else
				f[i][j] = max(f[i-1][j],v[i-1] + f[i-1][j-w[i-1]]);
		}
	}
	cout << f[6][60] << endl;
	system("pause");
	return 0;
}

3.计算字符串相似度(最小编辑代价)

题目:

      对于不同的字符串,我们希望能有办法判断相似程度,我们定义了一套操作方法来把两个不相同的字符串变得相同,具体的操作方法如下:1 修改一个字符,如把“a”替换为“b”。2 增加一个字符,如把“abdd”变为“aebdd”。3 删除一个字符,如把“travelling”变为“traveling”。比如,对于“abcdefg”和“abcdef”两个字符串来说,我们认为可以通过增加和减少一个“g”的方式来达到目的。上面的两种方案,都只需要一次操作。把这个操作所需要的次数定义为两个字符串的距离,而相似度等于“距离+1”的倒数。也就是说,“abcdefg”和“abcdef”的距离为1,相似度为1/2。给定任意两个字符串,你是否能写出一个算法来计算出它们的相似度呢?

思路:令dp[m+1][n+1]表示状态转移矩阵,dp[i][j]表示字符串str1[0...i-1]到str2[0..j-1]的最小编辑代价,则可以得到以下四种规则:

(1) dp[0][0]表示空串编辑成空串,故代价为dp[0][0] = 0;

(2) dp[i][0] = i;

(3) dp[0][j] = j;

(4) 其他情况下的 dp[i][j] 可以由边界条件推出,分为以下四种情况:

        ① str1[i] 先编辑为 str1[i-1],再由 str1[i-1] 编辑为 str2[j],dp[i][j] = dp[i-1][j] + 1;

        ② str1[i] 先编辑为 str2[i-1],再由 str2[i-1] 编辑为 str2[j],dp[i][j] = dp[i][j-1] + 1;

        ③ if : str1[i-1] == str2[j-1],dp[i][j] = dp[i-1][j-1];

        ④ if : str1[i-1] != str2[j-1], dp[i][j] = dp[i-1][j-1] + 1;

以上四种情况中最小的 dp[i][j] 即为所求。

代码:

#include 
#include 
#include 
using namespace std;

int main()
{
	string str1,str2;
	while (cin >> str1 >> str2)
	{
		vector>dp (str1.length()+1,vector(str2.length()+1,0));
		dp[0][0] = 0;
		for (int i = 0; i <= str1.length(); i++) dp[i][0] = i;
		for (int j = 0; j <= str2.length(); j++) dp[0][j] = j;
		for (int i = 1; i <= str1.length(); i++)
		{
			for (int j = 1; j <= str2.length(); j++)
			{
				int dp1 = dp[i-1][j]+1;
				int dp2 = dp[i][j-1]+1;
				int dp3 = dp[i-1][j-1];
				if(str1[i-1] != str2[j-1])
					dp3++;
				dp[i][j] = min(dp1,min(dp2,dp3));
			}
		}
		cout << "1/" << dp[str1.length()][str2.length()]+1 << endl;
	}
	system("pause");
	return 0;
}

参考链接:

漫画:什么是动态规划?

动态规划系列问题-最小编辑代价

你可能感兴趣的:(基础练习)