DP -- 递推

 

在学习动态规划前 , 先补充些有关递归的知识 。

  

  所谓的递归函数 就是调用自身函数的过程 ,因此是用栈来存储的 。

  递归函数的最终返回值 就是第一次调用函数的返回值 。

  在写函数递归时 , 要特别注意的两点 :

       一是  递归 递归 , 一定有让它有能让他回归的条件 。

    二是  写递归时 , 要找到一个最简单的关系式 , 方便写递归函数 。

  

递归可能出现的问题 :

  递归的特点是代码特别简洁 , 但是也有它的缺点 。

  1 . 递归是调用函数的本身 , 所以每次调用都会有时间和空间的消耗 。而每一次调用 , 都需要内存栈中 分配空间 , 以保存参数 ,返回地址 , 临时变量等参数 ,而且往栈里压入数据和弹出数据都需要时间 。

  2 . 递归有很多计算都是重复的 , 从而会对性能带来一定的负面影响 。 递归的本质是把一个问题分为两个或多个小问题 , 如果小问题里有重叠的部分 , 则每个重叠的部分都需要重复计算 。

  3 . 除了效率外 , 递归还存在调用栈溢出的情况 。 前面提到 每一次调用在内存中分配内存空间 , 而每个进程的栈容量是有限的 。 当递归调用的层级太多时 , 就会超出栈的容量 , 从而导致栈溢出 。

  

 

话不多说 , 进入正题 , 先看这道题 。( poj 1163 ) 

 

7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

(Figure 1)
Figure 1 shows a number triangle. Write a program that calculates the highest sum of numbers passed on a route that starts at the top and ends somewhere on the base. Each step can go either diagonally down to the left or diagonally down to the right.

Input

Your program is to read from standard input. The first line contains one integer N: the number of rows in the triangle. The following N lines describe the data of the triangle. The number of rows in the triangle is > 1 but <= 100. The numbers in the triangle, all integers, are between 0 and 99.

Output

Your program is to write to standard output. The highest sum is written as an integer.

Sample Input

5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

Sample Output

30


题目的意思是 : 从上到下 ,每次可以走左下角 或者右下角 , 问最大和是多少 。

  我们可以用一个二维数组去存放此三角形 。
   用 pre[i][j] 表示 第 i 行 第 j 个数 ,每次移动可以有两种选择 , 选择向左下走 , 即 pre[i+1][j] , 或者选择向 右下走 , 即 pre[i+1][j+1] , 若走到最后一行时 ,则返回 pre[i][j] , 不在调用 。


下面附上我的代码 , 还是很好理解的 , 但 是 T 了 。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std ;

int pre[101][101] ;
int n ;

int Max ( int i , int j ) {
	if ( i == n ) return pre[i][j] ;
		
	int x = Max ( i + 1 , j ) ;
	int y = Max ( i + 1 , j + 1 ) ;
	return max ( x , y ) + pre[i][j] ;
}


int main ( ) {
	cin >> n ;
	
	for ( int i = 1 ; i <= n ; i++ ) {
		for ( int j = 1 ; j <= i ; j++ ) {
			cin >> pre[i][j] ;
		}
	}
	cout << Max ( 1 , 1 ) << endl ;
	
	return 0 ;
}

  

想想为什么会超时 ?

  


这是因为 在计算时 遍历递归调用函数 , 有一些位置的 数会计算多次 ,在本题递归时每个数递归的次数 如上图 , 观察可知 复杂度为 2 ^ n , 则当 n = 100 时 , 复杂度为 2 ^ 100 , 则会超时 。


想一个优化的办法 , 可以在每个子节点找到它的最优路径后 , 就记录下来 , 此时就可以想到新建立一个数组 , 在之前函数的基础上 ,每次返回值前先记录一次 , 若递归调用时发现此路径之前走过 , 则可以直接用之前的最优解 , 这样下来会节省很多时间 。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std ;

int pre[101][101] ;
int sum[101][101] ;
int n ;

int Max ( int i , int j ) {
	if ( sum[i][j] != -1 ) 
		return sum[i][j] ;
	if ( i == n ) 
		sum[i][j] = pre[i][j] ;
	else {
		int x = Max ( i + 1 , j ) ;
		int y = Max ( i + 1 , j + 1 ) ;
		sum[i][j] = max ( x , y ) + pre[i][j] ;
	}
	return sum[i][j] ;
}


int main ( ) {
	cin >> n ;
	
	for ( int i = 1 ; i <= n ; i++ ) {
		for ( int j = 1 ; j <= i ; j++ ) {
			cin >> pre[i][j] ;
			sum[i][j] = -1 ;
		}
	}
	cout << Max ( 1 , 1 ) << endl ;
	
	return 0 ;
}

  

此时提交代码就会 AC , 并且会发现运行时间也很短 。 但是这样的代码也是有缺陷的 , 大量的使用堆栈空间会造成 栈溢出 , 现在就可以考虑 将递归写成递推 。

 

  递推的话就可以从最后一行向上推 , 直到推到第一行 ,此时仍可以用二维数组去存每次递推的值 。

 

 

 

通过上述样例 , 写出递归代码 :

 

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std ;

int pre[101][101] ;
int n ;
int maxsum[101][101] ;

int main ( ) {
	int i , j ;
	
	cin >> n ;
	for ( i = 1 ; i <= n ; i++ ) {
		for ( j = 1 ; j <= i ; j++ ) {
			cin >> pre[i][j] ;
		}
	}
	for ( i = 1 ; i <= n ; i++ ) {
		maxsum[n][i] = pre[n][i] ;
	}
	for ( i = n-1 ; i >= 0 ; i-- ) {
		for ( j = 1 ; j <= i ; j++ ) {
			maxsum[i][j] = max ( maxsum[i+1][j] , maxsum[i+1][j+1] ) + pre[i][j] ;
		}
	}
	cout << maxsum[1][1] << endl ;
	return 0 ;
}

  

  写到这里 , 会发现此程序仍然可以继续优化 , 当然这个优化就是对空间的优化 ,maxsum 其实不需要用一个二维数组存 , 一个一维数组完全可以解决问题 。

 

 

 

代码示例 :

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std ;

int pre[101][101] ;
int n ;
int maxsum[101] ;

int main ( ) {
	int i , j ;
	
	cin >> n ;
	for ( i = 1 ; i <= n ; i++ ) {
		for ( j = 1 ; j <= i ; j++ ) {
			cin >> pre[i][j] ;
		}
	}
	for ( i = 1 ; i <= n ; i++ ) {
		maxsum[i] = pre[n][i] ;
	}
	for ( i = n-1 ; i >= 0 ; i-- ) {
		for ( j = 1 ; j <= i ; j++ ) {
			maxsum[j] = max ( maxsum[j] , maxsum[j+1] ) + pre[i][j] ;
		}
	}
	cout << maxsum[1] << endl ;
	return 0 ;
}

  

 

 

  


posted @ 2017-08-09 19:17  楼主好菜啊  阅读(262)  评论(0编辑  收藏  举报