动态规划算法的思想
基本思想与分治算法类似,也是将待求解的问题划分为若干个子问题,按照划分的顺序求子阶段问题。前一个子问题的解,为后一个子问题求解提供了有用的信息。在求解任意子问题时,列出各种可能的局部解,通过决策保留可以达到最优的局部解,丢弃其它局部解。依次解决各个子问题,最后求出原问题的最优解。
其与分治算法最大的区别是:适用于动态规划算法的求解问题,经分解后得到的子问题往往不是相互独立的。
动态规划求解问题的基本步骤:
动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状
态。动态规划算法的代码设计都有一定的模式,一般都要经过以下几个步骤:
- 找出最优解的性质,并刻划其结构特征。(找问题状态)
- 递归地定义最优值。(找状态转移方程)
- 自底向上的方式计算出最优值。
- 根据计算最优值时得到的信息,构造最优解。
硬币选择问题
有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;
}