620 likes | 789 Views
第 6 章 C++ 过程函数. 第 6 次见面!. 学习新内容了,准备好了吗?. 记住 :. 学习是快乐的~. 第 6 章 C++ 中的函数. 递归函数 基于递归的算法. 递归用途. 递归程序设计:将一个大问题简化为 同样形式 的较小问题。 在一个递归求解中,分解的子问题与最初的问题具有一样的形式 作为处理问题的工具,递归技术是一种非常有力的工具。利用递归不但可以使得书写复杂度降低,而且使程序看上去更加美观 递归调用:在一个函数中直接或间接地调用函数本身. 递归条件. 必须有递归终止的条件 函数有与递归终止条件相关的参数
E N D
第6章 C++过程函数 第6次见面!
学习新内容了,准备好了吗? 记住: 学习是快乐的~
第6章 C++中的函数 • 递归函数 • 基于递归的算法
递归用途 • 递归程序设计:将一个大问题简化为同样形式的较小问题。 • 在一个递归求解中,分解的子问题与最初的问题具有一样的形式 • 作为处理问题的工具,递归技术是一种非常有力的工具。利用递归不但可以使得书写复杂度降低,而且使程序看上去更加美观 • 递归调用:在一个函数中直接或间接地调用函数本身
递归条件 • 必须有递归终止的条件 • 函数有与递归终止条件相关的参数 • 在递归过程中,决定终止条件的参数有规略地递增或递减
递归的标准模式 • 有可对函数的入口进行测试的基本情况 if (条件) return (不需要递归的简单答案); else return (递归调用同一函数); 基本情况
n!=1*2*3*4*…*(n-1)*n (n-1)! 典型的递归函数—阶乘函数 递归形式: 递归终止条件 long p(int n) {if (n == 0) return 1; else return n * p(n-1); }
简单应用——求n的k次幂 int RaiseIntToPower(int n, int k) { if (k == 0) { return(1); } else { return( n * RaiseIntToPower( n, k - 1)); } }
0 1 2 3 4 5 6 0 1 1 2 3 5 8 Fibonacci函数 int f(int n) {if (n==0) return 0; elseif (n==1) return 1; else return (f(n-1)+f(n-2)); }
理解递归 • 问题:求解n! • 可以用循环的方法,即从1开始,乘2,再乘3…..一直乘到n。这种方法容易理解,也容易实现 • 由于n! = n×(n-1)!数学里定义0!=1,从而n!可以用下面的递归公式表示:
递归函数设计 int p(int n) { if(n= = 0) return (1); else return (n * p(n-1)); }
递归执行的过程 求p(4) 递归过程 回溯
递归与迭代的选择 • 对于大多数常用的递归都有简单、等价的迭代程序。究竟使用哪一种,凭你的经验选择。 • 迭代程序复杂,但效率高。 • 递归程序逻辑清晰,但往往效率较低。
Fibonacci函数的递归实现 int f(int n) {if (n==0) return 0; elseif (n==1) return 1; else return (f(n-1)+f(n-2)); } • 实现效率分析:消费的时间是灾难性的!!!
Fibonacci函数的迭代实现 int f(int n) { int i, fn, fn_1 = 0, fn_2 = 1; if (n == 0) return 0; if (n == 1) return 1; for ( i = 2; i<=n; ++i) { fn = fn_1 + fn_2; fn_2 = fn_1; fn_1 = fn; } return fn; } 消耗的时间:执行n次加法和3n次赋值!!!
计算阶乘N! • f(n)=n!可以定义为:
代码: • #include<stdio.h> • int f(int n){ • return n == 0 ? 1 : f(n-1)*n; • } • int main(){ • printf("%d\n", f(3)); • return 0; • }
递归要调用栈来进行! 皇帝(拥有main函数的栈):大臣,你给我算一 下f(3). 大臣(拥有f(3)的栈):知府,你给我算一下f(2). 知府(拥有f(2)的栈):县令,你给我算一下f(1). 县令(拥有f(1)的栈):师爷,你帮我算一下f(0). 师爷(拥有f(0)的栈):回老爷,f(0)=1.
县令:(心算)回知府大人,f(1)=1. • 知府: (心算)回大人,f(2)=2. • 大臣: (心算) 3*f(2)=6,回皇上, f(3)=6
运行 • 计算f(3)=6; • 计算f(100000000),没有输出,溢出也应该有数啊! • 是段错误! • 段:是指二进制文件内的区域,某种特定类型的信息被保存在里面。
追忆2008年亚洲哈尔滨赛区 • 杨成虎同学的深搜算法就是递归写的,就是不过,因为该算法在递归调用5000次就段错误了,后来改成广搜算法(非递归)的就AC 了,时间多了1个小时,离银牌只差2名,血的教训! • 我们要牢记!
A B C 递归过程—Hanoi塔问题 目标: 将A上的盘子全部移到B上 规则: 每次只能移动一个盘子 不允许大盘子放在小盘子上
n=4(最开始的情况) n=4(完成情况) Hannoi塔
第1步:从开始的杆到辅助杆(src到aux) 第2步:从开始杆到目的杆(src到dst) Hannoi塔
第3步:从辅助杆到目的杆(aux到dst) 第4步:从开始的杆到辅助杆(src到aux) Hannoi塔
第5步:从目的杆到开始杆( dst 到src) 第6步:从目的杆到辅助杆(dst到aux) Hannoi塔
第7步:从开始杆到目的杆( src 到dst ) 第8步:从开始杆到目的杆(src到dst) Hannoi塔
第9步:从辅助杆到目的杆( aux 到dst ) 第10步:从辅助杆到开始的杆(aux到src ) Hannoi塔
第11步:从目的杆到开始杆( dst 到src) 第12步:从辅助杆到目的杆( aux 到dst ) Hannoi塔
第13步:从开始的杆到辅助杆(src到aux) 第14步:从开始杆到目的杆(src到dst) Hannoi塔
第15步:从辅助杆到目的杆( aux 到dst ) Hannoi塔
解题思路 • 最简单的情况,只有一个盘子:将盘子直接从A移到B • 大于一个盘子的情况: • 将除了最下面一个盘子外的所有盘子从A移到C • 将最下面的盘子从A移到B • 将C上的盘子移回B
Hanoi 塔函数 void Hanoi(int n, char start, char finish, char temp) { if (n==1) cout << start << "->" << finish << '\t'; else { Hanoi(n-1, start, temp, finish); cout << start << "->" << finish << '\t'; Hanoi(n-1, temp, finish, start); } }
红与黑 hdu 1312 • 有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。
输入数据 • 包括多个数据集合。每个数据集合的第一行是两个整数W 和H,分别表示x 方向 • 和y 方向瓷砖的数量。W 和H 都不超过20。在接下来的H 行中,每行包括W 个字符。 • 每个字符表示一块瓷砖的颜色,规则如下:
1)‘.’:黑色的瓷砖; • 2)‘#’:白色的瓷砖; • 3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。 • 当在一行中读入的是两个零时,表示输入结束。
输出要求 • 对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。
输入样例 • 6 9 • ....#. • .....# • ...... • ...... • ...... • ...... • ...... • #@...# • .#..#. • 0 0 结果:45
解题思路 • 这个题目可以描述成给定一点,计算它所在的连通区域的面积。需要考虑的问题包括矩阵的大小,以及从某一点出发向上下左右行走时,可能遇到的三种情况:出了矩阵边界、遇到’.’、遇到’#’。 • 设f(x, y)为从点(x,y)出发能够走过的黑瓷砖总数,则 • f(x, y) = 1 + f(x - 1, y) + f(x + 1, y) + f(x, y - 1) + f(x, y + 1) • 这里需要注意,凡是走过的瓷砖不能够被重复走过。可以通过每走过一块瓷砖就将它作标记的方法保证不重复计算任何瓷砖。
Main()函数 • int w,h; //定义公有变量 长和宽 • char z[21][21];
int main(int argc, char *argv[]) • { • while(cin>>w>>h) • { • if (w==0&&h==0) break; • for(int i=1;i<=h;i++) • for(int j=1;j<=w;j++) • cin>>z[i][j]; • for(int i=1;i<=h;i++) • for(int j=1;j<=w;j++) • if (z[i][j]=='@') • cout<<f(i,j)<<endl; • } • //system("PAUSE"); • return EXIT_SUCCESS; • }
再看递归部分: • int f(int i,int j) • { • if (i<1||i>h||j<1||j>w) //处理边界 • return 0; • if (z[i][j]!='#') • { • z[i][j]=‘#’; //这句话是啥意思? • return 1+f(i,j-1)+f(i,j+1)+f(i-1,j)+f(i+1,j); • } • else • return 0; • }
如何提高递归的效率 • 这是我们解决实际问题的重点 • 避免数据重复就可以了 • 掌握递归问题的规模 • 利用动态规划的思想
动态规划(DP) • 这个猛! • 分工明确,不要重复 • 劳动
F(5) F(4) F(3) F(3) F(1) F(2) F(2) F(2) F(1) F(1) F(1) F(0) F(0) F(1) F(0) 例:计算斐波那契数: n=5时分治法计算斐波那契数的过程。
0 1 2 3 4 5 6 7 8 9 0 1 1 2 3 5 8 13 21 34 注意到,计算F(n)是以计算它的两个重叠子问题F(n-1)和F(n-2)的形式来表达的,所以,可以设计一张表填入n+1个F(n)的值。 动态规划法求解斐波那契数F(9)的填表过程 :
用动态规划法求解的问题具有特征: • 能够分解为相互重叠的若干子问题; • 满足最优性原理(也称最优子结构性质):该问题的最优解中也包含着其子问题的最优解。 (用反证法)分析问题是否满足最优性原理: • 先假设由问题的最优解导出的子问题的解不是最优的; • 然后再证明在这个假设下可构造出比原问题最优解更好的解,从而导致矛盾。
动态规划法设计算法一般分成三个阶段: • (1)分段:将原问题分解为若干个相互重叠的子问题; • (2)分析:分析问题是否满足最优性原理,找出动态规划函数的递推式; • (3)求解:利用递推式自底向上计算,实现动态规划过程。 • 动态规划法利用问题的最优性原理,以自底向上的方式从子问题的最优解逐步构造出整个问题的最优解。
例1斐波那契数列 nefu 85 • 计算斐波那契数列的值!该数列为1 1 2 3 5 8 13 21 ......... • 有多组数据,每组1行,用N表示,1 <= N <= 50。 • 输出Fibonacci(N)的值!
代码: • int n; • long long feibo[51]; • feibo[1]=1; • feibo[2]=1; • for(int i=3;i<=50;i++) • feibo[i]=feibo[i-1]+feibo[i-2];//打表 • while(cin>>n) • cout<<feibo[n]<<endl;