动态规划

动态规划算法的思想

基本思想与分治算法类似,也是将待求解的问题划分为若干个子问题,按照划分的顺序求子阶段问题。前一个子问题的解,为后一个子问题求解提供了有用的信息。在求解任意子问题时,列出各种可能的局部解,通过决策保留可以达到最优的局部解,丢弃其它局部解。依次解决各个子问题,最后求出原问题的最优解。

其与分治算法最大的区别是:适用于动态规划算法的求解问题,经分解后得到的子问题往往不是相互独立的。

动态规划求解问题的基本步骤:
动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状
态。动态规划算法的代码设计都有一定的模式,一般都要经过以下几个步骤:

  1. 找出最优解的性质,并刻划其结构特征。(找问题状态)
  2. 递归地定义最优值。(找状态转移方程)
  3. 自底向上的方式计算出最优值。
  4. 根据计算最优值时得到的信息,构造最优解。

硬币选择问题

有1,3,5,面额的硬币,给定一个面值11,求给定面值需要的最少的硬币。

const int n = 303;
int dp[n+1] = { 0 };  // dp[n] : 组成价值n需要的硬币最少数量

int fun(int n) {
	if (dp[n] > 0) {  // dp[n]这个子问题已经被求解过了
		return dp[n];
	}
	if (n == 1 || n == 3 || n == 5) {
		dp[n] = 1;  // 代表了一个子问题最优解的性质(状态)
		return 1;
	}
	else if (n == 2 || n == 4) {
		dp[n] = 2;
		return 2;
	}
	else {
		int n1 = fun(n - 1) + 1; // 选择了1分硬币
		int n2 = fun(n - 3) + 1; // 选择了3分硬币
		int n3 = fun(n - 5) + 1; // 选择了5分硬币
		dp[n] = min({ n1, n2, n3 });
		return dp[n];
	}
}

int main()
{
	cout << fun(n);
	return 0;
}

非递归实现:

int main()
{
	vector<int> v = { 1,3,5 };
	int c = 18;
	vector<int> dp(c+1,0); // dp[c]   dp[0] = 0
	for (int i = 1; i <= c; ++i) 
	{
		dp[i] = i; // 表示初始全部由1分硬币组成
		for (int j = 0; j < v.size(); ++j) 
		{
			if (i >= v[j] && (1 + dp[i - v[j]]) < dp[i]) 
			{
				dp[i] = 1 + dp[i - v[j]];
			}
		}
	}

	cout << dp[c] ;
	return 0;
}

斐波那契数列

//递归
int fabnacci(int n, int dp[]) {
	if (dp[n] > 0) { // 子问题n之前被求解过了
		return dp[n];
	}
	if (n == 1 || n == 2) {
		dp[n] = 1;
		return 1;
	}
	else {
		dp[n] = fabnacci(n - 1, dp) + fabnacci(n - 2, dp);
		return dp[n] ;
	}
}

//非递归
int main()
{
	const int n = 10;
	int dp[n + 1] = {0};
	dp[1] = dp[2] = 1;
	for (int i = 3; i <= n; ++i) {
		dp[i] = dp[i - 1] + dp[i - 2];
	}
	cout << dp[n] << endl;
	return 0;
}

最大子段和

int main()
{
	int ar[] = { -2, 11, -4, 13, -5, 100 };
	const int n = sizeof(ar) / sizeof(ar[0]);
	int dp[n] = { 0 }; // 状态
	dp[0] = ar[0] < 0 ? 0 : ar[0];
	int maxval = dp[0];

	for (int i = 1; i < n; ++i) {   // O(n)
		dp[i] = ar[i] + dp[i - 1];  
		if (dp[i] < 0) {
			dp[i] = 0;
		}

		if (dp[i] > maxval) {
			maxval = dp[i];
		}
	}

	cout << maxval << endl;

	return 0;
}
		

最大非降子序列

int main()
{
	int arr[] = { 8,6,7,8,10 };
	const int n = sizeof(arr) / sizeof(arr[0]);
	int dp[n] = { 0 };
	int maxval = 0;

	for (int i = 0; i < n; ++i) {
		dp[i] = 1;
		for (int j = 0; j < i; ++j)
		{
			if (arr[j] <= arr[i])
			{
				dp[i] = 1 + dp[j];
			}
		}

		if (dp[i] > maxval) {
			maxval = dp[i];
		}
	}
	cout << maxval << endl;
	return 0;
}

求两个序列的最长公共子序列的长度   子串(连续的)

helloworl  hlweord   => 情况1
helloworlr  hlweor    => 情况2

X : X1,X2...Xn
Y:  Y1,Y2...Ym

状态的转移方程
如果Xn == Ym
dp(X[1...n],Y[1...m]) = dp(X[1...n-1], Y[1...m-1]) + 1
如果Xn != Ym
dp(X[1...n],Y[1...m]) = max{dp(X[1...n],Y[1...m-1]) ,  dp(X[1...n-1],Y[1...m]) }

状态:给定的两个序列的LCS的长度
dp[n][m] : n表示第一个串的长度   m表示第二个串的长度,n行m列元素的值,记录的就是这两个串的LCS长度

// 递归实现
int LCS(string X, int n, string Y, int m) {
	if (n < 0 || m < 0) {
		return 0;
	}

	if (dp[n][m] >= 0) {  // 查表,查子问题的解是否被求过
		return dp[n][m];
	}

	cnt++; 

	if (X[n] == Y[m]) {
		dp[n][m] = LCS01(X, n - 1, Y, m - 1) + 1;
		path[n][m] = 1;  // n,m  => n-1,m-1  对角线
		return dp[n][m];
	}
	else {
		int len1 = LCS01(X, n, Y, m - 1);
		int len2 = LCS01(X, n - 1, Y, m);
		if (len1 >= len2) {
			dp[n][m] = len1;
			path[n][m] = 2; // n,m => n,m-1  左边
		}
		else {
			dp[n][m] = len2;
			path[n][m] = 3; // n,m => n-1,m   上方
		}
		return dp[n][m];
	}
}


void backStrace(string str1, int n, int m) {
	if (n <= 0 || m <= 0) {
		return;
	}

	if (path[n][m] == 1) {  // 对应位置的元素是相等的
		backStrace(str1, n - 1, m - 1); // 向对角线递归
		cout << str1[n-1];
	}
	else {
		if (path[n][m] == 2) {
			backStrace(str1, n, m - 1);  // 向左递归
		}
		else {  // path[n][m] = 3
			backStrace(str1, n - 1, m); // 向上递归
		}
	}
}


// 非递归实现
int LCS(string X, int i, string Y, int j) {
	for (int n = 1; n <= i+1; ++n) {
		for (int m = 1; m <= j+1; ++m) {
			if (X[n-1] == Y[m-1]) {
				dp[n][m] = 1 + dp[n - 1][m - 1];  // n==0 m ==0
				path[n][m] = 1;
			}
			else {
				int len1 = dp[n-1][m]; // 上面来
				int len2 = dp[n][m-1]; // 左边来
				if (len1 >= len2) {
					dp[n][m] = len1;
					path[n][m] = 3;
				} 
				else {
					dp[n][m] = len2;
					path[n][m] = 2;
				}
			}
		}
	}
	return dp[i+1][j+1];
}

01背包

void backStrace(int w[],int v[],int n,int c,int**dp)
{
	int bestv = 0;
	for (int i = 0; i < n; i++)
	{
		if (dp[i][c] != dp[i + 1][c])
		{
			//选择了第i个物品
			cout << w[i] << " ";
			bestv += v[i];
			c -= w[i];
		}
	}

	//单独处理最后一行
	if (dp[n][c] > 0)
	{
		bestv += v[n];
		cout << w[n] << " ";

	}
	cout << endl;
	cout << bestv;
}

int main()
{
	int w[] = { 8,6,4,2,5 };
	int v[] = { 6,4,7,8,6 };
	int n = sizeof(w) / sizeof(w[0]) - 1;
	int c = 12;
	int **dp = nullptr;
	dp = new int*[n+1];
	for (int i = 0; i < n+1; i++)
	{
		dp[i] = new int[c + 1]();
	}

	//初始状态的值	最后一行
	for (int j = 1; j <= c; j++)
	{
		//第n个物品大于背包容量
		if (w[n] > j)
		{
			dp[n][j] = 0;
		}
		else //小于
		{
			dp[n][j] = v[n];
		}
	}

	for (int i = n - 1; i >= 0; i--)
	{
		for (int j = 1; j <= c; j++)
		{
			// 第i个物品无法装入背包
			if (w[i] > j)
			{
				dp[i][j] = dp[i + 1][j];
			}
			else
			{
				dp[i][j] = std::max(dp[i + 1][j],v[i]+ dp[i + 1][j-w[i]]);
			}
		}
	}

	backStrace(w, v,n, c, dp);
	

	for (int i = 0; i < n+1; i++)
	{
		delete[]dp[i];
	}
	delete[]dp;
	return 0;
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值