动态规划的典型分析与代码实现----主java

动态规划

1.矩阵连乘

2.电路布线

3.资源分配

4.流水线问题

5.背包问题 ---- 变型

6.最长公共子序列

7.最大字段和

8.最小硬币找零数

4.8还会更新



动态规划代码:

1.  矩阵连乘:

2.  package matrix;

3.   

4.  public class matrix {

5. 

6.  //计算最小的次数

7.  public static int matrix_chain(int[]p, intn , int[][]m ,int[][]s)

8.  {

9.      //说明:p是矩阵的行数,n是测试矩阵的个数,m是存储最小的次数,s是记录k的位置

10.     int i,j,k,r;

11.     //单个矩阵的运算最小次数是0,需要赋值,因为之后计算m1】【n】时候需要

12.     for(i = 0;i<n;i++)

13.     {

14.         m[i][i] = 0;

15.     }

16.     //r为矩阵连乘的个数,从2个开始。到n,也就是计算到n时候为最终最小结果

17.     for( r = 2 ; r <= n ;r++)

18.     {

19.         //i代表第一个矩阵的角标,比如r2时候,m0】【1】,m1】【2】,0,1.。。。n-r

20.         for(i = 0 ; i <=n - r ; i++ )

21.         {

22.             //j为第二个矩阵的角标,12等到n,但是不用写一个循环,可以用ir表示

23.             j = i + r -1;

24.             m[i][j] = 999999;

25.             //一会计算的最小的结果要放在mi】【j】会覆盖,只是因为不初始化没法比较,因为之后是循环,没法比较赋值。但是因为

26.             //规定了是n=6,因此最大不会超过999999,所以随意设置一个最大的数。这样省事。但是如果数据过大会出错

27.             for(k = i ; k <= j-1 ;k++)

28.             {

29.                

30.                 //k表示从第一个位置开始到最后一个找断点

31.                 inttemp = m[i][k] +m[k + 1][j] +p[i] *p[k+1]*p[j+1];

32.                         if(temp <m[i][j])

33.                         {

34.                             m[i][j] =temp;

35.                             s[i][j] =k;

36.                         }

37.             }

38.         }

39.     }

40.     /*检测程序

41.      * for(i= 0 ; i

42.     {

43.         for(j= 0 ; j< n;j++)

44.    

45.     {

46.         System.out.print(s[i][j]+" ");

47.     }

48.         System.out.println();

49.     }

50.     System.out.println("123");

51.     System.out.println(s[5][5]+" ");

52.     System.out.println("123");

53.     for(i= 0 ; i

54.     {

55.         for(j= 0 ; j< n;j++)

56.    

57.     {

58.         System.out.print(m[i][j]+" ");

59.     }

60.         System.out.println();

61.     }

62.      */

63.

64.     returnm[0][n-1];

65. }

66. /*

67.  * 时间复杂度有三个forO(N^3),空间复杂度是O(N^2)

68.  */

69. //打印加括号的表达式

70. public static void print_chain(inti , intj , char[]a , int[][]s)

71. {

72.     //递归写出最小的乘数的表达式输出

73.     if( i == j)

74.     {

75.         System.out.print(a[i]);

76.     }

77.     else

78.     {

79.         System.out.print("(");

80.         print_chain(i,s[i][j],a,s);

81.         print_chain(s[i][j] + 1 ,j ,a,s);

82.         System.out.print(")");

83.     }

84. }

85. public static void main(String[] args)

86. {

87.     int[][]m = newint[6][6] ;

88.     int[][]s = newint[6][6];

89.     char[]a = {'A','B','C','D','E','F'};

90.    

91.     int n = 6;          //矩阵的个数

92.     int min_times;   //最小次数。

93.     int[]p = {30,35,15,5,10,20,25};

94.     /*

95.      * 初始化了矩阵的行数和列数。可以input.next();

96.      * P[0] 表示第一个矩阵的行, p[1]第二个矩阵的行,(第二个矩阵的行就是第一个矩阵的列。。

97.      * p[5] 第六个矩阵的行, P[6]是第六个矩阵的列

98.      */

99.     min_times = matrix_chain(p,n,m,s);

100.             System.out.println("minest times: " +min_times);

101.             print_chain(0,n-1,a,s);

102.             

103.           

104.        }

105.    }

 

 

2.电路布线

在一块电路板的上、下两端分别有n个接线柱。根据电路设计,要求用导线将上端接线柱与下端接线柱相连

 

如上图所示,每个节点有且只连有一条线。
在制作电路板时,要求将这n条连线分布到若干绝缘层上。在同一层上的连线不相交。
这个问题是要确定将哪些连线安排在第一层上,使得该层上有尽可能多的连线(不相交)。


为了解决这个问题,我们可以将问题简化为这样:

 
设定上接线柱为1,2,3,....,n。
下接线柱是【 1,2,3,....,n】的一个排列 设为f(i),i的值就是上接线柱的数字,
如上图就是这样的模型:
i    1    2    3    4   5   6   7    8     9    10
f(i) 8,7,4,2,5,1,9,3,10,6
它们一一对应。 
我们还必须得到这样一个结论:
对于上接线柱i1 f(i1) 因为对于i1f(i2)则它们一定相交(画出图像来看看)


因此,我可以将这个电路布线问题,转化为这样的问题:


已知有一个{1,2,3,...,n}的排列,将其拆分为k个子排列,并且每个子排列都是严格递增的,求子排列的最大元素个数。


例如:排列 8,7,4,2,5,1,9,3,10,6可以拆分成五个子排列{4,5,9,10}{8}{7}{2,3,6}{1}。
它最大的元素个数是4。
排列 1,2,3,4,5,6,7,8,9,10它是递增的,所以无需拆分,最大元素个数是10。
算法实现:
对于排列8,7,4,2,5,1,9,3,10,6。
步骤:

先将记录第一个值8,依次向后扫描至大于8的元素。如果发现8的元素,例如9,则我可以产生一个子排列{8,9}(实际上只需要更改元素数量和最大值即可),继续扫描。
产生子排列{8,9,10}。扫描完毕后姑且认为子排列最大元素是3个。

第二次以7向后扫描,只要扫描到比它大的,就将产生一个子排列,继续向后扫描,扫描结束后,得到排列是{7,9,10}。个数是3,与之前的比较。

第三次以4开始扫描,。。。依次下去至扫描结束。 
使用递归算法代码简单,算法复杂度为O(N*N)

 

#include 
using namespace std;
#define N 10
 
 
int sub[N] = { 2, 7, 3, 4, 5, 6, 9, 8, 10, 1 };
 
int function(int MaxElem,int e=0,int counts=1)
{
    if (e==N-1)
    {
        //递归的出口
        if (sub[e] > MaxElem) counts++;
        return counts;
    }
    else
    {
        if (sub[e]>MaxElem)
        {
            MaxElem = sub[e];
            counts++;
 
        }
        return function(MaxElem,e+1,counts);
    }
 
}
 
int main()
{    
    int max = 0,t;
    for (int i = 0; i < N; i++)
    {
        if ((t = function(sub[i]))>max) max = t;
    }
    cout << max << endl;
    
 
    return 0;
}

Java实现:

但是该算法存在一个问题,就是答案如果有两种情况的时候怎么办,还是说不应该出现两种情况?

用动态规划根据等号的不同确实有不同的输出。但是下面的算法没有应用动态规划而是应用的数学的方面知识处理。

 

importjava.util.Scanner;

 

public class Circuit {

    public static void main(String[] args)

    {

        Scanner input =new Scanner(System.in);

        int[]data = newint[10];

        int i , j , k;//记录最长的子序列的个数

        int[][]result = newint[10][11];

        int count = 1;

        int temp;

   

        for(i = 0 ;i < 10 ; i++)

        {

            data[i]  =input.nextInt();

        }

        for(j = 0 ;j < 10 ; j++)

        {

            i = 1 ;

            result[j][i] =data[j];

            for(k = j+1 ; k < 10 ;k++ )

            {

               

                if(result[j][i] <data[k])

                {

                   i++;

                   result[j][i]=data[k];

                }

                  

            }

            result[j][0] =i ;

        }

       

        for(i = 0 ;i < 10 ; i++)

        {

            for(j = 0 ;j < 11;j++)

            {

                System.out.print(result[i][j] + " ");

            }

            /*

             * k =result[0][0];

            if(result[i][0] > k)

                k = result[i][0];

            System.out.print(k);

           

             */

            System.out.println();

        }

 

    }

}

输出结果:

3.资源分配问题及其空间优化方案

问题描述:资源分配问题:某厂根据计划安排,拟将n台相同的设备分配给m个车间,各车间获得这种设备后,可以为国家提供盈利Ci j(i台设备提供给j号车间将得到的利润,1≤in1≤jm)。问如何分配,才使国家得到最大的盈利?

一、算法思想

1、 动态规划的最优性

   算法的最优策略体现在每个子策略都是最优的。由此可以将m个车间划分为前m-1个和第m个,每次分配第m个的时候都是建立在前m-1的最优策略的基础上,再进行最优分配。这样在迭代的过程中就能保证每一次都是最优的,从而到最后的整体最优。

 

2、 资源分配递归式

确定最大利润时必须先判断前m-1个车间分配的情况,若前m-1分配了k个设备,则第m个只能分配j-k个设备。而如何分配才能时此次分配获得的利润最大,则要比较不同的分配方案所获得的利润:让k从0个设备增长到j,计算前m-1个分配k的价值+第m个分配j-k的价值,取生产价值最大的方案。并记录分配方案即k的值。

如共有3个车间2个设备,就有三种分配方案,分别是:前2个车间分配2个,第三个分配0个;前2个车间分配1个,第三个分配1个和前2个车间分配0个,第三个分配2个。在这些利润中挑选价值最高的方案,所以递归式如下:

       Value[i][j]=max(Value[i][j],Value[i-1][k]+profit[i][j-k])

其中1≤i≤m   1≤j≤n   0≤k≤j(m为车间数,n为设备数)  profi数组保存的是i号车间分配j台设备所能产生的利润。Value[i][j]的初始化均为0。

而这个也有限制条件,就是当车间数为0或是设备数为0的时候产生利润为0.即:

         Value[i][j]=0 (i=0 或 j=0)

 

3、建表&填表

  由于若使用递归方法则需要不断地去调用函数,这样比较浪费时间,所以用动态规划二维数组的方式来实现。

  随机生成的数据中,需注意价值表和最大利润表当i=0和j=0时,最大利润和价值都为0;同一个车间随着设备数的增加,能生产的价值也相应递增,所以在初始化的时候要注意这两点。

 

  可以看出每个车间分配不同台设备的数量产生的价值需要放在一个二维数组中,二维数组中的值表示第i号车间分配j台设备时产生的利润;而我们计算i个车间j台设备产生的最大利润又需要放在另一个二维数组中;最后产生最大利润的分配方案也要放在一个二维数组中,表示前i个车间分配j台机器时分配的设备数量。

  利用递归式进行填表即可,最后将生产利润最大的分配方案(j-k)填入路径数组中。

  伪代码如下:

For i 1 to m//m为车间数

  Forj  1 to n //n为设备数

    Max=0;

    Fork 0 to j

      Ifmax递归式

       Max=Value[i-1][k]+profit[i][j-k];

       Value[i][j]=max;//记下最大利润

       Road[i][j]=j-k;//记下路径

  由此可以看出一共需要三个二维数组表,这个占用内存很大,所以需要算法进行优化。

 

4、对随机生成利润表进行优化:

  将其变成一维数组的方式就是边生成边利用。从递归式可以看出,每一次计算最大利润只要利用到利润表中同一行的利润数据,所以我们可以生成一行的利润后直接利用。再次生成时覆盖原有的数据就可。这样就将一个利润二维数组优化为一位数组。

       伪代码如下:

     Profit[0]=0

      For I ito m

    profit[i]=profit[i-1]+rand()+1;//保证每一行递增

        //然后把profit数组传入函数中计算。

 

5、对最大利润生成表进行优化:

  从递归式也看得出,每一次产生最大利润所需要的数据只有上一行的利润。所以我们可以只保存最大利润在一维数组中,然后每次从后往前覆盖。因为计算第i行第j个的时候需要比较第i-1行的前j个,所以只能从后往前覆盖,以免造成数据丢失。

伪代码如下:

For i 1 to m

  For jn to 1

  Max=0

    Fork j to 0

   Ifmax

      Max=value[k]+profit[j-k]

      Value[j]=max

6、蛮力法:

将每个车间的分配情况用一个序列表示,假设有4个车间,5个设备,则用序列(0,0,3,2)表示设备的分配情况。如第三个数字为3表示第三个车间分配设配3个。

计算所有可能的方法则是利用排列组合的方法计算。假设有3个车间,2个设备。则每个车间有3种可能——0,1,2   一共有3个车间,所以共有33种可能。

  将每种可能转换成相应的序列,如20可被3取余,得到序列(0,2,0,2),但序列总额大于设备数,故此情况需要被舍弃。如6可被3取余,得到序列(0,0,2,0),则可计算下一步——计算此分配方案的利润。

       伪代码如下:

     Num=(n+1m //一共有这么多种可能

     Max=0

     For I 0 to num

       Sum=0

       If(Conversion(I,c))//conversion函数用于转换成序列,返回true or faluse

         For j 0 to m

         Sum=sum+value [j+1][c[j]]//统计利润

          If sum >max

           Max=sum;

           Flag=i//记录最大利润的情况

 

Convers(i,n)//主要是将i转换成(n+1)进制的序列

      while(i!=0&& m>=0)

       {

             m=m-1;

             inty=i%(n+1);

             c[m]=y;//用数组记录取余结果,表示第m个车间分配y台设备

             i=i/(n+1);

       }

      Forj 0 to n

             sum=sum+c[j];

      if(sum==n)//最后判断序列代表的分配的设备数之和是否等于我们的总设备数

             returntrue;

 

代码如下:

1.  #include   

2. #include   

3.  #include   

4. using namespace std;  

5.  void primary(int n,int m);  

6. void violent(int n ,int m);  

7.  bool conversion(int i,int c[],int n,int m);  

8. void roombetter(int n,int i);  

9.    

10.int value[12601][12601];  

11. int distribute[12601][12601];//记录最大利润  

12.int road[12601][12601];  

13. int result[51001];  

14.int makevalue[51001];  

15.   

16.int total=1;  

17. int main()  

18.{  

19.     cout<<"请输入选择:1、未优化前  2、蛮力法验证  3、空间优化后: ";  

20.    int t;  

21.     cin>>t;  

22.    int n,m;  

23.     cout<<"请输入车间数m和设备数n  ";  

24.    cin>>m>>n;  

25.     if(t==1 || t==2)  

26.    {  

27.     int i,j,k;  

28.    for(i=0;i<=m;i++)  

29.         for(j=0;j<=n;j++)  

30.        {  

31.             distribute[i][j]=0;  

32.            road[i][j]=0;  

33.         }  

34.    for(i=0;i<=n;i++)  

35.         value[0][i]=0;  

36.    for(i=1;i<=m;i++)  

37.     {  

38.        value[i][0]=0;  

39.         for(j=1;j<=n;j++)  

40.        {  

41.             value[i][j]=value[i][j-1]+rand()%100+1;//保证同一个车间,设备数增加的时候利润增加  

42.        //  cout<  

43.         }  

44.    //  cout<  

45.     }  

46.    if(t==1)  

47.         primary(n,m);//动态规划算法  

48.    if(t==2)  

49.         violent(n,m);//蛮力法  

50.    }  

51.     if(t==3)//空间优化算法  

52.    {  

53.         int i,j,k;  

54.        for(i=0;i<=n;i++)  

55.                 result[i]=0;  

56.        clock_t start, end;  

57.         start = clock();  

58.        for(i=1;i<=m;i++)  

59.         {  

60.            makevalue[0]=0;  

61.             for(j=1;j<=n;j++)  

62.            {   makevalue[j]=makevalue[j-1]+rand()%100+1;  

63.         //      cout<  

64.            }  

65.         //  cout<  

66.            roombetter(n,i);  

67.         }  

68.        end = clock();  

69.         k=n;  

70.        for(i=m;i>=1;i--)  

71.         {    

72.            for(j=total-1;j>=1;j--)  

73.                 if(p[j].chejian==i && p[j].shebei==k)  

74.                {  

75.                     cout<<""<"号车间分配了"<"台设备。"<

76.                    k=k-p[j].num;  

77.                     break;  

78.                }  

79.             if(j==0)  

80.            {  

81.                 cout<<""<"号车间分配了0台设备。"<

82.            }  

83.          }    

84.        cout<<"最大价值为:"<

85.     //  cout<  

86.        cout<<"运行时间为:"<<(double)(end - start) / (CLOCKS_PER_SEC)<<"s"<

87.     }  

88.     return 0;  

89. }  

90.   

91. //常规算法:动态规划

92. void primary(int n,int m)  

93. {  

94.     clock_t start, end;  

95.     start = clock();  

96.     int i,j,k;  

97.     for(i=1;i<=m;i++)  

98.         for(j=1;j<=n;j++)  

99.             for(k=0;k<=j;k++)  

100.               {  

101.                   if(distribute[i][j]//递归式,选出生产利润最大的分配方法  

102.                   {  

103.                       distribute[i][j]=distribute[i-1][k]+value[i][j-k];  

104.                       road[i][j]=j-k;  

105.    //记录给i个车间分配j个设备的过程中,给第i个车间分配的设备数。

106.                   }  

107.               }  

108.       end = clock();  

109.       for(i=0;i<=n;i++)  

110.           cout<" ";  

111.       cout<

112.        for(i=1;i<=m;i++)  

113.        {   cout<" ";  

114.            for(j=1;j<=n;j++)  

115.                cout<" ";  

116.            cout<

117.       }  

118.       k=n;  

119.        for(i=m;i>=1;i--){    

120.           cout<<""<"号车间分配了"<"台设备。"<

121.           k=k-road[i][k];  

122.    //输出的时候需要从最后的roadn

123.    m】中减,往上算

124.      }    

125.        cout<<"最大利润是:"<

126.        cout<<"运行时间为:"<<(double)(end - start) / (CLOCKS_PER_SEC)<<"s"<

127.         

128.   }  

129.    

130.    

131.    //穷举算法

132.   void violent(int n,int m)  

133.   {  

134.       int i,j,profit,max=0,flag;  

135.       int num=pow(n+1,m);  

136.       int c[10];  

137.       for(i=0;i

138.       {  

139.           profit=0;  

140.           if(conversion(i,c,n,m))//convers转化为序列  

141.           {  

142.               for(j=0;j

143.                   profit=profit+value[j+1][c[j]];//计算此种情况分配的利润  

144.               if(profit>max)  

145.               {  

146.                   max=profit;//记录最大利润的分配方案  

147.                   flag=i;  

148.               }  

149.           /*  for(j=0;j 

150.                   cout< 

151.               cout<  

152.           }  

153.       }  

154.       conversion(flag,c,n,m);  

155.       for(i=0;i

156.           cout<<""<"号车间分配了"<"台设备。"<

157.       cout<<"最大利润是:"<

158.   }  

159.     

160.   bool conversion(int i,int c[],int n,int m)  

161.   {  

162.       int j,sum=0;  

163.       for(j=0;j

164.           c[j]=0;  

165.       int number=m;//m为车间数  

166.     

167.       while(i!=0 && m>=0)  

168.       {  

169.           m=m-1;  

170.           int y=i%(n+1);//y的范围0~n  

171.           c[m]=y;  

172.           i=i/(n+1);  

173.       }  

174.       for(j=0;j

175.           sum=sum+c[j];  

176.       if(sum==n)  

177.           return true;  

178.       else  

179.           return false;  

180.   }  

181.     

182.    //优化

183.   void roombetter(int n,int i)  

184.   {  

185.       int j,k,max;  

186.       for(j=n;j>=1;j-- )  

187.       {  

188.           max=0;  

189.           for(k=j;k>=0;k--)  

190.           {  

191.               if(max

192.               {  

193.                   max=result[k]+makevalue[j-k];  

194.                   result[j]=max;  

195.                   road[i][j]=j-k;  

196.               }  

197.           }  

198.     

199.       }  

200.     

201.   }  

Java动态规划的实现:

随机实现data的数据,然后输出的结果有data【】【】和road【】【】。以及最后的总利润。因为有3个for,时间复杂性为O(N^3),同时需要开3个int【】【】

import java.util.Scanner;

 

public class ResourceAllocation {

    public static void main(String[] args)

    {

        Scanner input =new Scanner(System.in);

        System.out.println("input the n and m");

        int n = input.nextInt();   //车间的个数

        int m = input.nextInt();   //设备的个数

        //随机设置车间与设备的函数

        int[][]data = newint[n + 1][m + 1];

        int i , j ;

        //data数组中0行表示没有车间,0列表示没有设备,因此都是0

        for(i = 0 ;i <= n;i++)

        {

            data[i][0] = 0;

        }

        for(j = 0 ;j <= m;j++)

        {

            data[0][j] = 0;

        }

        for(i = 1 ;i <= n ;i++)

        {

            for(j = 1 ;j <= m;j++)

            {

                //随机产生的data中数字是i车间,j设备的value

                data[i][j] = (int) (data[i][j-1] + Math.random()*10);

            }

        }

       

        /*

         *   //打印原始数据的矩阵

        System.out.print("  ");

        for(j = 0 ; j <= m;j++)

            System.out.print(j + " ");

        System.out.println();

        for(i = 0 ; i <= n ;i++)

        {

            System.out.print(i+" ");

            for(j = 0 ; j <= m;j++)

            {

               

                System.out.print(data[i][j]+"");

            }

            System.out.println();

        }

       

         */

   

         

        int[][]value = newint[n+1][m+1]; //记录利润

        int[][]road = newint[n+1][m+1];  //记录最佳分配个数

         ResourceAllocation(n,m,value,road,data);

    }

    public static void ResourceAllocation(intn , intm , int[][]value , int[][]road,int[][]data)

    {

        int i ,j,k;

        for( i = 1; i <= n ;i++)

        {

            for(j = 1; j<=m;j++)

            {

                for(k = 0 ;k<=j ;k++)

                {

                   if(value[i][j] <value[i - 1][k]+data[i][j-k])

                   {

                       value[i][j] =value[i - 1][k]+data[i][j-k];

                       road[i][j] =j -k;

                   }

                }

               

            }

        }

        //System.out.println("123");

        for( i = 1 ;i<=n ;i++)

        {

            for(j = 1 ;j <= m ;j++)

                System.out.print(road[i][j] +" ");

            System.out.println();

           

        }

        k = m;

        for(i =n;i>=1;i--)

        {

            System.out.println(""+i+"个车间分配了"+road[i][k]+"台设备");

            k = k - road[i][k];

        }

        System.out.println("总利润是"+value[n][m]);

    }

}

4.流水线问题

5.背包问题

一、问题描述:有n 个物品,它们有各自的重量和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?

二、总体思路:根据动态规划解题步骤(问题抽象化、建立模型、寻找约束条件、判断是否满足最优性原理、找大问题与小问题的递推关系式、填表、寻找解组成)找出01背包问题的最优解以及解组成,然后编写代码实现;

三、动态规划的原理及过程:

  eg:number=4,capacity=8

i

1

2

3

4

w(体积)

2

3

4

5

v(价值)

3

4

5

6

 

1、原理

  动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。

2、过程

  a) 把背包问题抽象化(X1,X2…,Xn,其中 Xi 取0或1,表示第 i 个物品选或不选),Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积(重量);

  b) 建立模型,即求max(V1X1+V2X2+…+VnXn);

  c) 约束条件,W1X1+W2X2+…+WnXn

  d) 定义V(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值;

  e) 最优性原理是动态规划的基础,最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。判断该问题是否满足最优性原理,采用反证法证明:

    假设(X1,X2,…,Xn)是01背包问题的最优解,则有(X2,X3,…,Xn)是其子问题的最优解,

    假设(Y2,Y3…,Yn)是上述问题的子问题最优解,则理应有(V2Y2+V3Y3+…+VnYn)+V1X>(V2X2+V3X3+…+VnXn)+V1X1

    而(V2X2+V3X3+…+VnXn)+V1X1=(V1X1+V2X2+…+VnXn),则有(V2Y2+V3Y3+…+VnYn)+V1X> (V1X1+V2X2+…+VnXn);

    该式子说明(X1,Y2,Y3…,Yn)才是该01背包问题的最优解,这与最开始的假设(X1,X2,…,Xn)是01背包问题的最优解相矛盾,故01背包问题满足最优性原理;

  f) 寻找递推关系式,面对当前商品有两种可能性:

    第一,包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);

    第二,还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) }

       其中V(i-1,j)表示不装,V(i-1,j-w(i))+v(i) 表示装了第i个商品,背包容量减少w(i)但价值增加了v(i);

    由此可以得出递推关系式:

    1) j

    2) j>=w(i)     V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) }

  g) 填表,首先初始化边界条件,V(0,j)=V(i,0)=0;

 

  h) 然后一行一行的填表,

    1) 如,i=1,j=1,w(1)=2,v(1)=3,有j

    2) 又如i=1,j=2,w(1)=2,v(1)=3,有j=w(1),故V(1,2)=max{ V(1-1,2),V(1-1,2-w(1))+v(1) }=max{0,0+3}=3;

    3) 如此下去,填到最后一个,i=4,j=8,w(4)=5,v(4)=6,有j>w(4),故V(4,8)=max{ V(4-1,8),V(4-1,8-w(4))+v(4) }=max{9,4+6}=10;所以填完表如下图:

 

 1 void FindMax()//动态规划

 2 {

 3     int i,j;

 4     //填表

 5     for(i=1;i<=number;i++)

 6     {

 7         for(j=1;j<=capacity;j++)

 8         {

 9             if(j//包装不进

10             {

11                 V[i][j]=V[i-1][j];

12             }

13             else//能装

14             {

15                 if(V[i-1][j]>V[i-1][j-w[i]]+v[i])//不装价值大

16                 {

17                     V[i][j]=V[i-1][j];

18                 }

19                 else//前i-1个物品的最优解与第i个物品的价值之和更大

20                 {

21                     V[i][j]=V[i-1][j-w[i]]+v[i];

22                 }

23             }

24         }

25     }

26 }

  i) 表格填完,最优解即是V(number,capacity)=V(4,8)=10,但还不知道解由哪些商品组成,故要根据最优解回溯找出解的组成,根据填表的原理可以有如下的寻解方式:

    1) V(i,j)=V(i-1,j)时,说明没有选择第i 个商品,则回到V(i-1,j);

    2) V(i,j)=V(i-1,j-w(i))+v(i)实时,说明装了第i个商品,该商品是最优解组成的一部分,随后我们得回到装该商品之前,即回到V(i-1,j-w(i));

    3) 一直遍历到i=0结束为止,所有解的组成都会找到。

  j) 如上例子,

    1) 最优解为V(4,8)=10,而V(4,8)!=V(3,8)却有V(4,8)=V(3,8-w(4))+v(4)=V(3,3)+6=4+6=10,所以第4件商品被选中,并且回到V(3,8-w(4))=V(3,3);

    2) 有V(3,3)=V(2,3)=4,所以第3件商品没被选择,回到V(2,3);

    3) 而V(2,3)!=V(1,3)却有V(2,3)=V(1,3-w(2))+v(2)=V(1,0)+4=0+4=4,所以第2件商品被选中,并且回到V(1,3-w(2))=V(1,0);

    4) 有V(1,0)=V(0,0)=0,所以第1件商品没被选择;

 

  k) 到此,01背包问题已经解决,利用动态规划解决此问题的效率即是填写此张表的效率,所以动态规划的时间效率为O(number*capacity)=O(n*c),由于用到二维数组存储子问题的解,所以动态规划的空间效率为O(n*c);

 1 void FindWhat(int i,int j)//寻找解的组成方式

 2 {

 3     if(i>=0)

 4     {

 5         if(V[i][j]==V[i-1][j])//相等说明没装

 6         {

 7            item[i]=0;//全局变量,标记未被选中

 8            FindWhat(i-1,j);

 9         }

10         else if( j-w[i]>=0&& V[i][j]==V[i-1][j-w[i]]+v[i] )

11         {

12             item[i]=1;//标记已被选中

13             FindWhat(i-1,j-w[i]);//回到装包之前的位置

14         }

15     }

16 }

3、空间优化

  l) 空间优化,每一次V(i)(j)改变的值只与V(i-1)(x) {x:1...j}有关,V(i-1)(x)是前一次i循环保存下来的值;

  因此,可以将V缩减成一维数组,从而达到优化空间的目的,状态转移方程转换为 B(j)=max{B(j), B(j-w(i))+v(i)}

  并且,状态转移方程,每一次推导V(i)(j)是通过V(i-1)(j-w(i))来推导的,所以一维数组中j的扫描顺序应该从大到小(capacity到0),否者前一次循环保存下来的值将会被修改,从而造成错误。

 

 

注意第二个for循环是capacity到0.但是不优化的代码是从1到capacity。

因为I = 3 这行需要用i=2这行,如果从1到capacity既从左到右,结果就是会覆盖i= 2 的数据,对i= 3 后面部分的计算有影响。但是从后往前算不会影响。

  m) 同样以上述例子中i=3时来说明,有:

    1) i=3,j=8,w(3)=4,v(3)=5,有j>w(3),则B(8)=max{B(8),B(8-w(3))+v(3)}=max{B(8),B(4)+5}=max{7,4+5}=9;

    2) j- -即j=7,有j>w(3),则B(7)=max{B(7),B(7-w(3))+v(3)}=max{B(7),B(3)+5}=max{7,4+5}=9;

    3) j- -即j=6,有j>w(3),则B(6)=max{B(6),B(6-w(3))+v(3)}=max{B(6),B(2)+5}=max{7,3+5}=8;

    4) j- -即j=5,有j>w(3),则B(5)=max{B(5),B(5-w(3))+v(3)}=max{B(5),B(1)+5}=max{7,0+5}=7;

    5) j- -即j=4,有j=w(3),则B(4)=max{B(4),B(4-w(3))+v(3)}=max{B(4),B(0)+5}=max{4,0+5}=5;

    6) j- -即j=3,有j

 

  如果j不逆序而采用正序j=0...capacity,如上图所示,当j=8时应该有B(8)=B(8-w(3))+v(3)=B(4)+5,然而此时的B(4)已经在j=4的时候被修改过了,原来的B(4)=4,现在B(4)=5,所以计算得出B(8)=5+5=10,显然这于正确答案不符合;所以该一维数组后面的值需要前面的值进行运算再改动,如果正序便利,则前面的值将有可能被修改掉从而造成后面数据的错误;相反如果逆序遍历,先修改后面的数据再修改前面的数据,此种情况就不会出错了;

 1 void FindMaxBetter()//优化空间后的动态规划

 2 {

 3     int i,j;

 4     for(i=1;i<=number;i++)

 5     {

 6         for(j=capacity;j>=0;j--)

 7         {

 8             if(B[j]<=B[j-w[i]]+v[i] && j-w[i]>=0 )//二维变一维

 9             {

10                 B[j]=B[j-w[i]]+v[i];

11             }

12         }

13     }

14 }

  n) 然而不足的是,虽然优化了动态规划的空间,但是该方法不能找到最优解的解组成,因为动态规划寻早解组成一定得在确定了最优解的前提下再往回找解的构成,而优化后的动态规划只用了一维数组,之前的数据已经被覆盖掉,所以没办法寻找,所以两种方法各有其优点。

四、蛮力法检验:

  1) 蛮力法是解决01背包问题最简单最容易的方法,但是效率很低

  2) (X1,X2…,Xn)其中Xi=0或1表示第i件商品选或不选,共有n(n-1)/2种可能;

  3) 最简单的方式就是把所有拿商品的方式都列出来,最后再做判断此方法是否满足装包条件,并且通过比较和记录找出最优解和解组成(如果满足则记录此时的价值和装的方式,当下一次的装法优于这次,则更新记录,如此下去到最后便会找到最优解,同时解组成也找到);

  4) n件商品,共有n(n-1)/2种可能,故蛮力法的效率是指数级别的,可见效率很低;

  5) 蛮力法效率低不建议采取,但可以用于检验小规模的动态规划解背包问题的正确性和可行性,如下图输出可见,解01背包问题用动态规划是可行的:

五、总结:

  对于01背包问题,用蛮力法与用动态规划解决得到的最优解和解组成是一致的,所以动态规划解决此类问题是可行的。动态规划效率为线性,蛮力法效率为指数型,结合以上内容和理论知识可以得出,解决此问题用动态规划比用蛮力法适合得多。对于动态规划不足的是空间开销大,数据的存储得用到二维数组;好的是,当前问题的解只与上一层的子问题的解相关,所以,可以把动态规划的空间进行优化,使得空间效率从O(n*c)转化为O(c),遗憾的是,虽然优化了空间,但优化后只能求出最优解,解组成的探索方式在该方法运行的时候已经被破坏掉;总之动态规划和优化后的动态规划各有优缺点,可以根据实际问题的需求选择不同的方式。

Java实现:

packagebagProblem;

 

importjava.util.Scanner;

 

public class bagProblem {

    public static void main(String[] args)

    {

        Scanner input =new Scanner(System.in);

        System.out.println("input the number of items");

        int n = input.nextInt();

        System.out.println("input totle ofcapacity");

        int c = input.nextInt();

        System.out.println("input the capacity");

        int[]capacity = newint[n];

        for(inti = 0 ; i <n;i++)

        {

            capacity[i] =input.nextInt();

        }

        System.out.println("input the value");

        int[]value = newint[n];

        for(inti = 0 ; i <n;i++)

        {

            value[i] =input.nextInt();

        }

        int[][]V = FindMax(c,n,capacity,value);

       FindWhat(n,c,value,V,capacity);

       

    }

    public static int[][] FindMax(intc,intn , int[]capacity,int[]value)

    {

        int[][]V = newint[n + 1][c + 1];

        for(inti = 1 ; i <=n ; i ++)

        {

            for(intj = 1;j <= c;j++)

            {

                if(j <capacity[i - 1])//装不进去第i

                   V[i][j]  =V[i - 1][j];

                //因为装不进第iitem,因此增加了一个capacity也是和i-1时候一样

                else//能装下

                {

                   if(V[i - 1][j] >V[i -1][j -capacity[i - 1 ]] +value[i - 1])

                   {   //不装的价值大,

                       //因此当满足else时候刚好是满足能装下第iitem时候,但是需要比较用第i个替换前面的i-1个值不值得

                       V[i][j] =V[i - 1][j];

                   }

                          

                   else//i-1item的最优解与第iitem的价值之和更大

                   {

                        V[i][j] =V[i -1][j -capacity[i - 1]] +value[i - 1];

                   }

                }

            }

        }

        for(inti = 0 ; i <=n ; i ++)

        {

            for(intj = 0;j <= c;j++)

            {

                System.out.print(V[i][j] +" ");

            }

            System.out.println();

        }

    return V;

    }

   

      public static void FindWhat(inti ,intj , int[]value ,int[][]V ,int[]capacity)

    {

       

        if(i >= 1)

        {

            if(V[i][j] ==V[i -1][j])

            {

                FindWhat(i -1 ,j,value,V,capacity);

            }

            else if(j - capacity[i - 1] >= 0 && V[i][j] ==V[i-1][j-capacity[i - 1 ]]+value[i - 1] )

            {

                System.out.println(i);

                FindWhat(i - 1,j -capacity[i - 1],value,V,capacity);//回到装包之前的位置

            }

        }

    }

     

   

 

}

 

输出:

inputthe number of items

4

inputtotle ofcapacity

8

inputthe capacity

2 34 5

inputthe value

3 45 6

0 0 00 Exception inthread "main" 0 0 0 0 0

0 0 33 3 3 3 3 3

0 0 34 4 7 7 7 7

0 0 34 5 7 8 9 9

0 0 34 5 7 8 9 10

4

2

java.lang.ArrayIndexOutOfBoundsException: -1

    at bagProblem.bagProblem.FindWhat(bagProblem.java:70)

    at bagProblem.bagProblem.FindWhat(bagProblem.java:72)

    at bagProblem.bagProblem.FindWhat(bagProblem.java:77)

    at bagProblem.bagProblem.FindWhat(bagProblem.java:72)

    at bagProblem.bagProblem.FindWhat(bagProblem.java:77)

    at bagProblem.bagProblem.main(bagProblem.java:26)

为什么会报错,报错的是什么东西?

因为第70行,if(i>=0),但是存在capacity【I - 1】。因此会报错。

更改为if(i>=1)后正常:

5.1背包问题的改进---当同时限制背包的重量和体积时,求最大的value?

http://www.cnblogs.com/JLY001/p/7270683.html

 

 

6.最长公共子序列

下面,咱们运用此动态规划算法解此LCS问题。有一点必须声明的是,LCS问题即最长公共子序列问题,它不要求所求得的字符在所给的字符串中是连续的

 

LCS问题描述
题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。
请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。


例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子序列,则输出它们的长度4,并打印任意一个子序列。

 

分析:求最长公共子序列(Longest CommonSubsequence, LCS)是一道非常经典的动态规划题,因此一些重视算法的公司像MicroStrategy都把它当作面试题。

事实上,最长公共子序列问题也有最优子结构性质。

记:

Xi=﹤x1,⋯,xi﹥即X序列的前i个字符 (1≤i≤m)(前缀)

Yj=﹤y1,⋯,yj﹥即Y序列的前j个字符 (1≤j≤n)(前缀)

假定Z=﹤z1,⋯,zk﹥∈LCS(X , Y)。

若xm=yn(最后一个字符相同),则不难用反证法证明:该字符必是X与Y的任一最长公共子序列Z(设长度为k)的最后一个字符,即有zk = xm = yn 且显然有Zk-1∈LCS(Xm-1 , Yn-1)即Z的前缀Zk-1是Xm-1与Yn-1的最长公共子序列。此时,问题化归成求Xm-1与Yn-1的LCS(LCS(X ,Y)的长度等于LCS(Xm-1 , Yn-1)的长度加1)。

若xm≠yn,则亦不难用反证法证明:要么Z∈LCS(Xm-1, Y),要么Z∈LCS(X , Yn-1)。由于zk≠xm与zk≠yn其中至少有一个必成立,若zk≠xm则有Z∈LCS(Xm-1 , Y),类似的,若zk≠yn 则有Z∈LCS(X ,Yn-1)。此时,问题化归成求Xm-1与Y的LCS及X与Yn-1的LCS。LCS(X , Y)的长度为:max{LCS(Xm-1, Y)的长度, LCS(X , Yn-1)的长度}。

由于上述当xm≠yn的情况中,求LCS(Xm-1 , Y)的长度与LCS(X , Yn-1)的长度,这两个问题不是相互独立的:两者都需要求LCS(Xm-1,Yn-1)的长度。另外两个序列的LCS中包含了两个序列的前缀的LCS,故问题具有最优子结构性质考虑用动态规划法。

 

也就是说,解决这个LCS问题,你要求三个方面的东西:

1、LCS(Xm-1,Yn-1)+1;

2、LCS(Xm-1,Y),LCS(X,Yn-1);

3、max{LCS(Xm-1,Y),LCS(X,Yn-1)}。

 

2.1、最长公共子序列的结构

最长公共子序列的结构有如下表示:

设序列X=和Y=的一个最长公共子序列Z=,则:

若xm=yn,则zk=xm=yn且Zk-1是Xm-1和Yn-1的最长公共子序列;

若xm≠yn且zk≠xm ,则Z是Xm-1和Y的最长公共子序列;

若xm≠yn且zk≠yn ,则Z是X和Yn-1的最长公共子序列。

其中Xm-1=,Yn-1=,Zk-1=

 

2.2、子问题的递归结构

由最长公共子序列问题的最优子结构性质可知,要找出X=和Y=的最长公共子序列,可按以下方式递归地进行:当xm=yn时,找出Xm-1和Yn-1的最长公共子序列,然后在其尾部加上xm(=yn)即可得X和Y的一个最长公共子序列。当xm≠yn时,必须解两个子问题,即找出Xm-1和Y的一个最长公共子序列及X和Yn-1的一个最长公共子序列。这两个公共子序列中较长者即为X和Y的一个最长公共子序列。

由此递归结构容易看到最长公共子序列问题具有子问题重叠性质。例如,在计算X和Y的最长公共子序列时,可能要计算出X和Yn-1及Xm-1和Y的最长公共子序列。而这两个子问题都包含一个公共子问题,即计算Xm-1和Yn-1的最长公共子序列。

与矩阵连乘积最优计算次序问题类似,我们来建立子问题的最优值的递归关系。用c[i,j]记录序列Xi和Yj的最长公共子序列的长度。其中Xi=,Yj=。当i=0或j=0时,空序列是Xi和Yj的最长公共子序列,故c[i,j]=0。其他情况下,由定理可建立递归关系如下:

 

2.3、计算最优值

直接利用上节节末的递归式,我们将很容易就能写出一个计算c[i,j]的递归算法,但其计算时间是随输入长度指数增长的。由于在所考虑的子问题空间中,总共只有θ(m*n)个不同的子问题,因此,用动态规划算法自底向上地计算最优值能提高算法的效率。

计算最长公共子序列长度的动态规划算法LCS_LENGTH(X,Y)

l  以序列X=和Y=作为输入。

l  输出两个数组c[0..m ,0..n]和b[1..m ,1..n]。

其中c[i,j]存储Xi与Yj的最长公共子序列的长度,

b[i,j]记录指示c[i,j]的值是由哪一个子问题的解达到的,这在构造最长公共子序列时要用到。

l  最后,X和Y的最长公共子序列的长度记录于c[m,n]中。

[cpp] view plain copy

 print?

1.  Procedure LCS_LENGTH(X,Y);  

2. begin  

3.    m:=length[X];  

4.   n:=length[Y];  

5.    for i:=1 to m do c[i,0]:=0;  

6.   for j:=1 to n do c[0,j]:=0;  

7.    for i:=1 to m do  

8.     for j:=1 to n do  

9.        if x[i]=y[j] then  

10.        begin  

11.           c[i,j]:=c[i-1,j-1]+1;  

12.          b[i,j]:="";  

13.         end  

14.      else if c[i-1,j]≥c[i,j-1] then  

15.         begin  

16.          c[i,j]:=c[i-1,j];  

17.           b[i,j]:="↑";  

18.        end  

19.       else  

20.        begin  

21.           c[i,j]:=c[i,j-1];  

22.          b[i,j]:="←"  

23.         end;  

24.  return(c,b);  

25. end;  

由算法LCS_LENGTH计算得到的数组b可用于快速构造序列X=和Y=的最长公共子序列。

首先从b[m,n]开始,沿着其中的箭头所指的方向在数组b中搜索。

l  当b[i,j]中遇到"↖"时(意味着xi=yi是LCS的一个元素),表示Xi与Yj的最长公共子序列是由Xi-1与Yj-1的最长公共子序列在尾部加上xi得到的子序列;

l  当b[i,j]中遇到"↑"时,表示Xi与Yj的最长公共子序列和Xi-1与Yj的最长公共子序列相同;

l  当b[i,j]中遇到"←"时,表示Xi与Yj的最长公共子序列和Xi与Yj-1的最长公共子序列相同。

这种方法是按照反序来找LCS的每一个元素的。由于每个数组单元的计算耗费Ο(1)时间,算法LCS_LENGTH耗时Ο(mn)。

2.4、构造最长公共子序列

下面的算法LCS(b,X,i,j)实现根据b的内容打印出Xi与Yj的最长公共子序列。通过算法的调用LCS(b,X,length[X],length[Y]),便可打印出序列X和Y的最长公共子序列。

[cpp] view plain copy

 print?

1.  Procedure LCS(b,X,i,j);  

2. begin  

3.    if i=0 or j=0 then return;  

4.   if b[i,j]="" then  

5.      begin  

6.       LCS(b,X,i-1,j-1);  

7.        print(x[i]); {打印x[i]}  

8.     end  

9.    else if b[i,j]="↑" then LCS(b,X,i-1,j)   

10.                      else LCS(b,X,i,j-1);  

11. end;   

在算法LCS中,每一次的递归调用使i或j减1,因此算法的计算时间为O(m+n)。

例如,设所给的两个序列为X=和Y=。由算法LCS_LENGTH和LCS计算出的结果如下图所示:

我来说明下此图(参考算法导论)。在序列X={ABCBDAB} Y={BDCABA}上,由LCS_LENGTH计算出的表cb。第i行和第j列中的方块包含了c[ij]的值以及指向b[ij]的箭头。在c[7,6]的项4,表的右下角为XY的一个LCS的长度。对于ij>0,项c[ij]仅依赖于是否有xi=yi,及项c[i-1j]c[ij-1]的值,这几个项都在c[ij]之前计算。为了重构一个LCS的元素,从右下角开始跟踪b[ij]的箭头即可,这条路径标示为阴影,这条路径上的每一个对应于一个使xi=yi为一个LCS的成员的项(高亮标示)。

所以根据上述图所示的结果,程序将最终输出:“B CB A”,或“B D A B”

可能还是有读者对上面的图看的不是很清楚,下面,我再通过对最大子序列,最长公共子串与最长公共子序列的比较来阐述相关问题@Orisun

  • 最大子序列:最大子序列是要找出由数组成的一维数组中和最大的连续子序列。比如{5,-3,4,2}的最大子序列就是{5,-3,4,2},它的和是8,达到最大;而{5,-6,4,2}的最大子序列是{4,2},它的和是6。你已经看出来了,找最大子序列的方法很简单,只要前i项的和还没有小于0那么子序列就一直向后扩展,否则丢弃之前的子序列开始新的子序列,同时我们要记下各个子序列的和,最后找到和最大的子序列。更多请参看:程序员编程艺术第七章、求连续子数组的最大和
  • 最长公共子串:找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的。其实这又是一个序贯决策问题,可以用动态规划来求解。我们采用一个二维矩阵来记录中间的结果。这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab")

bab

c000

a010

b101

a010

我们看矩阵的斜对角线最长的那个就能找出最长公共子串。

不过在二维矩阵上找最长的由1组成的斜对角线也是件麻烦费时的事,下面改进:当要在矩阵是填1时让它等于其左上角元素加1

bab

c000

a010

b102

a020

这样矩阵中的最大元素就是最长公共子串的长度。

在构造这个二维矩阵的过程中由于得出矩阵的某一行后其上一行就没用了,所以实际上在程序中可以用一维数组来代替这个矩阵。

  • 最长公共子序列LCS问题:最长公共子序列与最长公共子串的区别在于最长公共子序列不要求在原字符串中是连续的,比如ADE和ABCDE的最长公共子序列是ADE。

我们用动态规划的方法来思考这个问题如是求解。首先要找到状态转移方程:

等号约定,C1S1的最右侧字符,C2S2的最右侧字符,S1‘是从S1中去除C1的部分,S2'是从S2中去除C2的部分。

LCS(S1,S2)等于:

1LCSS1S2’

2LCSS1’S2

3)如果C1不等于C2LCSS1’S2’);如果C1等于C2LCSS1'S2'+C1

边界终止条件:如果S1S2都是空串,则结果也是空串。

下面我们同样要构建一个矩阵来存储动态规划过程中子问题的解。这个矩阵中的每个数字代表了该行和该列之前的LCS的长度。与上面刚刚分析出的状态转移议程相对应,矩阵中每个格子里的数字应该这么填,它等于以下3项的最大值:

1)上面一个格子里的数字

2)左边一个格子里的数字

3)左上角那个格子里的数字(如果C1不等于C2);左上角那个格子里的数字+1(如果C1等于C2

举个例子:

GCTA

00000

G 01111

B 01111

T 01122

A 01123

填写最后一个数字时,它应该是下面三个的最大者:

1)上边的数字2

2)左边的数字2

3)左上角的数字2+1=3,因为此时C1==C2

所以最终结果是3

在填写过程中我们还是记录下当前单元格的数字来自于哪个单元格,以方便最后我们回溯找出最长公共子串。有时候左上、左、上三者中有多个同时达到最大,那么任取其中之一,但是在整个过程中你必须遵循固定的优先标准。在我的代码中优先级别是左上>>上。

下图给出了回溯法找出LCS的过程:

2.5、算法的改进

对于一个具体问题,按照一般的算法设计策略设计出的算法,往往在算法的时间和空间需求上还可以改进。这种改进,通常是利用具体问题的一些特殊性。

例如,在算法LCS_LENGTH和LCS中,可进一步将数组b省去。事实上,数组元素c[i,j]的值仅由c[i-1,j-1],c[i-1,j]和c[i,j-1]三个值之一确定,而数组元素b[i,j]也只是用来指示c[i,j]究竟由哪个值确定。因此,在算法LCS中,我们可以不借助于数组b而借助于数组c本身临时判断c[i,j]的值是由c[i-1,j-1],c[i-1,j]和c[i,j-1]中哪一个数值元素所确定,代价是Ο(1)时间。既然b对于算法LCS不是必要的,那么算法LCS_LENGTH便不必保存它。这一来,可节省θ(mn)的空间,而LCS_LENGTH和LCS所需要的时间分别仍然是Ο(mn)和Ο(m+n)。不过,由于数组c仍需要Ο(mn)的空间,因此这里所作的改进,只是在空间复杂性的常数因子上的改进。

另外,如果只需要计算最长公共子序列的长度,则算法的空间需求还可大大减少。事实上,在计算c[i,j]时,只用到数组c的第i行和第i-1行。因此,只要用2行的数组空间就可以计算出最长公共子序列的长度。更进一步的分析还可将空间需求减至min(m, n)。

Java实现:

importjava.util.Random;

 

public class LCS {

    public static void main(String[] args)

    {

        int substringLength1 = 20 ;

        int substringLength2 = 20 ;

        //随机生成字符

        String x= GetRandomStrings(substringLength1);

        String y= GetRandomStrings(substringLength1);

       

        Long startTime = System.nanoTime();

        //构造二维数组记录子问题x[i]y[j]LCS的长度,切记一定是+1的长度。

        int[][]opt = newint[substringLength1 + 1][substringLength2 + 1];

       

        //动态规划计算所有的子问题.相当于从最后一个字符往前运算。21*21的矩阵,但是最后一个位置为19*19是最开始运算的地方。输出的opt[][]也是在19*

        for(inti = substringLength1 - 1 ;i >= 0;i--)

        {

            for(intj = substringLength2 - 1;j >= 0;j--)

            {

                if(x.charAt(i) ==y.charAt(j))

                   opt[i][j] =opt[i + 1][j + 1] + 1;

                else

                   opt[i][j] = Math.max(opt[i + 1][j],opt[i][j + 1]);

            }

        }

        //打印最长字符串

        System.out.println("substring1" +x);

        System.out.println("substring2" +y);

        System.out.print("LCS:");

        int i = 0 ,j = 0;

        while(i <substringLength1 && j < substringLength2)

        {

            if(x.charAt(i) ==y.charAt(j))

            {

                System.out.print(x.charAt(i));

                i++;

                j++;

            }

            else if(opt[i + 1][j] >=opt[i][j + 1])

            {

                i++;

            }

            else

                j++;

        }

        System.out.println();

       

        //判断运行时间

        Long endTime = System.nanoTime();

        System.out.println("Totle time is" + (endTime -startTime) + "ns");

    }

   

    //子模块实现字符串的随机产生

    public static String GetRandomStrings(intlength)

    {

        StringBuffer buffer = new StringBuffer("abcdefghijklmnopqrstuvwxyz");

        StringBuffer sb = newStringBuffer();

        Random r = new Random();

        int range = buffer.length();

        for(inti = 0; i < length ;i++)

        {

//添加buffer字符串中随机位置的一个char给sb的尾部。产生的随机数在0-26之间。

            sb.append(buffer.charAt(r.nextInt(range)));

        }

        //返回生成的字符串

        returnsb.toString();

    }

   

}

 

7.最大字段和

l  题目描述:

用户输入一些数字,正负都可以。求这些数字中连续字段和最大的位置从i开始,到j结束的i和j,以及最大值是多少。

l  分析:因为存在正负数,因此开辟一个数组,result[ I ][ j ]表示,第i个元素放入后,前i个数字最佳的字段和,当放入第I 个元素后,只需要data[i]与result[i-1]+ data[i] 谁更大,决定是要前面的字段还是从我的位置重新开始。遍历整个数组即可完成。

l  注意:最大字段和和最长公共子串的区别是,最大字段必须连续。但是可以从任意位置开始。但是不能跳跃。

packagemaxsum;

 

import java.util.ArrayList;

importjava.util.Scanner;

 

public class maxsum {

    public static void main(String[] args)

    {

        //可以随机产生固定大小的数组。在-100100之前

        Scanner input =new Scanner(System.in);

        System.out.println("input the count of data:");

        int n = input.nextInt();

        System.out.println("Roadom " +n + "numbersare as follow");

        int[]data = newint[n];

        for(intk = 0 ; k <n ;k++)

        {

            data[k] = (int) ((Math.random() * 200)-100);

        }

        for( int k = 0 ;k < n;k++)

        {

            System.out.print(data[k] +" ");

        }

        System.out.println();

        int[]result = newint[n];

        int i = 0 , j = 0;

        result[0] = data[0];

        for(intk = 1 ;  k <n;k++)

        {

            if(data[k] >=result[k - 1] +data[k])

            {

                result[k] =data[k];

                j = i = k;

            }

            else

            {

                result[k] =result[k - 1] +data[k];

                j++;

            }

               

        }

        /*

         *测试result【】数组是否正确

         *for( int k = 0 ; k < data.length;k++)

        {

            System.out.print(result[k] + "");

        }

         */

        int max = result[0];

        for( int k = 0 ;k < n;k++)

        {

            if(result[k] >max)

                max = result[k];

        }

        System.out.println("the max count is " +max +" ");

        System.out.println("the first number is " +(i + 1) +" ");

        System.out.println("the second number is " +(j + 1) + " ");

    }

}

输出:

问题: 怎么用java接受一个不确定大小的int数字。如果用ArrayList确实可以忽略大小,但是怎么保证接受到回车停止接受。怎么判断和赋值?

 

 

8.最小硬币找零数

   硬币找零问题描述:现存在一堆面值为 V1、V2、V3 … 个单位的硬币,问最少需要多少个硬币才能找出总值为 T 个单位的零钱?----有面值种类的限制,但是没有个数的限制
  假设这一堆面值分别为 1、2、5、21、25 元,需要找出总值 T 为 63 元的零钱。很明显,只要拿出 3 个 21 元的硬币就凑够了 63 元了。
  基于上述动态规划的思想,我们可以从 1元开始计算出最少需要几个硬币,然后再求 2 元、3元…每一次求得的结果都保存在一个数组中,以后需要用到时则直接取出即可。那么我们什么时候需要这些子问题的解呢?如何体现出由子问题的解得到较大问题的解呢?
  其实,在我们从1元开始依次找零时,可以尝试一下当前要找零的面值(这里指 1 元)是否能够被分解成另一个已求解的面值的找零需要的硬币个数再加上这一堆硬币中的某个面值之和,如果这样分解之后最终的硬币数是最少的,那么问题就得到答案了。
  单是上面的文字描述太抽象,先假定以下变量:
     values[] :保存每一种硬币的币值的数组
     valueKinds :币值不同的硬币种类数量,即values[]数组的大小
     money :需要找零的面值
     coinsUsed[] :保存面值为 i 的纸币找零所需的最小硬币数
  算法描述:

  当求解总面值为 i的找零最少硬币数coinsUsed[ i ] 时,将其分解成求解 coinsUsed[ i – cents]和一个面值为 cents 元的硬币,由于 i – cents < i , 其解 coinsUsed[ i – cents] 已经存在,如果面值为 cents 的硬币满足题意,那么最终解 coinsUsed[ i ]则等于 coinsUsed[ i – cents] 再加上 1(即面值为cents)的这一个硬币。

具体实例及代码如下所示:----动态规划的思想(填表)

/**
 * @Title: MinCoinsChangeCount.java
 * @Package dynamicprogramming
 * @Description: TODO
* @author peidong
 * @date 2017-6-7 上午9:18:37 
 * @version V1.0
 */
package dynamicprogramming;
 
/**
 * @ClassName: MinCoinsChangeCount
 * @Description: 最小硬币找零数
 * @date 2017-6-7 上午9:18:37 
 *
 */
 
public class MinCoinsChangeCount {
    /**
     *
     * @Title: makeChange
     * @Description: 硬币找零
     * @param values  保存每一种硬币的币值数组
     * @param valueKinds 币值不同的硬币种类数量,values数组的大小
     * @param money 需要找零的面值
     * @param coinsUsed 保存面值为i的纸币找零所需的最小硬币数
     * @return void
     * @throws
     */
    public static void makeChange(int[] values, int valueKinds, int money, int[] coinsUsed) {
 
        coinsUsed[0] = 0;
        // 对每一分钱都找零,即保存子问题的解以备用,即填表  
        for (int cents = 1; cents <= money; cents++) {
 
            // 当用最小币值的硬币找零时,所需硬币数量最多  
            int minCoins = cents;
 
            // 遍历每一种面值的硬币,看是否可作为找零的其中之一  
            for (int kind = 0; kind < valueKinds; kind++) {
                // 若当前面值的硬币小于当前的cents则分解问题并查表  
                if (values[kind] <= cents) {
                    int temp = coinsUsed[cents - values[kind]] + 1;
                    if (temp < minCoins) {
                        minCoins = temp;
                    }
                }
            }
            // 保存最小硬币数  
            coinsUsed[cents] = minCoins;
 
            System.out.println("面值为 " + (cents) + " 的最小硬币数 : "
                    + coinsUsed[cents]);
        }
    }
 
    /**
     * @Title: main
     * @Description: TODO
     * @param args
     * @return void
     * @throws
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        // 硬币面值预先已经按降序排列  
        int[] coinValue = new int[] { 25, 21, 10, 5, 1 };
        // 需要找零的面值  
        int money = 63;
        // 保存每一个面值找零所需的最小硬币数,0号单元舍弃不用,所以要多加1  
        int[] coinsUsed = new int[money + 1];
 
        makeChange(coinValue, coinValue.length, money, coinsUsed);
 
    }
 
}

输出:

面值为1硬币数为1

面值为2硬币数为2

……      省略        ……

面值为63硬币数为3

面值为64硬币数为4

动态规划的结果很难输出分别哪种硬币几个。

 

如果不用动态规划的思想,并且不仅输出个数,还输出用什么硬币怎么找的零(遍历)

代码如下:

 

public class MinCoinsChangeCount {

    public static void main(String[] args)

    {

        //默认5种面值的硬币,默认为降序

        int[]value = { 25, 21, 10, 5, 1 };

        //默认钱数

        int money = 65;

        int count = 0 ;//默认记录minCoins

        System.out.print("最少需要的硬币个数为:");

        System.out.println();

        for(inti = 0 ; i < 5;i++)

        {

            inttemp = 0;

            if(value[i] <=money)

            {

                 temp = money / value[i];

                count += temp;

                money -= temp*value[i];

                System.out.println("需要面值为"+value[i] + "的硬币" +temp + "");

            }

        }

        System.out.println("总个数为" +count );

    }

}

输出:

8.1当存储硬币面值的数组是T【1…n】;存储相应n个硬币的个数为coins【n…m】个。问存M的总数,怎么分配min的硬币。

代码如下:

 

public class MinCoinsChangeCount {

    public static void main(String[] args)

    {

        //默认5种面值的硬币,默认为降序

        int[]value = { 25, 21, 10, 5, 1 };

        int[]coins = {1,4,5,1,2};

        //默认钱数

        int money = 65;

        int count = 0 ;//默认记录minCoins

        System.out.print("最少需要的硬币个数为:");

        System.out.println();

        for(inti = 0 ; i < 5;i++)

        {

            inttemp = 0;

            if(value[i] <=money)

            {

                 temp = money / value[i];

                 if(temp <=coins[i])

                 {

                       count +=temp;

                       money  =money%value[i];

                      

                 }

                 else

                 {

                    temp coins[i];

                    count += coins[i];

                    money -= coins[i] * value[i];

                 }

                 System.out.println("需要面值为"+value[i] + "的硬币" +temp + "");

            }

        }

        System.out.println("总个数为" +count );

    }

}

输出:

动态规划实现:

 

你可能感兴趣的:(算法分析与设计)