动态规划(英语:Dynamic programming,简称 DP),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。
简单来说,动态规划其实就是,给定一个问题,我们把它拆成一个个子问题,直到子问题可以直接解决。然后呢,把子问题答案保存起来,以减少重复计算。再根据子问题答案反推,得出原问题解的一种方法。
动态规划最核心的思想,就在于拆分子问题,记住过往,减少重复计算。
1.把电路布线问题作为动态规划算法的第一个问题是不太合适的,因为这个问题看着比较复杂,难以看懂,而且抽象成代码比较困难,但是实际需要我去讲这个问题,所以就把电路布线问题作为我学习动态规划的第一个问题。
看问题:
这是书上给的内容,看完这个题目和分析,是不是感觉很懵,这个问题我们需要get到如下几点就够了。
1.每个端点都有端点对应,一个上端点对应一个下端点,我们可以看成从上端点1到10按照顺序向下连线。因此我们可以使用一维数组来存储这个电路板,其中上端点为数组下标,下端点为数组的值。(这些限制了解法1,解法2大于解法1)
2.该问题就是求解在电路网格中最多有几个互不相交的线,其实就是求这些布线中最多选几条线使其不相交。
下面进行动态规划思路的思考。
动态规划的问题思考分为5个步骤
1.状态表示:就是思考dp数组是什么,代表什么意思,我们可以构建一个二维的dp数组,dp[i][j]表示在当前上下端点的连接之下,我们原来网格中最多有几个互不相交的电线。如dp[7][5]代表从上端点7到下端点5这条线左边有几条不相交线路
2:状态转移方程:第二张图中给了我们状态转移方程,简要分析如下:
上端点为1时,dp[1][j],当j<a[1](上端点1对应的下端点a[1])时,dp[1][j]=0,当j>=a[1]时,dp[1][j]=1
上端点i大于1时
若j小于a[i]时,就是指下端点还没有到我们当前该上端点对应的下端点上,因为不包括i到a[i]这条线,就是说删除i点对结果没有影响,所以就有dp[i][j]=dp[i-1][j],而dp[i-1]的所有值已算出
若j大于等于a[i],说明包括了i到a[i]这条线,此时需要比较dp[i-1][a[i]-1]+1与dp[i-1][j]的关系,dp[i][j]=max{dp[i-1][a[i]-1]+1,dp[i-1][j]},若前者比较大,dp[i-1][a[i]-1]+1表示当前的原dp数组中加入这条线,后者表示该线不加入当前确定的最大数组中,而是选择以这个dp数组的前一个节点作为最大值向后面更新。
3.初始化
根据状态转移方程来初始化,对这个二维dp数组初始化,我们最好把dp等于0的情况全部初始化为0,防止对数组中未初始化的值进行访问。
4.构建dp表
5.返回值
这个返回值就是dp[n][n],就是二维数组的最后一个值,因为二维dp数组的更新大的值一定会沉到后面,因此dp[n][n]就是我们的返回值。
代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#define N 100
int main()
{
int i, j, n;
//创建dp表
int a[N], dp[N][N];
printf("请输入端点个数:");
scanf("%d", &n);
printf("请输入各端点对应的下端点:");
for (i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
//初始化dp表
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= n; j++)
{
if (i == 0 || j == 0)
dp[i][j] = 0;
}
}
//填表
for (i = 1; i <= n; i++) {//上端点为1的情况
if (i < a[1]) dp[1][i] = 0;
else dp[1][i] = 1;
}
for (i = 2; i <= n; i++) {//其他情况
for (j = 1; j <= n; j++) {
if (j < a[i]) {//小于対应线的坐标
dp[i][j] = dp[i - 1][j];
}
else {//大于等于
//printf("%d \n", dp[i - 1][a[i] - 1]);
if (dp[i - 1][j] > dp[i - 1][a[i] - 1] + 1) {//更新dp最大值
dp[i][j] = dp[i - 1][j];
}
else
dp[i][j] = dp[i - 1][a[i] - 1] + 1;
}
}
}
// 返回结果
printf("%d\n", dp[n][n]);
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= n; j++)
{
printf("%d ", dp[i][j]);
}
printf("\n");
}
return 0;
}
该题目还有解法2,分析可以知道,这个题目其实是求最大递增子序列题目的子题目,因此可以用求最大递增子序列的方法求解该题目。这个题目后面再讲,该解法代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#define N 100
int max(int a, int b)
{
return a > b ? a : b;
}
int main()
{
int n;
int a[N];
printf("请输入端点个数:");
scanf("%d", &n);
printf("请输入各端点对应的下端点:");
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
// 创建dp表
int dp[N];
//初始化dp表
for (int i = 0; i < n; i++)
{
dp[i] = 1;
}
// 填表
int ret = 0;
for (int i = 1; i < n; i++)
{
for (int j = 0; j < i; j++)
{
if (a[i] > a[j])
dp[i] = max(dp[j] + 1, dp[i]);
}
if (dp[i] > ret)
ret = dp[i];
}
// 返回结果
printf("%d ", ret);
return 0;
}
参考博客: