矩阵连乘问题——动态规划

定义

给定n个矩阵 {A1,A2,…,An} ,其中Ai与Ai+1是可乘的(i=1,2,…,n-1)。由于矩阵乘法满足结合律,所以它们的连乘积 A1A2…An有不同的计算次序。不同计算次序需要的乘法次数不同,求使乘法次数最少的计算次序。

输入

1.第一行:矩阵个数n
2.第二行: (n+1)个数字 p[n+1] ,其中p[i-1]和p[i]表示第 i 个矩阵的行和列数。

输出

最少乘法次数 和 对应计算次序。

#include 
#include 
void matrixChain(int n, int *p, int **m, int **s);
void Traceback(int i,int j,int **s);
int main(int argc, char const *argv[])
{
    int n;
    printf("请输入矩阵个数:");
    scanf("%d", &n);
    // n个矩阵连乘,第i个矩阵行列数分别为p[i-1]和p[i]
    int *p = (int *)malloc((n + 1) * sizeof(int));
    // m[i][j]表示计算A[i:j]所需最少乘次数
    int **m = (int **)malloc((n + 1) * sizeof(int *));
    // s[i][j]表示计算A[i:j]断开的位置
    int **s = (int **)malloc((n + 1) * sizeof(int *));
    printf("请输入表示行列的 n+1 个数字:");
    for (int i = 0; i <= n; i++)
    {
        scanf("%d", &p[i]);
        m[i] = (int *)malloc((n + 1) * sizeof(int));
        s[i] = (int *)malloc((n + 1) * sizeof(int));
    }
    matrixChain(n, p, m, s);
    printf("最少需要乘 %d 次\n", m[1][n]);
    Traceback(1, n, s);
    return 0;
}

动态规划

void matrixChain(int n, int *p, int **m, int **s)
{
    for (int i = 1; i <= n; i++)
        m[i][i] = 0; // 单个矩阵,不用乘
    for (int r = 2; r <= n; r++)
    { // 计算连续r个矩阵相乘
        for (int i = 1; i <= n - r + 1; i++)
        {
            int j = i + r - 1;
            //A[i]和A[i+1:j]
            int u = m[i + 1][j] + p[i - 1] * p[i] * p[j];
            s[i][j] = i;
            // printf("m[%d][%d] = %d\n", i, j, m[i][j]);
            for (int k = i + 1; k < j; k++)
            { // A[i:j]于第 k 个矩阵断开
                // 计算A[i:k]和A[k+1:j]最少乘法次数+二者相乘乘法次数
                int t = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
                if (t < u)
                { // 依次寻找乘法次数最少的断点
                    u = t;
                    s[i][j] = k;
                }
            }
            m[i][j] = u;
            printf("从 %d 到 %d,在 %d 断开,m[%d][%d] = %d\n", i, j, k, i, j, m[i][j]);
        }
    }
}

回溯输出次序

void Traceback(int i,int j,int **s)
{
    if (i == j)
    { // 单个矩阵,输出
        printf("A%d", i);
        return;
    }
    printf("(");
    Traceback(i, s[i][j], s);     // 输出断点前
    Traceback(s[i][j] + 1, j, s); // 输出断点后
    printf(")");
}

备忘录方法

该方法是动态规划的变形,都是记录查表。不同的是,备忘录方法是自顶而下的。当所有子问题都需要求解时,两种方法没太大区别。但由于备忘录递归调用,一般使用动态规划。备忘录方法适合需要剪枝的场合。

int LookupChain(int i, int j, int **m, int **s, int *p)
{
    if (m[i][j] > 0)    return m[i][j];
    if (i == j)    return 0;
    int u = LookupChain(i, i, m, s, p) + LookupChain(i + 1, j, m, s, p) + p[i - 1] * p[i] * p[j];
    // int u = 0 + LookupChain(i + 1, j, m, s, p) + p[i + 1] * p[i] * p[j];
    s[i][j] = i;
    for (int k = i + 1; k < j; k++)
    {
        int t = LookupChain(i, k, m, s, p) + LookupChain(k + 1, j, m, s, p) + p[i - 1] * p[k] * p[j];
        if (t < u)
        {
            u = t;
            s[i][j] = k;
        }
    }
    m[i][j] = u;
    return u;
}
int MemorizeMatrixChain(int n, int **m, int **s, int *p)
{
    for (int i = 1; i < n; i++)
        for (int j = 1; j <= n; j++)
            m[i][j] = 0;
    return LookupChain(1, n, m, s, p);
}

你可能感兴趣的:(数据结构与算法设计,矩阵,动态规划,算法)