数据结构学习笔记6-动态规划(钢条切割问题)

来源:互联网 发布:黑客编程入门3下载 编辑:程序博客网 时间:2024/05/17 04:43

下面记下在学习算法导论的动态规划过程中的笔记及代码:

动态规划含义:

动态规划一般也只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。

动态规划算法分以下4个步骤:

1.描述最优解的结构
2.递归定义最优解的值
3.按自底向上的方式计算最优解的值 //此3步构成动态规划解的基础。
4.由计算出的结果构造一个最优解。 //此步如果只要求计算最优解的值时,可省略。

动态规划应用1

钢条切割问题

问题:公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。

假定我们知道公司出售一段长度i英寸的钢条的价格为pi(i=1,2,…,单位为美元)。钢条的长度均为整英寸。图给出了一个价格表的样例。
这里写图片描述

切割钢条的问题是这样的:给定一段长度为n英寸的钢条和一个价格表Pi(i=1,2…n),求切割方案,使得销售收益Rn最大。

解决思路:

钢条切割问题还存在一种相似的但更为简单的递归求解方法:将钢条从左边切割下长度为i的一段,只对右边剩下的长度为n-i的一段继续进行切割,对左边的一段则不再进行切割。这样得到的公式为:这里写图片描述。这样原问题的最优解只包含一个相关子问题(右端剩余部分)的解,而不是两个。

朴素递归算法之所以效率很低,是因为它反复求解相同的子问题。因此,动态规划方法仔细安排求解顺序,对每个子问题只求解一次,并将结果保存下来。如果随后再次需要此子问题的解,只需查找保存的结果,而不必重新计算。因此,动态规划方法是付出额外的内存空间来节省计算空间。

动态规划两种解决方法:
动态规划的实现方法:

1.带备忘的自顶向下法:此方法仍按自然的递归形式编写过程,但过程会保存每个子问题的解(通常保存在一个数组或散列表中)。当需要一个子问题的解时,过程首先检查是否已经保存过此解。如果是,则直接返回保存的值,从而节省了计算时间;否则,按通常方式计算这个子问题。

2.自底向上法:这种方法一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解都依赖于“更小的”子问题的求解。因而我们可以将子问题按规模排序,按由小至大的顺序进行求解。当求解某个子问题时,它所依赖的那些更小的子问题都已经求解完毕,结果已经保存。每个子问题只需要求解一次,当我们求解它(也是第一次遇到它)时,它的所有前提子问题都已求解完成。

注意:两种方法得到的算法具有相同的渐进运行时间,仅有的差异是在某些特殊情况下,自顶向下方法并未真正递归地考察所有可能的子问题。由于没有频繁的递归函数调用的开销,自底向上方法的时间复杂度函数通常具有更小的系数。

带备忘的自顶向下法

先看伪代码:
这里写图片描述
这里写图片描述

根据伪代码写的C语言代码:

////  main.c//  钢条切割问题////#include <stdio.h>#include <stdlib.h>int MEMOIZED_CUT_ROD_AUX(int *p,int n,int *r);int MEMOIZED_CUT_ROD(int *p,int n)  //p数组是存放着价格表的数值,n代表要切割一条长度为n的钢条{    int *r = (int *)malloc(sizeof(int)*n);//新建一个长度为n的数组r,用来存储子问题的解    if (r == 0) {        return -1;//代表分配内存失败    }    for (int i=0; i<n; i++) {        *(r+i)=-1;  //初始化r,算法导论中写的是负无穷    }    return MEMOIZED_CUT_ROD_AUX(p, n, r);}int MEMOIZED_CUT_ROD_AUX(int *p,int n,int *r){    int temp=0;    int q=0;//q保存的是子问题求得的最大的收益    if (r[n-1] >= 0) {        return r[n-1];    }    if (n == 0) {        q=0;//求到最后只剩下最后的子问题:切割长度为0的钢条,所以这里设置q收益=0    } else {        q=-1;        for (int i=1; i<=n; i++) {            temp = *(p+i-1)+ MEMOIZED_CUT_ROD_AUX(p, n-i, r);            q = q>temp?q:temp;        }    }    r[n-1]=q;    return q;}int main(int argc, const char * argv[]) {    int p[10]={1,5,8,9,10,17,17,20,24,30};    int n=5;    printf("%d\n",MEMOIZED_CUT_ROD(p, n));    return 0;}

输出结果:
这里写图片描述

自底向上法

(只求出最优解,并未求出切割方案)(C语言代码:)

 ////  main.c//  钢条切割问题////#include <stdio.h>#include <stdlib.h>int BUTTOM_UP_CUT_ROD(int *p,int n){    int *r = (int *)malloc(sizeof(int) * (n+1));//是n+1不是n,因为要保存r[0]    r[0] = 0;    for (int j = 1; j<=n; j++) {//这里是小于等于而不是小于        int q=-1;        for (int i = 1; i<=j; i++) {//这里是小于等于而不是小于            q = q>(*(p+i-1) + *(r+j-i))?q:(*(p+i-1) + *(r+j-i));        }        r[j]=q;//        printf("the r[%d] is %d\n",j,r[j]);    }    return *(r+n);}int main(int argc, const char * argv[]) {    int p[10]={1,5,8,9,10,17,17,20,24,30};    int n=10;    printf("%d\n",BUTTOM_UP_CUT_ROD(p, n));    return 0;}

上述代码仅能输出最优解,但是还未保存最优切割方案:
下述代码能够输出切割方法:

#include <stdio.h>#include <stdlib.h>int *s;int BUTTOM_UP_CUT_ROD(int *p,int n){    int *r = (int *)malloc(sizeof(int) * (n+1));    s = (int *)malloc(sizeof(int) * n);    r[0] = 0;    for (int j = 1; j<=n; j++) {        int q=-1;        for (int i = 1; i<=j; i++) {            if (q<(*(p+i-1) + *(r+j-i))) {                q=*(p+i-1) + *(r+j-i);                *(s+j-1)=i;            }        }        r[j]=q;//        printf("the r[%d] is %d\n",j,r[j]);    }    return *(r+n);}void PRINT_CUT_ROD_SULUTION(int n){    while (n>0) {        printf("%d\n",*(s+n-1));        n=n-*(s+n-1);    }}int main(int argc, const char * argv[]) {    int p[10]={1,5,8,9,10,17,17,20,24,30};    int n=7;    printf("%d\n",BUTTOM_UP_CUT_ROD(p, n));    PRINT_CUT_ROD_SULUTION(n);    return 0;}
0 0
原创粉丝点击