算法动态规划个人总结

由于有时候用到递归来求解问题的时候由于它是动态进行想自顶向下入栈然后在出口的地方自下而上全部出栈来进行顶层求解的。如求斐波那契数:1 1 2 3 5 8 13.....f(n-2),f(n-1),f(n)   规律f(n-2)+f(n-1)=f(n)

如果用递归如图所示那就有些值计算的多次  如f(2) f(3) 这就是递归的问题,效率不高

那为了解决这个问题:我们想到打表,把前面计算过的数记下来以后就不用重复算了 如:
根据1 1 算出第3个数为2  根据1 2 算出第4为3 那就可以用一个数组全部从最前面的一个一个向下推
1 1 2 3 5......都打表记下来,以后就可以查询了。

其实这个方法的精髓就是动态规划,动态规划的核心思想就是把大问题分小问题解决,先把前面的计算出来,然后根据前面的结果进行下一个结果的计算,这样就绝不会重复计算了。
warshall 的有向图传递闭包就是一个很好的动态规划典型例子;
   先是把其邻接矩阵得到:A[][]
    然后在一个点一个点加在里面,进行对前面结果矩阵A[][]进行修改;
    即是:加入一个点后要对前面的表格进行修改,也就是利用前一个结果表对后一个结果表的更新;
    加入点a后每个点对这个a进行查找记录和a联通的点放在n[]中,然后对于每个n[]中的点q对每个点进行判断,找到连接点放在m[]中点p,这时候代表q,b是通过加入a点是相连的,所以把p b对于的A[][]进行更新为1


最短路径floyd
   对于最先建立有向矩阵A[][] 然后加入a点,先找和a点相连的点n[],在对于每个n[]的点进行每个点的查找,然后把连个对应的数相加与原来的矩阵的数比较:如 x a y,通过中间点a 后xa+ay如果<xy对于的数那就更新为xa+ya到xy位置上;就这样通过添加中间点进行矩阵更新,添加了全部点后就是最后的最短路径。 


哈弗曼树也是通过动态规划来建立的:把点都放到一个数组中,然后通过找最小的两个点进行的结果赋值个一个新点,这样一个小树建好了,把这个点放进数组中,最小的两个点标记为已用(有了父节点)。这样一步一步更新数,直到没有节点可以了,就是最后的哈弗曼树了。  (动态规划的思想)


背包问题:01背包问题是基础,即每样东西有一个,背包能装下就行,不要求是刚刚好,求最大价值时:物品的组合情况
有一个这样的递推式:
V[i,j] 当j-W(i)>=0即能放i物品时:有两种情况:一:放进去V[i-1,j-W(i)]+v(i)二:就是不放V[i-1,j]  选它们两个中的最大值    
       但j-W(i)<0即不能放i物品时:V[i-1,j] 


由上面的公式可以有下面的推导结果
由于i=0间物品时背包j多重都是价值为0,那就通过比较可以的一个一个计算出每个A[i][j]的点对于的值,通过这个二维表就得到就终结果了。



01背包的条件改为包要刚刚放满
  转为:01背包问题解决  其它的过程和01背包一样只是在一开始初始化上有差别:当i=0时,如果j背包也为0那就符合条件此时赋值A[0][0]=0,如果j!=0时,那是不符合条件所以赋值为-无穷大。这样也可以计算出来

完全背包问题:

  即物品可以放多件  
  一种方式:把每一种的多件也作为不同物品,满足(nC(i)<背包重量j)这样就和01背包是一样了,但效率太低了,表太大.
  二种方式:在第一种方式的基础上进行的改进,把n个一种物品,不把它作为n个不同的物品了,而是通过把nC(i),利用n把它变成二进制的组合:如:13 变成00000111即分别是2的1次方,2次方,3次方这三种物品,就不用可能最大13种物品了,有效减少物品种类个数,这样为解决问题提供可能性了。
  
背包问题还有很多:可以查看网上背包问题九讲的电子档


今天动态规划总结到这,以后再更新
其实由于递归算法产生的算法效率问题,现在主要是用两个方法来解决:一是:动态规划  二就是:分支区限法:这就是在分支画图分析的时候把不可能的,或者不是最好的去除掉不要递归,从而减少递归,提高效率.
 
用动态规划解--滑雪题 算法分析            

    问题描述:
    Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长底滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子

     1  2  3  4 5
    16 17 18 19 6
    15 24 25 20 7
    14 23 22 21 8
    13 12 11 10 9
    一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。
    Input
    输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。

    Output
    输出最长区域的长度。
    Sample Input

    5 5
    1 2 3 4 5
    16 17 18 19 6
    15 24 25 20 7
    14 23 22 21 8
    13 12 11 10 9

    Sample Output

    25
    分析及代码实现:

    这个问题应该来说是个简单的,很容易想到用动态规划去做的题目。这个问题满足最有子结构
    是比较容易看出来。非常容易建立如下递归式:
    如果从i,j可以顺着某侧滑的话:
    dis_sk[i][j] = max{dis_sk[i-1][j],dis_sk[i][j-1],dis_sk[i+1][j],dis_sk[i][j+1]}+1
    那么我们很容易写出递归的:
    int dis(int i,int j){
      for(i,j上侧,下侧,左侧,右侧)
          if(该位置没有越界){
             if(顺着该侧可以往下滑)
                如果该侧位置可以滑行的距离(递归调用dis函数)大于dis_sk[i][j],则把dis_sk[i][j]改成该距离+1
          }
    }
    把这个递归改成动态规划很容易,只要在开始判断一下
    if(dis_sk[i][j]) return dis_sk[i][j]; //dis_sk[i][j]开始为0
    这样基本上就可以很顺畅的写出代码了:
    定义的变量如下:

    cpp 代码
    1. int h[101][101];//输入的高度值  
    2. int dis_sk[101][101];//记录了每个点可以滑行的最大距离  
    3. int dx[]={-1,1,0,0};//为了方便上下左右侧的滑行的最大距离而使用的方便数组  
    4. int dy[]={0,0,-1,1};   
    5. int r,c;//输入的行和列  

     一个用来判断是否越界的辅助函数:

    cpp 代码
    1. bool in_bound(int i,int j){   
    2.    return i >= 0 && i < r && j >= 0 && j < c;    
    3. }  

    下边就是以lookup方式写的动态规划实现的从i,j下滑最大距离:

    cpp 代码
    1. int dis(int i,int j){   
    2.     int temp;   
    3.     if(dis_sk[i][j])//如果已经求出来了,直接返回  
    4.         return dis_sk[i][j];   
    5.     for(int k = 0; k < 4; k++){   
    6.         if(in_bound(i+dx[k],j+dy[k])){//如果没有越界  
    7.             if(h[i][j] > h[ i+dx[k] ][ j+dy[k] ]){//如果顺着该侧可以滑  
    8.                temp = dis(i+dx[k],j+dy[k]);//递归求dis(i+dx[k],j+dy[k]),并保存在临时变量temp中  
    9.                dis_sk[i][j] = dis_sk[i][j] > temp ? dis_sk[i][j] : temp + 1;//如果dis_sk[i][j]比temp小,则取temp+1  
    10.             }   
    11.         }   
    12.     }   
    13.     return dis_sk[i][j];   
    14. }  

    最后是main函数,取dis(i,j)的最大者就是所求的最长区域的长度:

     
     
    //输出最长路径的长度和实际的路径
    #include <iostream>
    #include <string>
    #include <tr1/unordered_map>
    using namespace std;
      int h[101][101];
      int dis_sk[101][101];
      int pre[101][101][2];//用来记录结点的前一个结点
      int dx[]={-1,1,0,0};
      int dy[]={0,0,-1,1};
      int r,c;
    bool in_bound(int i,int j){
         return i>=0&&i<r&&j>=0&&j<c;
    }
    int dis(int k,int m){
        int i,temp;
    	if(dis_sk[k][m])
    	  return dis_sk[k][m];
    	for(i=0;i<4;i++){
    		 if(in_bound(k+dx[i],m+dy[i]))
                {
    			  if(h[k][m]>h[k+dx[i]][m+dy[i]]){
    			     temp=dis(k+dx[i],m+dy[i]);
    				 if(dis_sk[k][m]<=temp) {
    				 pre[k][m][0]=k+dx[i];
    				 pre[k][m][1]=m+dy[i];
    				 dis_sk[k][m]=temp+1;
    				 }//如果儿子结点记录的路径大于或等于父结点,那么儿子结点就是父亲结点的下一个结点
    			     //dis_sk[k][m]=dis_sk[k][m]>temp?dis_sk[k][m]:temp+1;
    			  }
    			}
    	}
    	return dis_sk[k][m];
    }
    int main(){
       
       int max_dis=0;
       int temp;
       int i,j;
       cin>>r>>c;
       for(i=0;i<r;i++){
           for(j=0;j<c;j++)
    	   {
    	   cin>>h[i][j];
    	   dis_sk[i][j]=0;
    	   pre[i][j][0]=-1;
    	   pre[i][j][1]=-1;
    	   }
    	}
    	int ii,jj;
    	 for(i=0;i<r;i++){
           for(j=0;j<c;j++)
    	   {temp=dis(i,j);
    	    if(max_dis<temp){
    		ii=i;//记录最大路径的结点序号
    		jj=j;//记录最大路径的结点序号
    		max_dis=temp;
    		}
    	   // max_dis=max_dis>temp?max_dis:temp;
    	   }
    	}
    	cout<<max_dis+1<<endl;//最长路径
    	//输出其路径
    	while((pre[ii][jj][0]!=-1)&&(pre[ii][jj][1]!=-1))
    	    {   
    		   cout<<h[ii][jj]<<"  ";
    		   int temp_i=pre[ii][jj][0];
    		   int temp_j=pre[ii][jj][1];
    		   ii=temp_i;
               jj=temp_j;		   
    		}
    	cout<<h[ii][jj]<<"  "<<endl;
    	
    	return 0;
    }
     
      

    回溯法

     

    八皇后

    这种可能成为n的指数形式的编程一般用 回溯的方法找其中一个解,如果要全部求出解那和递归效率差不多,不过递归有个压栈,也就可能栈被压爆了,那还是用回溯来弄,回溯的另一种改进方法时 分支限定法:也就是在他用回溯求出一个解后,那这个解就可以成为一个限定条件来进行其它解的最优情况的排除,就可以尽量少的进行遍历了。


     
    #include<stdio.h>
    #include<math.h>
    int x[100];
    bool place(int k)//考察皇后k放置在x[k]列是否发生冲突
    {
        int i;
        for(i=1;i<k;i++)
            if(x[k]==x[i]||abs(k-i)==abs(x[k]-x[i]))
                return false;
            return true;
    }

    void queue(int n)
    {
        int i,k;
        for(i=1;i<=n;i++)
            x[i]=0;
        k=1;
        while(k>=1)
        {
            x[k]=x[k]+1;   //在下一列放置第k个皇后
            while(x[k]<=n&&!place(k))
                x[k]=x[k]+1;//搜索下一列
            if(x[k]<=n&&k==n)//得到一个输出
            {
                for(i=1;i<=n;i++)
                    printf("%d ",x[i]);
                printf("\n");
            //return;//若return则只求出其中一种解,若不return则可以继续回溯,求出全部的可能的解
            }
            else if(x[k]<=n&&k<n)
                k=k+1;//放置下一个皇后
            else
            {
                x[k]=0;//重置x[k],回溯
                k=k-1;
            }
        }
    }

    void main()
    {
       int n;
       printf("输入皇后个数n:\n");
       scanf("%d",&n);
       queue(n);
    }


    递归实现

    #include <iostream>
    #include <cmath>
    using namespace std;
    int n=4;
    int x[100];
    bool place(int k)//考察皇后k放置在x[k]列是否发生冲突
    {
        int i;
        for(i=1;i<k;i++)
            if(x[k]==x[i]||abs(k-i)==abs(x[k]-x[i]))
                return false;
            return true;
    }

    void Backtree(int k){
        if(k>n){
       for(int i=1;i<=n;i++)
           cout<<x[i];
        cout<<endl;
        return;
     }
     for(int i=1;i<=n;i++)
      {    x[k]=i;
      if(place(k))  Backtree(k+1);
        }
    }

    int main(){
    Backtree(1);
    return 0;
    }

     


    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值