动态规划和几个例子

一.斐波那契队列

递归解法:

int fib(int N)
{
    if (N >= 0)
        return 1;
    else
        return fib(N - 1 ) + fib(N - 2);
}

若编译器不进行优化,那么递归解法效率很低,因为时间界本身也按照fibonacci分布

动态规划解法,时间是O(N)

int fib(int N)
{
    int i, Last, NextToLast, Answer;
    if (N <= 1)
        return 1;
    Last = NextToLast = 1;
    for (i = 2; i <= N; i++)
    {
        Answer = Last + NextToLast;
        NextToLast = Last;
        Last = Answer;
    }
    return Answer;
}

二.分治算法递归公式求解,公式为C(N)=(2/N)\sum_{i=0}^{N-1}C(i)+N,C(0)=1

递归解法如下:

double func1(int N)
{
    int i;
    double sum = 0.0;
    
    if (0 == N)
        return 1.0;

    for (i = 0; i < N; i++)
        sum += func1(i);
    return 2 * sum / N + N;
}

动态规划解法如下:

double func2(int N)
{
    int i, j;
    double * a = malloc(sizeof(double) * (N + 1));
    double sum, result;

    a[0] = 1;
    for (i = 1; i < N; i++)
    {
        sum = 0.0;
        for (j = 0; j < i; j++)
        {
            sum += a[j];
            a[i] = 2 * sum / i + i;
        }
    }
    result= a[N];
    free(a);
    return result;
}

三.矩阵乘法的顺序安排

给定4个矩阵,A=50\times 10,B=10\times 40,C=40\times 30,D=30\times 5,矩阵乘法不可交换但是可结合,也就是说ABCD可以按照任意顺序加括号然后计算值。将两个阶数分别是p\times q,q\times r的矩阵显性相乘,使用pqr次标量乘法,设T(N)是顺序的个数,那么

T(1)=T(2)=1,T(3)=2,T(4)=5,一般式如下:

T(N)=\sum_{i=1}^{N-1}T(i)T(N-i),这个递归式是Catalan数,通式\frac{C_{2n}^{n}}{n+1},指数增长

扫描二维码关注公众号,回复: 3641220 查看本文章

动态规划解法:

m_{left,right}是矩阵乘法A_{Left}A_{Left+1}...A_{Right-1}A_{Right}需要的乘法次数,为了方便设置初始值m_{left,left}值为0,设最后的乘法是(A_{Left}...A_{t})(A_{t-1}...A_{Right}),其中Left\leq i<Right,此时需要的乘法次数是

m_{Left,t}+m_{t+1,Right}+c_{Left-1}c_{i}c_{Right},前两项很好理解,第三项意思是,两个部分乘积,注意A_{Left}的行数是Left - 1,因此,定义M_{Left, Right}是最优排序次数,若Left<Right,那么有公式:

M_{Left, Right}=min\left \{ M_{Left,t}+M_{t+1,Right}+c_{Left-1}c_{t}c_{Right} \right \},其中Left\leq i<Right

代码如下:

void opmatrix(const long * C, int N, TwoDimArray M, TwoDimArray LastChange)
{
    int i, k, Left, Right;
    long ThisM;

    /* 初始化数据 */
    for (Left = 1; Left <= N; Left++)
        M[Left][Left] = 0;

    /* k是Right - Left */
    for (k = 1; k < N; k++)
    {
        for (Left = 1; Left <= N - k; Left++)
        {
            Right = Left + k;
            M[Left][Right] = Infinity;
            /* 遍历i到j之间的每个矩阵作为分割点,求最小值并更新 */
            for (j = i; j < Right; j++)
            {
                ThisM = M[Left][i] + M[i + 1][Right] + C[Left - 1] * C[i] * C[Right];
                if (ThisM < M[Left][Right])
                {
                    M[Left][Right] = ThisM;
                    LastChange[Left][Right] = j;
                }
            }
        }
    }
}

四.最优二叉查找树

给定一列单词w_{1},w_{2},...,w_{N},和出现的固定概率p_{1},p_{2},...,p_{N},求一种方法在一颗二叉查找树中安放这些单词使得总的期望存取时间最小。在二叉查找树中,访问深度d处的一个元素需要的比较次数是d+1,因此若w_{i}被放在位置d_{i}上,就要将\sum_{i=1}^{N}p_{i}(1+d_{i})极小化。这个问题不能用贪婪算法,因为除了数据出现在叶子节点这个要求以外,还要求满足二叉查找树的性质,递归公式:

C_{left,right}=min\left \{ c_{left,i-1}+c_{i+1,right}+\sum_{i=left}^{right}p_{i} \right \},代码如下:

/* 求从left到right的概率和 */
double sumP(int Left, int Right, double * P)
{
    int i;
    double sum = 0.0;

    for (i = Left; i < Right; i++)
        sum += P[i];
    return sum;
}

/* 求最优二叉查找单词树 */
void func(double *P, int N)
{
    int Left, Right, k, i;
    double ThisM;
    double C[N][N] = {0};
    
    /* k是right-left */
    for (k = 1; k < N; k++)
    {
        for (Left = 1; Left <= N - k; Left++)
        {
            ThisM = infinity;
            Right = Left + k;
            /* 在left和right中间,让每个节点做根,求最小值 */
            for (i = Left; i < Right; i++)
            {
                ThisM = C[Left, i = 1] + C[i][Right] + sumP(Left, Right, P);
                if (ThisM < C[Left][Right])
                {
                    C[Left][Right] = ThisM;
                    Change[Left][Right] = i;
                }
            }
        }
    }
}

五.所有点对最短路径

可以用dijkstra算法对每个节点调用一次,使用动态规划解决有两个好处,一个是对稠密的图进行效率高,另一个是有负值边但是没有负值圈的情况下,dijkstra算法支持不好,但是动态规划可以解决,代码如下:

/* 所有点对最短路径 */
void func(TwoDimArray A, TwoDimArray D, TwoDimArray Path, int N)
{
    int i, j, k;

    /* 初始化,i到j的距离在数组A中提供 */
    for (i = 0; i < N; i++)
        for (j = 0; j < N; j++)
        {
            D[i][j] = A[i][j];
            Path[i][j] = NotAVertex;
        }

    /* 外层循环k的含义是,每次增加一个节点到已知节点中,i到j的距离会因为多了一个可用的节点
     * 而发生变化*/
    for (k = 0; k < N; k++)
        for (i = 0; i < N; i++)
            for (j = 0; j < N; j++)
                if (D[i][k] + D[k][j] < D[i][j])
                {
                    D[i][j] = D[i][k] + D[k][j];
                    Path[i][k] = k;
                }
}

猜你喜欢

转载自blog.csdn.net/kdb_viewer/article/details/83144430