算法设计与分析课-实验-动态规划

算法设计与分析课

第一题

矩阵连乘问题:

分析:该问题求矩阵连乘积最少乘次数,对于两个矩阵A(i行j列)和B(j行n列),他们相乘的乘次数为ijn,对于两个矩阵只有它们的列和行相等才能相乘。

用数组A接受矩阵的行列,对于一个矩阵An,其行为A[n-1],列为A[n],题目中给了Ai与Ai+1是可以相乘的。用数组M表示矩阵链相乘的最优解,例如M[i][j]表示从矩阵i到矩阵j的矩阵链相乘的最小计算次数。

利用动态规划求解,对于每一个矩阵链,其中的矩阵不断相乘,最后剩下两个矩阵相乘得到最后的矩阵,记它们在k处断开。但是k的值不确定,需要计算,所以动态规划就得到了解决公式。

如果i=j,则M[i][j]=0。

如果i<j,则M[i][j]等于min{M[i][k]+M[k+1][j]+Ai-1 * Ak * Aj}。

解决代码:

#include<iostream>
#include<cstring>
#define maxn 1000+5
#define inf 0x3f3f3f3f
using namespace std;

//利用动态规划求解
//用数组A接受矩阵的行列,对于一个矩阵An,其行为A[n-1],列为A[n],题目中给了Ai与Ai+1是可以相乘的
//用数组M表示矩阵链相乘的最优解,例如M[i][j]表示从矩阵i到矩阵j的矩阵链相乘的最小计算次数
//分析矩阵链的相乘,乘到最后只剩下两个矩阵相乘然后得到最终的矩阵,那么可以假设该矩阵链最后的相乘是从k处断开的
//但是k的值不确定,需要计算,所以动态规划就得到了解决公式
//如果i=j,则M[i][j]=0,如果i<j,则M[i][j]等于min{M[i][k]+M[k+1][j]+pi-1*pk*pj}
//其中p为矩阵的行列

int M[maxn][maxn];//记录矩阵链相乘的最优解
int S[maxn][maxn];//记录矩阵链相乘的断开点
int A[maxn];//记录矩阵
int n;//矩阵个数

void MatrixMultiply(int A[], int n, int M[][maxn], int S[maxn][maxn]);
void PrintWay(int S[][maxn], int l, int r);

int main()
{
    int n;
    cin>>n;
    for(int i=0; i<=n; i++) cin>>A[i];
    //memset(S, -1, sizeof(S));
    for(int i=1; i<=n; ++i)
        for(int j=1; j<=n; ++j)
            S[i][j]=-1;
    MatrixMultiply(A, n, M, S);
    cout<<M[1][n]<<endl;
    //cout<<S[1][1]<<endl;
    PrintWay(S, 1, n);
    return 0;
}

void MatrixMultiply(int A[], int n, int M[][maxn], int S[maxn][maxn])
{
    //memset(M, inf, sizeof(M));
    for(int i=1; i<=n; ++i)
        for(int j=1; j<=n; ++j)
            M[i][j]=inf;

    for(int i=1; i<=n; i++) M[i][i]=0;

    for(int len=2; len<=n; ++len)//矩阵链的长度
    {
        for(int i=1; i<=n-len+1; ++i)
        {
            int j=i+len-1;//最后一个矩阵的列

            /*不给数组M设置初值的写法,初始认为k为i
            M[i][j]=M[i][i]+M[i+1][j]+A[i-1]*A[i]*A[j];//可以不写M[i][j],因为该元素值为0
            S[i][j]=i;
            for(int k=i+1; k<j; ++k)
            {
                if(M[i][k]+M[k+1][j]+A[i-1]*A[k]*A[j]<M[i][j])
                {
                    M[i][j]=M[i][k]+M[k+1][j]+A[i-1]*A[k]*A[j];
                    S[i][j]=k;
                }
            }
            */
            for(int k=i; k<j; ++k)
            {
                if(M[i][k]+M[k+1][j]+A[i-1]*A[k]*A[j]<M[i][j])
                {
                    M[i][j]=M[i][k]+M[k+1][j]+A[i-1]*A[k]*A[j];
                    S[i][j]=k;
                }
            }
        }
    }
}

void PrintWay(int S[][maxn], int l, int r)
{
    if(S[l][r]!=-1)
    {
        PrintWay(S, l, S[l][r]);
        PrintWay(S, S[l][r]+1, r);
        cout<<"Multiply A"<<l<<","<<S[l][r]<<" and A"<<S[l][r]+1<<","<<r<<endl;
    }
    else
    {
        return ;
    }
}

第二题

0-1背包问题:

背包问题即将n个不同的物品放入一个容量为c的背包中,要求这些物品组成的价值最大。

利用动态规划进行求解,定义一个dp[i][j],其含义为当前背包容量为j的条件下前i个物品的最优组合,然后分析递推公式。

首先对于我们当前选中的一个物品,它只有放入背包和不放入背包两种可能。

1、j<w[i],则该物品重量大于背包容量,不能放入背包,所以dp[i][j]=dp[i-1][j],也即
此时(dp[i][j])的最大总价值和只从前i-1个物品选择装入背包的最大总价值是一样的。

2、j>w[i],则该物品重量小于背包容量,可以放入背包,但是放入背包之后其占用了背包的容量,却并不一定可以使背包的总价值达到最大,所以要进行判定,判定公式为dp[i][j]=max(dp[i-1][j], dp[i-1][j-w[i]]+v[i])。对两种情况取max,前者表示该物品不放入背包,背包总价值等于只从前i-1个物品中进行选择放入背包的总价值,后者表示该物品装入背包,背包总价值等于在背包容量为j-w[i]时只从前i-1个物品中进行选择的总价值加上该物品的价值(背包容量为j-w[i]因为该物品的重量也占用了背包容量)。

代码:

#include<iostream>
#include<cstring>
#define maxn 1000+5
using namespace std;
//0-1背包问题
//利用动态规划进行求解
//定义一个dp[i][j],其含义为当前背包重量为j的条件下前i个物品的最优组合,然后寻找递推公式
//对于当前选中的一个商品,只有两种可能
//1、装不下,j<w[i],那么dp[i][j]=dp[i-1][j],也即此时背包总价值和只从前i-1个物品中选择的总价值是一样的
//2、装得下,j>=w[i],但是可以选择不装,因为装了也不一定使背包总价值变大,那么dp[i][j]=max{dp[i-1][j], dp[i-1][j-w[i]]+v[i]}


int n;//物品数
int w[maxn];//物品重量
int v[maxn];//物品价值
int s[maxn];//物品是否装入背包 1->装入
int c;//背包容量
int maxWeight;
int dp[maxn][maxn];

void getMax(int n, int c, int w[], int v[], int dp[][maxn]);
void findWay(int i, int j, int dp[][maxn], int s[]);
void printWay(int s[], int n);

int main()
{
    maxWeight=0;
    cin>>n; cin>>c;
    for(int i=1; i<=n; ++i) cin>>w[i];
    for(int i=1; i<=n; ++i) cin>>v[i];
    for(int i=1; i<=n; ++i)
        for(int j=1; j<=c; ++j)
            dp[i][j]=0;
    memset(s, 0, sizeof(s));
    getMax(n, c, w, v, dp);
    findWay(n, c, dp, s);
    printWay(s, n);
    return 0;
}

void getMax(int n, int c, int w[], int v[], int dp[][maxn])
{
     for(int i=1; i<=n; ++i)
     {
        for(int j=1; j<=c; ++j)
        {
            if(j<w[i])
            {
                dp[i][j]=dp[i-1][j];
            }
            else
            {
                dp[i][j]=max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);
            }
        }
     }
}

//思路:对于dp[i][j],如果它等于dp[i-1][j],说明没有选择i,如果满足递推公式的第二个情况,说明选择了i
void findWay(int i, int j, int dp[][maxn], int s[])
{
    if(i>=0)
    {
        if(dp[i][j]==dp[i-1][j])
        {
            s[i]=0;
            findWay(i-1, j, dp, s);
        }
        else if(j>=w[i] && dp[i][j]==dp[i-1][j-w[i]]+v[i])
        {
            s[i]=1;
            findWay(i-1, j-w[i], dp, s);
        }
    }
}

void printWay(int s[], int n)
{
    int ans=0;
    for(int i=1; i<=n; ++i)
    {
        if(s[i])
        {
            ans+=v[i];
        }
    }
    cout<<ans<<endl;
    for(int i=1; i<=n; ++i)
    {
        if(s[i])
        {
            cout<<"x"<<i<<" ";
        }
    }
}

findWay函数用来得到最优解选择装入了哪些物品,思路为:对于dp[i][j],如果它等于dp[i-1][j],说明没有选择i,如果满足递推公式的第二个情况,说明选择了i,然后递归求解。

posted @ 2022-09-22 23:31  HD0117  阅读(53)  评论(0编辑  收藏  举报