算法设计与分析:动态规划 || 装配线调度问题、矩阵链乘法问题、最长公共子序列问题、01背包问题代码实现...

递归是从n逐步化简直到递归出口的过程(递归出口往往十分简单),而动态规划则是从原来设计的递归出口,反向分析到n的过程,动态规划往往比递归运行效率更高。动态规划可以算作递归的剪枝优化版,由于使用到了额外的空间保存已经计算过的信息,可以节省大量重复计算的时间。

动态规划就是制表的过程

算法设计与分析系列主要是完成书上的例题或习题,题面可能不完善或简略。

装配线调度问题

算法设计与分析:动态规划 || 装配线调度问题、矩阵链乘法问题、最长公共子序列问题、01背包问题代码实现..._第1张图片

装配线调度问题

求进厂到出厂中时间最短的线路,由上图。

①状态转移方程:

fp[i] = min{fp[i - 1], fp'[i - 1] + tp'[i - 1]}

②设计表格f(+号表示同类的延展,下同)

存放:最短时间 结点编号+
第一条装配线

第二条装配线

核心代码:

f[0][1] += a[1];
    f[1][1] += b[1];
    for(int i = 2; i <= n; i++)
    {
        f[0][i] = min(f[0][i - 1], f[1][i - 1] + t[1][i - 1]) + a[i];
        f[1][i] = min(f[0][i - 1] + t[0][i - 1], f[1][i - 1]) + b[i];
    }

完全可运行的代码:

#include 
#include 
using namespace std;


const int N = 1001;
int f[2][N];


int main()
{
    int n;
    cin >> n;
    int a[n + 10], b[n + 10], t[2][n + 10];
    for(int i = 1; i <= n; i++)
    cin >> a[i];
    for(int i = 1; i <= n; i++)
    cin >> b[i];
    for(int i = 1; i <= n; i++)
    cin >> t[0][i];
    for(int i = 1; i <= n; i++)
    cin >> t[1][i];
    cin >> f[0][1] >> f[1][1];
    f[0][1] += a[1];
    f[1][1] += b[1];
    for(int i = 2; i <= n; i++)
    {
        f[0][i] = min(f[0][i - 1], f[1][i - 1] + t[1][i - 1]) + a[i];
        f[1][i] = min(f[0][i - 1] + t[0][i - 1], f[1][i - 1]) + b[i];
    }
    /*输出表格*/
    for(int i = 1; i <= n; i++)
    {
        cout << f[0][i] << '\t';
    }
    cout << endl;
    for(int i = 1; i <= n; i++)
    {
        cout << f[1][i] << '\t';
    }
    int mmin = min(f[0][n] + t[0][n], f[1][n] + t[1][n]);
    cout << mmin;
}

给出测试数据:

//测试数据结构:

节点个数

第一条流水线处理时间

第二条流水线处理时间

第一条流水线转至第二条流水线消耗时间

第二条流水线转至第一条流水线消耗时间

入场时间(入1流水,入2流水)

//输入结束

6
7 9 3 4 8 4
8 5 6 4 5 7
2 3 1 3 4 3
2 1 2 2 1 2
2 4

得到输出

9       18      20      24      32      35
12      16      22      25      30      37      38

答案是正确的,路径记录则再开一个数组,可以轻松实现。

矩阵链乘法问题

给出一串矩阵相乘(一定满足可乘性质)求最小乘法次数。

由矩阵乘法的定义,一个p行q列乘q行r列的矩阵的乘法次数为pqr。

当划分为1个矩阵时不需要乘,即为0;

①状态转移方程

f[i, j] = min( f[i, k] + f[k + 1, j] + 对应pqr)
边界条件:如果只有一个矩阵,那乘法不需要进行,为0
填表方向:沿对角线

②设计表格f

存放:从i乘到j最小乘法次数
右边界:j+
左边界:i+

③结果:

f[i, j]

核心代码:

for(int i = 1; i <= n; i++)
    f[i][i] = 0; // 初始化边界条件
    for(int m = 2; m <= n; m++)//观察到i从1到n-1循环,j先从2开始到n,再从3开始到n,用m表示起点
    for(int j = m, i = 1; j <= n; j++, i++, i == n ? i = 1 : 1)
    for(int k = i; k < j; k++)//关键:沿着对角线方向依次打表
    {
        int tmp = f[i][k] + f[k + 1][j] + height[i] * weight[k] * weight[j];
        f[i][j] =  (tmp < f[i][j])||!f[i][j] ? tmp : f[i][j];
    }

完全代码:

#include 
#include 
using namespace std;


const int N = 1010;
int f[N][N];


int main()
{
    int n;
    cin >> n;
    int height[n + 10], weight[n + 10];
    for(int i = 1; i <= n; i++)
    cin >> height[i] >> weight[i];
    for(int i = 1; i <= n; i++)
    f[i][i] = 0; // 初始化边界条件
    for(int m = 2; m <= n; m++)//观察到i从1到n-1循环,j先从2开始到n,再从3开始到n,用m表示起点
    for(int j = m, i = 1; j <= n; j++, i++, i == n ? i = 1 : 1)
    for(int k = i; k < j; k++)//关键:沿着对角线方向依次打表
    {
        int tmp = f[i][k] + f[k + 1][j] + height[i] * weight[k] * weight[j];
        f[i][j] =  (tmp < f[i][j])||!f[i][j] ? tmp : f[i][j];
    }
    /*打印表*/
    for(int i = 1; i <= n; i++, cout << endl)
    for(int k = 1; k <= n; k++)
    cout << f[i][k] << '\t';
    cout << f[1][n];
}

使用测试用例:

6
30 35
35 15
15 5
5 10
10 20
20 25
//书上的测试用例,第一行表示矩形个数,后面依次表示行、列数

得到结果:

0       15750   7875    9375    11875   15125
0       0       2625    4375    7125    10500
0       0       0       750     2500    5375
0       0       0       0       1000    3500
0       0       0       0       0       5000
0       0       0       0       0       0
15125

得到的结果是正确的。

最长公共子序列问题

最长公共子序列问题曾经在acwing学习过

点击阅读

最长公共子序列问题,简称LCS(Longest Common Subsequence)是指寻找两字符串中最长的公共子序列算法,在生活中具有许多应用。使用一般枚举的办法时间复杂度为O(n*2^m)为指数级,这里研究动规解法。

有字符串{x1,...,xi},{y1, ..., yj}要寻找两字符串中最长公共子序列。

①状态转移方程

用f[i][j]表示x字符串1~i,y字符串1~j中最长公共子序列长度,可以得到如下状态转移方程

如果i位置和j位置字符相同,则
f[i][j] = f[i - 1][j - 1] + 1;
如果i位置和j位置字符不同,证明该位置对公共子序列无贡献
f[i][j]的值应取f[i][j - 1]和f[i - 1][j]中的大值
f[i][j] = max{f[i][j - 1], f[i - 1][j]}
边界条件:如果字符串长度为0,那必然没有,于是为0
填表方向:横向


②设计表格

存放:X:1~i,Y:1~j最长公共子序列长度
s2:j+
s1:i+

③结果

结果取f[i][j]

核心代码:

for(int i = 1; i < s1.length(); i++)
    for(int j = 1; j < s2.length(); j++)
    if(s1[i] == s2[j]) f[i][j] = f[i - 1][j - 1] + 1;
    else f[i][j] = max(f[i - 1][j], f[i][j - 1]);

全代码:

#include 
#include 
using namespace std;


const int N = 1000;
int f[N][N];
int main()
{
    string s1, s2;
    cin >> s1 >> s2;
    for(int i = 0; i < s1.length(); i++)
    f[i][0] = 0;
    for(int j = 0; j < s2.length(); j++)
    f[0][j] = 0;
    //边界条件
    s1 = ' ' + s1;
    s2 = ' ' + s2;
    //处理,使得s1从1开始,便于后续存放
    for(int i = 1; i < s1.length(); i++)
    for(int j = 1; j < s2.length(); j++)
    if(s1[i] == s2[j]) f[i][j] = f[i - 1][j - 1] + 1;
    else f[i][j] = max(f[i - 1][j], f[i][j - 1]);
    /*打印表格*/
    for(int i = 0; i < s1.length(); i++, cout << endl)
    for(int j = 0; j < s2.length(); j++)
    cout << f[i][j] << '\t';
    cout << f[s1.length() - 1][s2.length() - 1];
}

测试数据:

使用书上的测试数据

ABCBDAB
BDCABA

输出:

0       0       0       0       0       0       0
0       0       0       0       1       1       1
0       1       1       1       1       2       2
0       1       1       2       2       2       2
0       1       1       2       2       3       3
0       1       2       2       2       3       3
0       1       2       2       3       3       4
0       1       2       2       3       4       4
4

得到的表格与书上一致。

01背包问题

背包问题也曾发过推送

点击查看背包问题

这里介绍01背包,物品不能分割,只有两种状态,拿或不拿,求给定容积的背包能拿的最大价值。

对于背包空间为w,物品1~i,求最大价值

①状态转移方程

根据物品i要么放要么不放的性质
对物品i讨论
f[i][w] = max{ f[i - 1][w], f[i - 1][w - wi] + vi}
状态转移方程即对不放和放两种状态取最大值
边界条件:当i = 0无物可放 或 w = 0 空间耗尽时为0
填表方向:横向

②设计表格

存放:当前i,w所能存放的最大价值
容积w+
物品数i+

③结果

取结果f[i][w]

核心代码:

for(int i = 0; i <= n; i ++)
    f[i][0] = 0;
    for(int i = 0; i <= wmax; i++)
    f[0][i] = 0;//边界条件


    for(int i = 1; i <= n; i++)
    for(int j = 1; j <= wmax; j++)
    {
        f[i][j] = f[i - 1][j];
        if(w[i] <= j)f[i][j] = max(f[i][j], f[i - 1][j - w[i]] + v[i]);
    }//横向制表

全代码:

#include 


using namespace std;


const int N = 1000;
int f[N][N];
int main()
{
    int w[N], v[N];
    int n, wmax;
    cin >> n >> wmax;
    for(int i = 1; i <= n; i ++)
    cin >> w[i] >> v[i];


    for(int i = 0; i <= n; i ++)
    f[i][0] = 0;
    for(int i = 0; i <= wmax; i++)
    f[0][i] = 0;//边界条件


    for(int i = 1; i <= n; i++)
    for(int j = 1; j <= wmax; j++)
    {
        f[i][j] = f[i - 1][j];
        if(w[i] <= j)f[i][j] = max(f[i][j], f[i - 1][j - w[i]] + v[i]);
    }
    /*打印表格*/
    for(int i = 0; i <= n; i++, cout << endl)
    for(int j = 0; j <= wmax; j++)
    cout << f[i][j] << '\t';


    cout << f[n][wmax];


}

测试数据:

使用书上的测试数据

4 5
2 12
1 10
3 20
2 15

输出:

0       0       0       0       0       0
0       0       12      12      12      12
0       10      12      22      22      22
0       10      12      22      30      32
0       10      15      25      30      37
37

结果和书上一致。

算法设计与分析:动态规划 || 装配线调度问题、矩阵链乘法问题、最长公共子序列问题、01背包问题代码实现..._第2张图片

你可能感兴趣的:(算法,动态规划,矩阵,数学建模,线性代数)