一:递归
基本概念:一个函数调用它自身,就是递归
如求n的阶乘的递归:
#include<iostream>
using namespace std;
int factorial(int n) {
if (n == 1)
return 1;
else
return n * factorial(n - 1);
}
int main() {
int n;
cin >> n;
factorial(n);
return 0;
}
这里在求n的阶乘时,我们就用到递归的概念。假如n=3,那么函数首先调用factorial(3),将函数调用压入栈,继续调用factorial(2),继续压入栈,调用factoral(1),factoral(1)此时处于栈顶位置,因为n==1时,函数返回1,所以执行完factoral(1)后,后入栈的函数调用就会先被弹出来,随后执行factorial(2),继续弹出,直到factorial(3)被执行,全部函数调用都被弹出,函数执行完毕,所以递归的过程就是不断压栈出栈的过程。
递归的作用:
- 替代多重循环
- 解决本来就是用递归形式定义的问题
- 将问题分解为更小的子问题求解
可以看到,递归的作用有点类似与循环,但递归可能从某方面来说,能使得解决方案更清晰,其实并没有性能上的优势,实际上,在有些情况下,使用循环的性能更好。总而言之,借用某个大牛的话:使用循环可能会使得你的程序性能更高,但使用递归,可能会然后你的程序看起来更容易理解。
二:全排列问题
基本思路:该题就是一个排列组合问题,主要利用递归来解决,因为字符的个数不大于6个。因此我们可以用一个字符数组来存贮不同组合下的变化情况,另外,为了输出不同的排列,还需要设置一个访问标志位,具体程序如下所示:
要注意的是,递归的条件需要包含两个:基线条件和递归条件。递归条件是能让你的程序使得问题能够一步步的被分解,直到分解成最小规模求解问题,基线条件就是为了防止程序无限分解下去,即陷入死循环,所以这个条件也可以称之为边界条件。
#include<iostream>
#include<string>
#include<cstdio>
using namespace std;
string s1; //输入字符串
char s2[6]; //用来存放不断调整的字符组合
int visit[6] = {0}; //判断字符串某一位是否被访问的标志位
int len; //字符串的长度
void out_result(int steps) {
if (steps == len) {
cout << s2 << endl; //输出某一种情况下的字符组合
return;
}
for (int i = 0; i < len; i++) {
if (visit[i] == 0) {
visit[i] = 1; //避免下次递归在对应循环中执行判断
s2[steps] = s1[i];
out_result(steps + 1);
visit[i] = 0;
}
}
}
int main() {
cin >> s1;
len = s1.size();
out_result(0);
system("pause");
return 0;
}
执行程序,输入测试样本,打印结果如下:
三:2的幂次方问题
基本思路:从体重我们可以看到,当幂的大小小于4的时候,其实可以当作一种特殊情况,即递归的边界条件,有了边界条件,就可以对程序进行递归。
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int power_func(int x) {
if (x > 4) {
int max_power = 1;
while (pow(2, max_power) <= x)
max_power++; //得到大于x的2的最大幂次数
cout << "2(";
power_func(max_power - 1); //递归小于x的2的最大幂次数
cout << ')';
if (x != pow(2,max_power - 1)) {
cout << '+'; //如果2的幂次方不等于x,就输出一个加号
}
power_func(x - pow(2,max_power - 1)); //继续递归x-第一轮小于x的2的最大幂次方
}
else {
switch (x)
{
case 0:return 0;
case 1:cout << "2(0)"; break;
case 2:cout << "2"; break;
case 3:cout << "2+2(0)"; break;
case 4:cout << "2(2)"; break;
}
}
}
int main() {
int x;
cin >> x;
power_func(x);
//system("pause");
return 0;
}
输入测试数据,得到结果如下所示:
四:布尔表达式
基本思路:这一题和四则运算表达式的思路相似,同样是将表达式分为项和因子来进行递归。
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
using namespace std;
char s[10000] = { 0 };
bool expression_value();
bool term_value();
bool factor_value();
int my_cent;
bool term_value() { //项表达式
bool result;
char op = s[my_cent];
if (op == '!') {
my_cent++;
result = !factor_value();
}
else {
result = factor_value();
}
return result;
}
bool factor_value() { //因子表达式
bool result;
char o = s[my_cent];
if (o == '(') {
my_cent++;
result = expression_value(); //如果字符为括号,就继续递归括号内的表达式
my_cent++;
}
else if (o == 'V') {
result = true;
my_cent++;
}
else if (o == 'F') {
result = false;
my_cent++;
}
else if (o == '!') {
result = term_value();
}
return result;
}
bool expression_value() {
bool result = term_value();
bool more = true;
while (more) {
char op = s[my_cent]; //观察第一个字符,不取走
if (op == '&' || op == '|') {
my_cent++; //取走第一个字符
bool value = term_value();
if (op == '&')
result = result & value;
else
result = result | value;
}
else {
more = false;
}
}
return result;
}
int main(){
int k = 0;
while (cin.getline(s,10000)) {
char t[10000] = { 0 };
int len = strlen(s);
int n = 0;
for (int i = 0; i < len; i++) {
if (s[i] != ' ') {
t[n++] = s[i];
}
}
//cout << k;
len = strlen(t);
for (int i = 0; i < len; i++) {
s[i] = t[i];
}
s[len] = '\0'; //到这一步,输入字符中的空格被去除
cout << "Expression " << ++k << ": " << (expression_value() ? 'V' : 'F') << endl;
my_cent = 0;
memset(s, 0, 10000);
}
system("pause");
return 0;
}
五:简单的整数划分问题
基本思路:如果{m1,m2,...,mi}中的最大值不超过m,即max(m1,m2,...,mi)<=m,则称它属于n的一个m划分。这里我们记n的m划分的个数为f(n,m);例如但n=4时,他有5个划分,{4},{3,1},{2,2},{2,1,1},{1,1,1,1};注意4=1+3 和 4=3+1被认为是同一个划分。该问题是求出n的所有划分个数,即f(n, n)。下面我们考虑求f(n,m)的方法;
(1)当n=1时,不论m的值为多少(m>0),只有一种划分即{1};
(2)当m=1时,不论n的值为多少,只有一种划分即n个1,{1,1,1,...,1};
(3)当n=m时,根据划分中是否包含n,可以分为两种情况:
(a)划分中包含n的情况,只有一个即{n};
(b)划分中不包含n的情况,这时划分中最大的数字也一定比n小,即n的所有(n-1)划分。
因此 f(n,n) =1 + f(n,n-1);
(4)当n<m时,由于划分中不可能出现负数,因此就相当于f(n,n);
(5)但n>m时,根据划分中是否包含最大值m,可以分为两种情况:
(a)划分中包含m的情况,即{m, {x1,x2,...xi}}, 其中{x1,x2,... xi} 的和为n-m,因此这情况下
为f(n-m,m)
(b)划分中不包含m的情况,则划分中所有值都比m小,即n的(m-1)划分,个数为f(n,m-1);
因此 f(n, m) = f(n-m, m)+f(n,m-1);
#include<iostream>
using namespace std;
int equationCount(int n, int m)
{
if (n == 1 || m == 1)
return 1;
else if (n < m)
return equationCount(n, n);
else if (n == m)
return 1 + equationCount(n, n - 1);
else
return equationCount(n, m - 1) + equationCount(n - m, m);
}
int main()
{
int n;
while (scanf_s("%d", &n) != EOF && (n >= 1 && n <= 120))
{
printf("%d\n", equationCount(n, n));
}
return 0;
}
小结:
递归优点:
1. 简洁
2.在树的前序,中序,后序遍历算法中,递归的实现明显要比循环简单得多。
递归缺点:
1.递归由于是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址以及临时变量,而往栈中压入数据和弹出数据都需要时间。->效率
2.递归中很多计算都是重复的,由于其本质是把一个问题分解成两个或者多个小问题,多个小问题存在相互重叠的部分,则存在重复计算,如fibonacci斐波那契数列的递归实现。->效率
3.调用栈可能会溢出,其实每一次函数调用会在内存栈中分配空间,而每个进程的栈的容量是有限的,当调用的层次太多时,就会超出栈的容量,从而导致栈溢出。->性能