第 6 章 C++ 过程函数

62
1 第 6 第 C++ 第第第第 第 6 第第第

description

第 6 章 C++ 过程函数. 第 6 次见面!. 学习新内容了,准备好了吗?. 记住 :. 学习是快乐的~. 第 6 章 C++ 中的函数. 递归函数 基于递归的算法. 递归用途. 递归程序设计:将一个大问题简化为 同样形式 的较小问题。 在一个递归求解中,分解的子问题与最初的问题具有一样的形式 作为处理问题的工具,递归技术是一种非常有力的工具。利用递归不但可以使得书写复杂度降低,而且使程序看上去更加美观 递归调用:在一个函数中直接或间接地调用函数本身. 递归条件. 必须有递归终止的条件 函数有与递归终止条件相关的参数 - PowerPoint PPT Presentation

Transcript of 第 6 章 C++ 过程函数

1

第 6 章 C++ 过程函数

第 6 次见面!

2

学习新内容了,准备好了吗?

记住 :

第 6 章 C++ 中的函数

递归函数 基于递归的算法

递归用途• 递归程序设计:将一个大问题简化为同样形

式的较小问题。–在一个递归求解中,分解的子问题与最初的问题

具有一样的形式 –作为处理问题的工具,递归技术是一种非常有力

的工具。利用递归不但可以使得书写复杂度降低,而且使程序看上去更加美观

• 递归调用:在一个函数中直接或间接地调用函数本身

递归条件

• 必须有递归终止的条件

• 函数有与递归终止条件相关的参数

• 在递归过程中,决定终止条件的参数有规略地递增或递减

递归的标准模式

• 有可对函数的入口进行测试的基本情况

if ( 条件 )

return ( 不需要递归的简单答案 );

else

return ( 递归调用同一函数 ) ;

基本情况

典型的递归函数—阶乘函数n!=1*2*3*4*…*(n-1)*n

(n-1)!

递归形式:

其他)!1(*

01!

nn

nn

递归终止条件

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));

}

}

Fibonacci 函数0

0

1

1

2

1

3

2

4

3

5

5

6

8

其他)2()1(

11

00

)(

nFnF

n

n

nF

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! 可以用下面的递归公式表示:

)1()!1(

)1,0(1{!

nnn

nn

递归函数设计

int p(int n)

{ if(n= = 0)

return (1);

else

return (n * p(n-1));

}

递归执行的过程

求 p(4)

1)0(

*1)1(

*2

)2(

*3

)3(

*4

)4(

pp

pp

p

递归过程

回溯

递归与迭代的选择

• 对于大多数常用的递归都有简单、等价的迭代程序。究竟使用哪一种,凭你的经验选择。

• 迭代程序复杂,但效率高。

• 递归程序逻辑清晰,但往往效率较低。

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

次赋值!!!

16

计算阶乘 N !• f(n)=n! 可以定义为

)1()1()(

1)0(

nnnfnf

f

17

代码:• #include<stdio.h>• int f(int n){• return n == 0 ? 1 : f(n-1)*n;• }• int main(){• printf("%d\n", f(3));• return 0;• }

18

递归要调用栈来进行!

皇帝(拥有 main 函数的栈):大臣,你给我算一 下 f(3).

大臣(拥有 f(3) 的栈 ): 知府,你给我算一下 f(2).

知府(拥有 f(2) 的栈):县令,你给我算一下 f(1).

县令(拥有 f(1) 的栈):师爷,你帮我算一下 f(0).

师爷(拥有 f(0) 的栈):回老爷, f(0)=1.

19

• 县令:(心算)回知府大人, f(1)=1.

• 知府: (心算)回大人, f(2)=2.

• 大臣: (心算) 3*f(2)=6 ,回皇上, f(3)=6

20

运行

• 计算 f(3)=6;

• 计算 f(100000000), 没有输出,溢出也应该有数啊!

• 是段错误!• 段:是指二进制文件内的区域,某种特

定类型的信息被保存在里面。

21

追忆 2008 年亚洲哈尔滨赛区• 杨成虎同学的深搜算法就是递归写的,

就是不过,因为该算法在递归调用 5000次就段错误了,后来改成广搜算法(非递归)的就 AC 了,时间多了 1 个小时,离银牌只差 2 名,血的教训!

• 我们要牢记!

递归过程— Hanoi 塔问题

目标:将 A 上的盘子全部移到 B 上

规则:每次只能移动一个盘子

不允许大盘子放在小盘子上

A B C

Hannoi 塔

• 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 )

解题思路

• 最简单的情况,只有一个盘子:将盘子直接从 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);

}

}

34

红与黑 hdu 1312

• 有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

35

输入数据• 包括多个数据集合。每个数据集合的第

一行是两个整数 W 和 H ,分别表示 x 方向

• 和 y 方向瓷砖的数量。 W 和 H 都不超过 20 。在接下来的 H 行中,每行包括W 个字符。

• 每个字符表示一块瓷砖的颜色,规则如下:

36

• 1 )‘ .’ :黑色的瓷砖;• 2 )‘ #’ :白色的瓷砖;• 3 )‘ @’ :黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。

• 当在一行中读入的是两个零时,表示输入结束。

37

输出要求• 对每个数据集合,分别输出一行,显示

你从初始位置出发能到达的瓷砖数 ( 记数时包括初始位置的瓷砖 ) 。

38

输入样例• 6 9• ....#.• .....#• ......• ......• ......• ......• ......• #@...#• .#..#.• 0 0

39

解题思路• 这个题目可以描述成给定一点,计算它所在的连通区

域的面积。需要考虑的问题包括矩阵的大小,以及从某一点出发向上下左右行走时,可能遇到的三种情况:出了矩阵边界、遇到’ .’ 、遇到’ #’ 。

• 设 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)• 这里需要注意,凡是走过的瓷砖不能够被重复走过。

可以通过每走过一块瓷砖就将它作标记的方法保证不重复计算任何瓷砖。

40

Main() 函数• int w,h; // 定义公有变量 长和宽• char z[21][21];

41

• 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;• }

42

再看递归部分:• 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; • }

如何提高递归的效率• 这是我们解决实际问题的重点• 避免数据重复就可以了• 掌握递归问题的规模• 利用动态规划的思想

43

动态规划( DP)

• 这个猛!• 分工明确,不要重复• 劳动

44

23/4/21 45

n=5 时分治法计算斐波那契数的过程。 F(5)

F(4) F(3)

F(3) F(2) F(2) F(1)

F(2) F(1)

F(1) F(0)

F(1) F(0) F(1) F(0)

例:计算斐波那契数:

2)2()1(

11

00

)(

nnFnF

n

n

nF

23/4/21 46

0 1 2 3 4 5 6 7 8 90 1 1 2 3 5 8 13 21 34

动态规划法求解斐波那契数 F(9) 的填表过程 :

注意到,计算 F(n) 是以计算它的两个重叠子问题 F(n-1)和 F(n-2) 的形式来表达的,所以,可以设计一张表填入 n+1

个 F(n) 的值。

23/4/21 47

用动态规划法求解的问题具有特征: 能够分解为相互重叠的若干子问题;

满足最优性原理(也称最优子结构性质):该问题的最优解中也包含着其子问题的最优解。

(用反证法)分析问题是否满足最优性原理:1. 先假设由问题的最优解导出的子问题的解不是最优的 ;2. 然后再证明在这个假设下可构造出比原问题最优解更好

的解,从而导致矛盾。

23/4/21 48

动态规划法设计算法一般分成三个阶段:( 1)分段:将原问题分解为若干个相互重叠的

子问题;( 2)分析:分析问题是否满足最优性原理,找

出动态规划函数的递推式;( 3)求解:利用递推式自底向上计算,实现动

态规划过程。

动态规划法利用问题的最优性原理,以自底向上的方式从子问题的最优解逐步构造出整个问题

的最优解。

23/4/21 49

例 1 斐波那契数列 nefu 85

• 计算斐波那契数列的值!该数列为 1 1 2 3 5 8 13 21 .........

• 有多组数据,每组 1 行,用 N 表示, 1 <= N <= 50 。

• 输出 Fibonacci(N) 的值 !

23/4/21 50

代码:• 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;

23/4/21 51

用递归能做吗?• long long data[51];• long long fblq(int n)• {• if (n==1||n==2) return 1;• else• {• if (data[n]==0)• data[n]=fblq(n-1)+fblq(n-2); // 看看这个! 只算 1 次!• return data[n]; • • } • • • }

23/4/21 52

现在同学们在纸上计算下题:• 计算 N!

• input

• 有多组数据,每组一行,用 N 表示, 0 < = N <= 20;

• output

• 输出 N !

23/4/21 53

递归的代码:• #include <cstdlib>• #include <iostream>• using namespace std;• long long data[21];• long long jc(int n)• {• if (n==0||n==1) return 1;• if (data[n]==0)• data[n]=n*jc(n-1);• return data[n]; • • }

int main(int argc, char *argv[]) {

int n; memset(data,0,sizeof(data));

while(cin>>n) cout<<jc(n)<<endl; system("PAUSE");

return EXIT_SUCCESS; }

23/4/21 54

递推的代码:• #include <cstdlib>• #include <iostream>• using namespace std;• int main(int argc, char *argv[])• {• int n; long long data[51];• memset(data,0,sizeof(data));• data[0]=1;data[1]=1;• for(int i=2;i<=20;i++)• data[i]=i*data[i-1];• while(cin>>n)• cout<<data[n]<<endl;• system("PAUSE");• return EXIT_SUCCESS;• }

23/4/21 55

Nefu20 穿过街道

• 一个城市的街道布局如下 : 从最左下方走到最右上方 , 每次只能往上或往右走 , 一共有多少种走法 ?

23/4/21 56

• input

• 输入很多行行数 , 每行 1 个数字代表 n 的值 , 当 n=0 时结束 (2<=n<=15)

• output

• 输出对应每行 n值的走法 .

23/4/21 57

• sample_input• 1• 2• 10 • 5 • 0• sample_output• 2• 6• 184756 • 252

23/4/21 58

递归的代码:• int f[100][100];• int getf(int x,int y)• {• if (f[x][y]!=-1) return f[x][y];• int result;• if (x==0||y==0)• result=1;• else• result=getf(x-1,y)+getf(x,y-1);• f[x][y]=result;• return result;• }

23/4/21 59

Main()• int main(int argc, char *argv[])• {• int i,j,n;• memset(f,-1,sizeof(f));• while(cin>>n)• {• if (n==0) break; • cout<<getf(n,n)<<endl; • }• system("PAUSE");• return EXIT_SUCCESS;• }

23/4/21 60

其实这题递推的代码更短:• int n;• long long data[21][21];• for(int i=0;i<=20;i++)• {• data[i][0]=1;// 处理边界• data[0][i]=1;// 处理边界• }• for(int j=1;j<=20;j++)• for(int k=1;k<=20;k++)• data[j][k]=data[j-1][k]+data[j][k-1];// 公式• while(cin>>n&&n)• cout<<data[n][n]<<endl;

61

Welcome to HDOJ

Thank You ~

• 课后一定要练习之!

62