用递归解决递归形式问题
例题1:表达式运算
描述:
输入为四则运算表达式,仅由数字,+,*,/,()组成,没有空格,要求求其值。加黑色运算符结果都是整数。“/”结果也是整数。
输入样例:
(2+3)*(5+7)+9/3
输出样例:
63
解题思路:
可以对表达式进行递归处理。
表达式:
——————> 项 ————————>
| |
|——|——> + <——|——|
|——> - <——|
项:
——————> 因子 ————————>
| |
|——|——> * <——|——|
|——> / <——|
因子:
———( ———> 表达式 ——— ) —————>
| |
|——————— 整数 ——————————|
代码:
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
int factor_value();//求一个因子的值
int term_value();//求一个项的值
int expression_value();//求一个表达式的值
int main()
{
cout << expression_value() << endl;
return 0;
}
int expression_value()//求一个表达式的值
{
int result = term_value();//求第一项的值
bool more = true;
while(more) {
char op = cin.peek();//看一个字符,不取走
if(op == '+' || op == '-') {
cin.get();//从输入中取走一个字符
int value = term_value();
if(op == '+') result += value;
else result -= value;
}
else more = false;
}
return result;
}
int term_value()//求一个项的值
{
int result = factor_value();//求第一个因子的值
while( true ) {
char op = cin.peek();
if(op == '*' || op == '/') {
cin.get();
int value = factor_value();
if(op == '*')
result *= value;
else result /= value;
}
else
break;//没有项即多项式结束
}
return result;
}
int factor_value()//求一个因子的值
{
int result = 0;
char c = cin.peek();
if(c == '(') {
cin.get();
result = expression_value();
cin.get();
}
else {
while(isdigit(c)) {
result = 10*result + c - '0';//由于读取的是整数,所以以位数形式进行累加,然后转换为数值
cin.get();
c=cin.peek();
}
}
return result;
}
总结:
- 语法问题
1)isdigit函数判断字符型是否是数字
2)cin.peek()和cin.get()的区别
cin.peek()的返回值是一个char型的字符,其返回值是指针指向的当前字符,但它只是观测指针停留在当前位置并不后移
cin.get()用来从指定的输入流中提取一个字符(包括空白字符),函数的返回值就是读入的字符。
在程序中使用cin.get()目的是把已经读取的字符扔掉。 - 理解问题
1)对于不等式的理解。。
例题2:爬楼梯
描述:
树老师爬楼梯,他可以每次走1级或者2级,输入楼梯的级数,求不同的走法。
例如:楼梯一共有三级,他可以每次都走一级,或者第一次走一级,第二次走两级,也可以第一次走两级,第二次走一级,一共3种方法。
输入:
输入包含若干行,每行包含一个正整数N,代表楼梯级数,1<=N<=30输出不同的走法数,每一行输入对应一行。
输出:
不同的走法数,每一行输入对应一行输出
输入样例:
5
8
10
输出样例:
8
34
89
解题思路:
用递归将问题分解为规模更小的子问题。
n级台阶的走法=先走一级后,n-1级台阶的走法 + 先走两级后,n-2级台阶的走法
f(n) = f(n-1) + f(n-2)
边界条件:
n < 0 0
n = 0 1
或
n = 0 1
n = 1 1
或
n < 1 1
n = 2 2
代码:
#include <iostream>
using namespace std;
int N;
int stairs(int n)
{
if(n < 0)
return 0;
if(n == 0)
return 1;
return stairs(n-1) + stairs(n-2);
}
int main()
{
while(cin >> N) {
cout << staris(N) << endl;
}
return 0;
}
总结:
- 理解问题
1)注意递归条件
例题3:放苹果
描述:
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?5,1,1和1,5,1是同一种分法。
输入:
第一行是测试数据的数目t(0 <= t <= 20)。以下每行均包含两个整数M和N,以空格隔开。1 <= M,N <= 10。
输出:
对输入的每组数据M和N,用一行输出相应的k。
输入样例:
1
7 3
输出样例:
8
解题思路:
设m个苹果放在n个盘子里放法总数是f(m,n),则:
n > m时,f(m,n) = f(m,m)
n <= m时,总放法 = 有盘子为空的方法 + 没盘子为空的放法
f(m,n) = f(m,n-1) + f(m-n,n)
边界条件:
1)m=0 一种摆放方式:全空
2)n=0 没有摆放方式
代码:
#include <iostream>
using namespace std;
int f(int m, int n)
{
if(n > m)
return f(m,m);
if(m == 0)
return 1;
if(n == 0)
return 0;
return f(m,n-1) + f(m-n,n);
}
int main()
{
int t, m, n;
cin >> t;
while(t --) {
cin >> m >> n;
cout << f(m,n) << endl;
}
return 0;
}
总结:
例题4:算24
描述:
给出4个小于10的正整数,你可以使用加减乘除4种运算以及括号把这四个数连接起来得到一个表达式。现在的问题是,是否存在一种方式使得得到的表达式的结果等于24。
这里加减乘除以及括号的运算结果和运算的优先级和平常定义一致。
比如,对于5,5,5,1,我们知道5*(5-1)+(5-1)=24。
输入:
输入数据包括多行,每行给出一组测试数据,包括4个小于10的正整数。最后一组测试数据种包括4个0,表示输入的的结束,这组数据不用处理。
输出:
对于每一组测试数据,输出一行,如果可以得到24,输出YES,否则,输出NO。
输入样例:
5 5 5 1
1 1 4 2
0 0 0 0
输出样例:
YES
NO
解题思路:
n个数算24,必有两个数要先算。这两个数算的结果,和剩余n-2个数,就构成了n-1个数求24的问题
枚举先算的两个数,以及这两个数的运算方式。
边界条件:一个数算24
注意:浮点数比较是否相等,不能用==。
代码:
#include <iostream>
#include <cmath>
using namespace std;
double a[5];//存放初始数值
#define EPS 1e-6
bool isZero(double x)
{ //用于判断两数是否相等
return fabs(x) <= EPS; //cmath中的库函数
}
bool count24(double a[],int n)
{ //用数组a里的n个数,计算24
if(n==1) {
if(isZero(a[0] - 24))
return true;
else
return false;
}
double b[5]; //存放中间结果
for(int i = 0; i< n - 1; ++i)
for(int j = i + 1; j < n; ++j) { //枚举两个数的组合
int m=0; //还剩下m个数,m=n-2
for(int k = 0; k < n ; ++k)
if(k != i && k != j)
b[m++] = a[k]; //把其余数放入b
b[m] = a[i] + a[j]; //加法;这里 m=m+1
if(count24(b,m+1))// m+1即剩余的m个数和新产生的一个数
return true;
b[m] = a[i] - a[j]; //减法1
if(count24(b,m+1))
return true;
b[m] = a[j] - a[i]; //减法2
if(count24(b,m+1))
return true;
b[m] = a[i] * a[j]; //乘法
if(count24(b,m+1))
return true;
if(!isZero(a[j])) { //除法1
b[m] = a[i] / a[j];
if(count24(b,m+1))
return true;
}
if(!isZero(a[i])) { //除法2
b[m] = a[j] / a[i];
if(count24(b,m+1))
return true;
}
}
return false;
}
int main()
{
while(true){
for(int i = 0; i < 4;i++)
cin >> a[i];
if(a[0] == 0)
break;
if(count24(a,4))
cout << "YES" << endl;
else
cout << "NO" << endl;
}
return 0;
}
总结:
- 语法问题
1)使用函数count24(double a[],int n), a[]代表函数名,引用应该是if(count(a,4))。 - 理解问题
1)注意计算过程中的组合逻辑,比如减法需要使用两次,除法必须判断被除数不为0。
2)(count24(b,m+1)中,对于中间变量b[]的运用,以及m+1的理解。
3)b[m++]=a[k]中m++在下一行中以m+1的值呈现。
课后习题1:
描述:
将正整数n表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。
正整数n的这种表示称为正整数n的划分。正整数n的不同的划分个数称为正整数n的划分数。
输入:
标准的输入包含若干组测试数据。每组测试数据是一个整数N(0 < N <= 50)。
输出:
对于每组测试数据,输出N的划分数。
输入样例:
5
输出样例:
7
提示:
5, 4+1, 3+2, 3+1+1, 2+2+1, 2+1+1+1, 1+1+1+1+1
解题思路:
设m个1用n个1加和的情况是f(m,n),则:
m < n时,f(m,n) = f(m,m)
m >= n时,总放法 = 有1不用(重复使用) + 全部1都用
f(m,n) = f(m,n-1) + f(m-n,n)
边界条件:
1)m = 0 一种情况:0本身
2)n = 0 错误,返回0
代码:
#include <iostream>
using namespace std;
int ways(int n,int i)
{
if( n == 0)
return 1;
if( i == 0)
return 0;
if( i <= n)
return ways(n-i,i) + ways(n,i-1); //用i和不用i的情况。i可以重复使用
else
return ways(n,n);
}
int main()
{
int n;
while(cin >> n)
cout << ways(n,n) << endl;
}
/*老师提供的代码里,当m < n时, return ways(n,i-1),而放苹果代码里return ways(n,n)。由于该变动不影响最终结果,我打印了中间过程进行进一步比较,可以看出,ways(n,n)使得过程更加简洁,所以对代码进行了修改。
输入 n = 5
return ways(n,i-1)时:
ways(0,5)ways(5,4)
ways(1,4)ways(5,3)
ways(1,3)
ways(1,2)
ways(1,1)
ways(0,1)ways(1,0)
ways(2,3)ways(5,2)
ways(2,2)
ways(0,2)ways(2,1)
ways(1,1)ways(2,0)
ways(0,1)ways(1,0)
ways(3,2)ways(5,1)
ways(1,2)ways(3,1)
ways(1,1)
ways(0,1)ways(1,0)
ways(2,1)ways(3,0)
ways(1,1)ways(2,0)
ways(0,1)ways(1,0)
ways(4,1)ways(5,0)
ways(3,1)ways(4,0)
ways(2,1)ways(3,0)
ways(1,1)ways(2,0)
ways(0,1)ways(1,0)
return ways(n,n)时:
ways(0,5)ways(5,4)
ways(1,4)ways(5,3)
ways(1,1)
ways(0,1)ways(1,0)
ways(2,3)ways(5,2)
ways(2,2)
ways(0,2)ways(2,1)
ways(1,1)ways(2,0)
ways(0,1)ways(1,0)
ways(3,2)ways(5,1)
ways(1,2)ways(3,1)
ways(1,1)
ways(0,1)ways(1,0)
ways(2,1)ways(3,0)
ways(1,1)ways(2,0)
ways(0,1)ways(1,0)
ways(4,1)ways(5,0)
ways(3,1)ways(4,0)
ways(2,1)ways(3,0)
ways(1,1)ways(2,0)
ways(0,1)ways(1,0)
*/
总结:
- 理解问题
1)注意例题的积累和反复练习。
课后习题2:Boolean Expressions
描述:
The objective of the program you are going to produce is to evaluate boolean expressions as the one shown next:
Expression: ( V | V ) & F & ( F | V )
where V is for True, and F is for False. The expressions may include the following operators: ! for not , & for and, | for or , the use of parenthesis for operations grouping is also allowed.
To perform the evaluation of an expression, it will be considered the priority of the operators, the not having the highest, and the or the lowest. The program must yield V or F , as the result for each expression in the input file.
输入:
The expressions are of a variable length, although will never exceed 100 symbols. Symbols may be separated by any number of spaces or no spaces at all, therefore, the total length of an expression, as a number of characters, is unknown.
The number of expressions in the input file is variable and will never be greater than 20. Each expression is presented in a new line, as shown below.
输出:
For each test expression, print "Expression " followed by its sequence number, ": ", and the resulting value of the corresponding test expression. Separate the output for consecutive test expressions with a new line.
Use the same format as that shown in the sample output shown below.
输入样例:
( V | V ) & F & ( F| V)
!V | V & V & !F & (F | V ) & (!F | F | !V & V)
( F & F | V | ! V & ! F & ! ( F | F & V ) )
输出样例:
Expression 1: F
Expression 2: V
Expression 3: V
解题思路:
类似于四则运算,搞清楚符号优先级。
代码:
#include <iostream>
#include <cstdio>
using namespace std;
char wholeExp[200];
int ptr = 0;
bool exp();
bool factor();
bool item();
bool notExp();
bool exp()
{ //或项
bool result = item();
while(wholeExp[ptr] == '|' ) {
++ptr;
result |= item();
}
return result;
}
bool item()
{ //与项
bool result = factor();
while(wholeExp[ptr] == '&') {
++ptr;
result &= factor();
}
return result;
}
bool notExp()
{ //取反
ptr++;
bool result;
switch(wholeExp[ptr]) {
case 'F':
++ptr;
return true;
case 'V':
++ptr;
return false;
case '(':
++ptr;
result = exp();
++ptr; //skip ')'
return !result;
case '!':
result = notExp();
return !result; //注意返回值应为!result
}
}
bool factor()
{ //求一个因子的值
bool result;
switch( wholeExp[ptr]) {
case 'F':
++ptr;
return false;
case 'V':
++ptr;
return true;
case '(':
++ptr;
result = exp();
++ptr;
return result;
case '!':
result = notExp();
return result;
}
}
int main()
{
char c;
int i = 0;
int t = 1;
int n = EOF + 1;
while(n != EOF) { //无限循环
n = scanf("%c",&c);
if( n == EOF || c == '\n') { //一行输入结束
wholeExp[i] = 0;
//cout << "wholeExp[" << i << "]=" << 0 << endl;
if( i > 0) { //处理输入行
ptr = 0; //注意不要漏掉
bool r = exp();
if (r)
printf("Expression %d: V\n",t++);
else
printf("Expression %d: F\n",t++);
}
i = 0;// 开始新的一行
}
else if( c != ' ') //开始输入
wholeExp[i++] = c;
//cout << "wholeExp[" << i - 1 << "]=" << c << endl;
}
}
/*
输入 ( V | V ) & F & ( F| V) 时:
wholeExp[0]=(
wholeExp[1]=V
wholeExp[2]=|
wholeExp[3]=V
wholeExp[4]=)
wholeExp[5]=&
wholeExp[6]=F
wholeExp[7]=&
wholeExp[8]=(
wholeExp[9]=F
wholeExp[10]=|
wholeExp[11]=V
wholeExp[12]=)
wholeExp[13]=0
Expression 1: F
wholeExp[0]=0
*/
总结:
- 语法问题
1)break是直接退出switch语句;return是退出该函数,即switch语句块后面的语句也不执行。如果把return写在main函数里, 即程序执行完毕。如果是别的函数, 就返回调用函数那个地方。 - 理解问题
1)注意运算符顺序
2)细节问题不要漏掉