目录
《一》构造递归
抓住两个要点:“相似性”和“出口”
没有相似性,主动构造
不相似的原因很有可能是缺少参数
类似数学公公式上的递推
《二》递归的调用
- 被调函数恰为主调函数
- 调用次序不同
递归的调用离不开栈,栈的作用是后进先出
栈会保存断点的信息,待执行完被调函数之后返回主调函数从断点处继续执行
- 注意返回次序(后进先出)
- 形参并非同一变量
- 虽然传递的都是同一个参数,但他们本质上是不同的
- 比如:山上有个庙庙里有个老和尚这样的循环,老和尚所在的山是山1,他所讲的山是山2
构造递归代码
# include<cstdio>
#if 0
构造递归:
先考虑共性函数,再对终止条件做限制
共性函数的设计不同,其递归函数的结构也不同
# endif
//从begin打印到end 先打印begin(1)
void f(int begin,int end)
{
/*
当发现递归没有共通性时,可以考虑增加参数
*/
if(begin > end)
return ;
printf("%d ",begin);
f(begin+1,end);
}
//打印1~n 先打印9(从大到小打印)
void f2(int n){
if(n > 1)
f2(n-1);
printf("%d ",n);
}
int main()
{
int n = 9;
f(1,n);//打印0-n
printf("\n");
f2(n);
return 0;
}
/*数组求和*/
# include<cstdio>
/*三种思路
*(1)a[begin] + (a[begin+1]+...+a[end])
*(2)(a[begin] + a[end-1]) + ... a[end]
*(3)折半相加
*/
int length(int a[])
{
int len = 0;
for(int i = 0;a[i];++i)
len++;
return len;
}
/*两种基本递归求和*/
int f1(int a[],int begin)
{
if(begin == length(a))
return 0;
int x = f1(a,begin+1);
return x + a[begin];
}
int f2(int a[],int end)
{
if(end < 0)
return 0;
int x = f2(a,end-1);
return x + a[end];
}
/*折半相加*/
int f3(int a[],int begin,int end)
{
int mid = (begin+end)/2;
if(mid == end)
return a[end];
if(begin == mid)
return a[mid];
return f3(a,begin,mid) + f3(a,mid,end);
}
int main()
{
int a[] = {2,5,8,9,6,3,1,4,7};
int begin = 0;
//int sum1 = f1(a,begin);
//int sum2 = f2(a,length(a));
int sum3 = f3(a,begin,length(a));
printf("%d",sum3);
return 0;
}
# include<iostream>
# include<string>
using namespace std;
/*判断两个串是否相等*/
bool f(string& s1,string& s2){
if(s1.length() != s2.length())
return false;
if(s1.length() == 0)
return true;
if(s1[0] != s2[0])
return false;
string ss1 = s1.substr(1,s1.length());
string ss2 = s2.substr(1,s2.length());
return f(ss1,ss2);
}
int main()
{
string s1,s2;
cout << "输入串1:" << endl;
getline(cin,s1);
cout << "输入串2:" << endl;
getline(cin,s2);
if(f(s1,s2))
cout << "相等!" << endl;
else
cout << "不相等!" << endl;
return 0;
}
《三》递归和回溯
注意:
每一次调用都要降低计算规模
找到相似性
定义出口
注意出口的顺序
注意回溯
回溯的作用就是保持单一变量
回溯代码:
# include<iostream>
# include<algorithm>
# include<string>
using namespace std;
/*全排列递归算法 */
#if 0
相似性:
字符串中的每一个字符都可以做第一位(遍历字符串中的每一个字符),确定当前一位,之后依次确定
出口:
字符串中的每一个字符都放到过第一位
#endif
//k:记录当前交换位置
void f(string &s,int k){
if(k == s.length()){
for(int i = 0;i < s.length();++i)
cout << s[i] << " ";
cout << endl;
}
//循环遍历时,需要注意回溯 回归到原状态,来确保只有单一变量
for(int i = k;i < s.length();++i)
{
int t = s[k]; s[k] = s[i]; s[i] = t;//交换
f(s,k+1);
t = s[k]; s[k] = s[i]; s[i] = t;//回溯
}
}
int main()
{
string str = "abc";
f(str,0);
return 0;
}
# include<iostream>
# include<algorithm>
# include<string>
using namespace std;
/*求最大公共子序列个数*/
//递归
/*
相似性:
s1的第一个字符和s2的字符
{
相等:截取两个串的第一个的串比较
不相等:分别截取s1和s2的首字符求两个最大公共子串的最大值
}
出口:
任一个子串的长度为0
*/
int f(string &s1,string &s2){
if(s1.length() == 0 || s2.length() == 0)
return 0;
string ss1 = s1.substr(1,s1.length());
string ss2 = s2.substr(1,s2.length());
if(s1.at(0) == s2.at(0))
return f(ss1,ss2)+1;
else
return max(f(ss1,s2),f(s1,ss2));
}
int main()
{
string s1,s2;
getline(cin,s1);
getline(cin,s2);
cout << f(s1,s2);
return 0;
}
更多代码
# include<iostream>
# include<algorithm>
using namespace std;
/*递归解法
相似性:
思路就是从n个球中选一个特殊的,要么选【f(n-1,m-1)】,要么不选【f(n-1,m)】
出口:
n < m --> 0
n == m --> 1
m == 0 -->1
*/
long long f(int n,int m){
if(n < m)
return 0;
if(n == m)
return 1;
if(m == 0)
return 1;
return f(n-1,m-1) + f(n-1,m);
}
/*优化算法*/
long long f1(int n,int m){
}
int main()
{
cout << f(3000,3) << endl;
return 0;
}
# include<iostream>
# include<algorithm>
# include<string>
using namespace std;
/*字符串反转递归实现*/
string reverse(string &s){
if(s.length() < 2) return s;
string ss = s.substr(1,s.length());
return reverse(ss) + s.at(0);
}
int main()
{
string s;
cin >> s;
cout << reverse(s) << endl;
return 0;
}
# include<iostream>
# include<algorithm>
using namespace std;
#if 0
杨辉三角可以转化成下三角矩阵的形式
1 1
1 1 1 1
1 2 1 1 2 1
1 3 3 1 ===> 1 3 3 1
1 4 6 4 1 1 4 6 4 1
1 5 10 10 5 1 1 5 10 10 5 1
# endif
//输出杨辉三角的第m层第n个元素的系数
int f(int m,int n){
if(m == 0 || n == 0) return 1;
if(n == m) return 1;
return f(m-1,n) + f(m-1,n-1);
}
int main()
{
int m;
cout << "输入杨辉三角的层数:";
cin >> m;
m -= 1;
for(int i = 0;i <= m;++i)
cout << f(m,i) << " ";
return 0;
}
# include<iostream>
# include<algorithm>
using namespace std;
int n = 5;
/*计算错误和*/
//err_sum:错误和
//a[]:数组
//k:当前项
//cur_sum:当前和
//b[]:是否取当前项
void f(int err_sum,int a[],int k,int cur_sum,bool b[]){
if(cur_sum > err_sum) return ;
if(cur_sum == err_sum){
for(int i = 0;i < n;++i)
if(b[i])
cout << a[i] << " ";
cout << endl;
return ;
}
if(k >= n) return ;
//分两种情况:是否取当前项
b[k] = false;
f(err_sum,a,k+1,cur_sum,b);
b[k] = true;
cur_sum += a[k];
f(err_sum,a,k+1,cur_sum,b);
b[k] = false;
}
int main()
{
int err_sum = 6;
int a[] = {3,2,4,3,1};
bool *b = new bool[n];
for(int i = 0;i < n;++i)
b[i] = false;
f(err_sum,a,0,0,b);
return 0;
}
《四》递归的局限性
一、函数调用需要使用内存中的栈来保存函数的数据以及访问链和控制链,如果数据是必须的,那么访问链和控制链等所占的内存则是额外的。
二、使用递归,会进行很深层次的调用函数,所以需要调用很多函数,需要建立许多的访问链和控制链,占用大量内存,而且调用时传递参数,申请空间,返回时恢复现场,都有时间的花销,所以递归效率低。
三、内存的栈当作一个容器,每次递归,函数都往容器中添加容量,当添到一定层次,容器满了,就溢出了,如果用递归,如果忘记了递归截至条件,你就可能进入了无穷递归,无穷递归必然会溢出,所以,不提倡递归。
小结
递归算法一般都是提供一种思路,对于规模较小可以解决,但远远不能满足正常规模的输入(如组合问题中,才3000就需要30多秒,这已经算挂了),所以常常需要优化算法。
部分优化算法:
斐波拉切数列及优化:斐波拉切数列的定义及优化_AmosTian的博客-CSDN博客
阶乘计算及优化:阶乘计算及优化_AmosTian的博客-CSDN博客