算法:动态规划

本文深入探讨了动态规划的概念,包括如何设计动态规划算法的四个步骤,并通过最长公共子序列问题进行了详细讲解。示例中分别展示了递归和非递归的实现方法,以及如何避免重复计算以提高效率。此外,还提供了两个例题,分别是斐波那契数列和网格路径计数问题,进一步阐述动态规划的应用。
摘要由CSDN通过智能技术生成


一、动态规划

1. 概念

动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划法求解的问题,经分解得到的子问题往往不是互相独立的。若用分治法解这类问题,则分解得到的子问题数目太多,以至于最后解决原问题需要耗费指数时间。然而,不同子问题的数目常常只有多项式量级。在用分治法求解时,有些子问题被重复计算了许多次。如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算,从而得到多项式时间算法。为了达到这个目的,可以用一个表来记录所有已解决的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中,这就是动态规划法的基本思想。具体的动态规划算法是多种多样的,但它们具有相同的填表格式。

2. 设计步骤

动态规划算法适用于解最优化问题,通常可以按以下步骤设计动态规划算法:
(1) 找出最优解的性质,并刻画其结构特征;
(2) 递归地定义最优值;
(3) 以自底向上的方式计算出最优值;
(4) 根据计算最优值时得到的信息,构造最优解。

3. 示例:最长公共子序列

3.1 判断最长公共子序列长度

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<atomic>
#include<string>
#include<map>
#include<memory>
#include<vector>
#include<queue>
#include<algorithm>
#include<limits.h>
#include<stdexcept>
using namespace std;

//最长公共子序列
int LCSLength(const char* X, const char* Y, int m, int n)
{
	if (m == 0 || n == 0) return 0;
	else
	{
		if (X[m] == Y[n])
		{
			return LCSLength(X, Y, m - 1, n - 1) + 1;
		}
		else
		{
			int max1 = LCSLength(X, Y, m - 1, n);
			int max2 = LCSLength(X, Y, m, n - 1);
			return max1 > max2 ? max1 : max2;
		}
	}
}

int main()
{
	char X[] = { "#ABCDDAB" };// 7
	char Y[] = { "#BDCABA" };// 6
	int xm = strlen(X) - 1, yn = strlen(Y) - 1;
	int maxlen = LCSLength(X, Y, xm, yn);

	cout << maxlen << endl;
	return 0;
}

在这里插入图片描述

3.2递归写法

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<atomic>
#include<string>
#include<map>
#include<memory>
#include<vector>
#include<queue>
#include<algorithm>
#include<limits.h>
#include<stdexcept>
#include<iomanip>// 格式控制
using namespace std;
template<class T>
void Print_Vec(vector<vector<T> >& c)
{
	int m = c.size();
	for (int i = 0; i < m; ++i)
	{
		int n = c[i].size();
		for (int j = 0; j < n; ++j)
		{
			cout << setw(3) << c[i][j];
		}
		cout << endl;
	}
	cout << endl;
}
int LCSLength(const char* X, const char* Y, int m, int n,
	vector<vector<int> > &c, vector<vector<int> >& s)
{
	if (m == 0 || n == 0) return 0;
	else if (c[m][n] != 0) return c[m][n];
	else
	{
		if (X[m] == Y[n])
		{
			c[m][n] = LCSLength(X, Y, m - 1, n - 1,c,s) + 1;
			s[m][n] = 1;
		}
		else
		{
			int max1 = LCSLength(X, Y, m - 1, n,c,s);
			int max2 = LCSLength(X, Y, m, n - 1,c,s);
			if (max1 > max2)
			{
				c[m][n] = max1;
				s[m][n] = 2;
			}
			else
			{
				c[m][n] = max2;
				s[m][n] = 3;
			}
		}
	}
	return c[m][n];
}

void LCS(const char *X, vector<vector<int> > &s, int i,int j)
{
	if (i == 0 || j == 0) return;
	if (s[i][j] == 1)
	{
		LCS(X, s, i - 1, j - 1);
		cout << X[i];
	}
	else if (s[i][j] == 2)
	{
		LCS(X, s, i - 1, j);
	}
	else
	{
		LCS(X, s, i, j - 1);
	}
}
int main()
{
	char X[] = { "#ABCBDAB" };
	//            01234567
	char Y[] = { "#BDCABA" };
	//            0123456
	int xm = strlen(X) - 1, yn = strlen(Y) - 1;
	vector<vector<int> > c,s;
	c.resize(xm + 1);
	s.resize(xm + 1);
	for (int i = 0; i < xm + 1; ++i)
	{
		c[i].resize(yn + 1, 0);
		s[i].resize(yn + 1, 0);
	}
	int maxlen = LCSLength(X,Y, xm,  yn,c,s);

	Print_Vec(c);
	Print_Vec(s);
	cout << maxlen << endl;

	LCS(X, s, xm, yn);
	return 0;


}

在这里插入图片描述

3.3非递归写法

void LCSLength(string& X, string& Y, vector<vector<int>>& c, vector<vector<int>>& s)
{
	int n = X.size() - 1;//物理下标
	int m = Y.size() - 1;
	for (int i = 0; i <= n; ++i) c[i][0] = 0;
	for (int j = 0; j <= m; ++j) c[0][j] = 0;
	for (int i = 0; i <= n; ++i)
	{
		for (int j = 0; j <= m; ++j)//区域值
		{
			if (X[i] == Y[j])
			{
				c[i][j] = c[i - 1][j - 1] + 1;
				s[i][j] = 1;
			}
			else if (c[i - 1][j] > c[i][j - 1])
			{
				c[i][j] = c[i - 1][j];
				s[i][j] = 2;
			}
			else
			{
				c[i][j] = c[i][j - 1];
				s[i][j] = 3;
			}
		}
	}
}

在这里插入图片描述

4.例题

古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问第24个月的兔子总数为多少?(斐波那契数列)

//斐波那契数列消除重复
int fac(int n, int a, int b)
{
	if (n == 1 || n == 2) return a;
	return fac(n - 1, a + b, a);
}
int fac(int n)
{
	int a = 1, b = 1;
	return fac(n, a, b);
}

5.例题

一个机器人位于一个m*n网格地左上角(起始点在下图中标记欸“start"),机器人每次只能向下或者向右移动一步。机器人视图达到网格地右下角(在下图中标记为”finish")。问一共有多少条不同的路径?

//动规方程式
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];

在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值