算法小谈:递归

一:递归

基本概念:一个函数调用它自身,就是递归

如求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.调用栈可能会溢出,其实每一次函数调用会在内存栈中分配空间,而每个进程的栈的容量是有限的,当调用的层次太多时,就会超出栈的容量,从而导致栈溢出。->性能

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值