@rg070836rg
2018-07-06T06:36:45.000000Z
字数 5352
阅读 2577
data_structure我认为写一个递归函数,主要就是两点:
- 什么时候出递归
- 每一次递归前/后还要做哪些事情
下面的题目,尽量依靠这两点来分析。
在递归调用情况下,被调函数的局部变量采用动态分配存储区(每调用一次就分配一份,以存放当前使用的数据,当返回时随即释放)。我们把这个动态区称为运行栈。每次调用函数执行入栈操作,每次从函数返回时,执行出栈操作。
概括来讲,函数的调用可分为三个步骤:
- 调用函数发送调用信息,包括调用方要传给被调方的信息,如传给形参的实参值,函数返回地址;
- 分配被调方需要的局部数据区,用来存放被调方定义的局部变量,形参变量,返回地址等,并接收调用方传来的调用信息;
- 调用方暂停,把计算控制力转移到被调方。
当被调方结束运行,返回调用方时,返回处理分为三个步骤:
- 传送返回信息,包括被调方要传回给调用方的信息,如计算结果等;
- 释放分配给被调方的数据区;
- 按返回地址把控制转回给调用方;
一般的题目复杂度可以大致看看就能算出来,但是一旦比较难,就不能直接的看出来,下面链接是网上总结比较好的一篇博文,放在此留作参考。
递归实际上是非常非常的消耗空间的,每次递归就会开辟一个递归运行栈,很可能就会由于递归的次数太多初始默认的栈空间被耗尽。同时,也不会去建议为了递归而调高空间。
首先,我们需要先了解下,递归是如何调用的
递归调用的详细过程
那么对于一个问题,要怎么去把递归换成非递归?
- 可以取一个特例
- 分析哪些元素需要放在栈中
- 分析栈的变化
- 转化到普遍,编写算法
其实最重要的就是最后一部,如何把特例转化为普遍规律,这步是很难的,理论上,所有的递归都能用栈来解决,搞清楚递归调用树,分析每步的操作。
下面,链接一篇文章,详细的讲了该问题:算法设计中的递归与非递归转换
编写把十进制正整数转换为S进制(S=2,8,16)数输出的递归算法。
十进制转换为S进制的基本方法是“除基取余,上右下左”。
- 什么时候出递归
- 每一次递归前/后还要做哪些事情

1、当新的被除数为0的时候,出递归;
2、每次递归输后出余数
所以代码如下:
/*** void transfrom(int x,int n)}* 用于进制的转换* 采用递归的方式* @param[in] x十进制数 n转的进制数* @param[out]T x*/void transfrom(int x,int n){if(x==0) return ;//当新的被除数为0的时候,出递归;transfrom(x/n,n);if (x%n<9) //每次递归输后出余数cout<<x%n;elsecout<<char(x%n+55);}
main中测试:
cout<<"递归操作:"<<endl;transfrom(171,16);cout<<endl;transfrom(2147483647,8);cout<<endl;transfrom(2147483647,2);
测试截图:
复杂度很好分析,每次递归做的事情级别为O(1),一共递归了n次,所以是O(n).
下面提供一种非递归的方法,原来的递归是把数字反序输出,所以用栈,可以达到这个目的。
/*** void transfrom_1(int x,int n)* 用于进制的转换* 采用栈的方式* @param[in] x十进制数 n转的进制数* @param[out]T x*/void transfrom_1(int x,int n){stack<int> tmp;while(x!=0){tmp.push(x%n);x=x/n;}while (!tmp.empty()){if (tmp.top()>9)cout<<char(55+tmp.top());elsecout<<tmp.top();tmp.pop();}}
cout<<"栈操作:"<<endl;cout<<endl;transfrom_1(171,16);cout<<endl;transfrom_1(2147483647,8);cout<<endl;transfrom_1(2147483647,2);cout<<endl;
测试截图:

斐波那契数列
F(0)=0,
F(1)=1,
F(n)=F(n-1)+F(n-2)(n≥2,n∈N*)
1、当新的数为1的时候,出递归;
2、每次返回结果
所以代码如下:
/*** int' Fibonacci(int n)* 计算斐波那契数列的值* 采用递归的方式* @param[in] n 项数* @param[out]T x*/long Fibonacci(int n){if (n==0)return 0;else if (n==1)return 1;elsereturn Fibonacci(n-1)+Fibonacci(n-2);}
测试函数:
for (int i=0;i<30;i++){cout<<Fibonacci(i)<<endl;}
测试截图:
关于复杂度:
我们取F(n)看,发现能展开n-1层,每层又需要计算很多重复的东西,比如算f(100)需要重新算f(99)与f(98),所以时间复杂度应该近似会为O(),空间复杂度,到后期,每增加一个维度,都会把前面的重新再算一遍,所以空间复杂度额也近似为O()
同样,这个也提供一种复杂度较低的非递归算法:
long Fibonacci_2(int n){int a,b,c;a=1;b=1;if (n==0)return 0;else if (n==1)return 1;for(int i=2; i<=n; i++){c=a+b;a=b;b=c;}return c;}
很明显这个复杂度就降到了O(n)级别了,空间没有额外开辟为O(1);
分析下这个算法,这个方法每次只保存当前计算值的前两个数,不必像递归那般重复计算已经算过的值多次。那么,同样,可以用这种思路去优化递归算法。
并没有必要重复计算那么多次, 每个值只要计算一次即可,计算完存到一个数组里面,用的时候直接调。代码实现如下:
/*** int' Fibonacci_1(int n)* 计算斐波那契数列的值* 采用数组记忆法,减少了重复计算的步骤,大大降低了时间复杂度* @param[in] n 项数* @param[out]T x*/long memory[100];long Fibonacci_1(int n){if (n==0)return 0;else if (n==1)return 1;if (memory[n]!=0)return memory[n];return memory[n]=Fibonacci_1(n-1)+Fibonacci_1(n-2);}
这段代码,大大降低了原来递归的复杂度,和非递归相比,在计算多个值上比较有效率,而非递归的那种方法,只是在计算每个值时,效率比这段代码高。
杨辉三角数为:

看得出,除了两边的数,其余的都是上边两数字的和,所以:
- 当每排第一个或者每排最后一个时,返回1出递归
- 否则返回两递归之和
所以代码设计十分之简单
int Triangle(int x,int y){if(y==1||y==x)return 1;elsereturn Triangle(x-1,y-1)+Triangle(x-1,y);}/**
通过打印Triangle(i,j)就能得到第i行第j个数字。
主函数测试打印:
int RowCount,i,j,k;cout << "请输入杨辉三角的行数:";cin >> RowCount;for(i = 1 ;i <=RowCount;i++)//从第0行开始{ //打印第i行第一个元素前面的空格for(k = 0;k < RowCount - i;k++){cout << " ";}for(j = 1;j <= i ;j++)//打印第i行的所有元素{cout << Triangle(i,j) << " ";}cout << endl;}
测试结果:

当然,这样的复杂度也很高,每次都是把前面的工作全部重做,近似指数级指数。
同样,类似于Fibonacci数列,可以用记忆数组,把算过的全部存储起来。
/*** int Triangle_1(int x,int y)* 用于求第x行第y个元素的值* 采用递归的方式,记忆数组辅助* @param[in] x行 y列* @param[out]*/int memory[100][100];int Triangle_1(int x,int y){if (memory[x][y]!=0){return memory[x][y];}if(y==1||y==x)return 1;else{memory[x][y]=Triangle(x-1,y-1)+Triangle(x-1,y);return memory[x][y];}}
这样,复杂度一下就降低到了O(n)级别了,效率已经算是很高了
定义相信都很熟悉,不再阐述。
全排序1~4,就是4个首位交替,并且降低规模,重新交替,再降低规模
- 只剩一位数,开始打印,退出递归
- 递归前交换首位与后面的位置,递归后再交换回来
所以代码如下:
void FullArray(int k,int m){int i=0;if(k==m){for(i=0; i<=m; i++)cout<<a[i];cout<<"\t";count++;if(count % 5==0)cout<<endl;}else{for(i=k; i<=m; i++){Swap(&a[k],&a[i]);FullArray(k+1,m);Swap(&a[i],&a[k]);}}}
测试函数:
int main(){count=0;for(int i=0; i<MaxNum; i++)a[i]=i+1;FullArray(0,3);cout<<endl;cout<<"1~"<<4<<"全排列总数为:"<<count<<endl;return 0;}
运行截图:
复杂度也是规模越大,前面做的活就越多,近似指数级了吧。
一个射击运动员打靶,靶一共有10环,连开10枪打中90环的可能性有多少种?试用递归算法实现。
其实这个问题是很好分析的,我要前n枪共打m环,那如果我最后一枪大了i环,就需要前n-1枪打了m-i环,所以问题化简就有了。
- 当最后一枪需要打比10还要大,无解,返回0
- 当最后一枪需要小于等于10,唯一解,返回1
- 当总环数比枪数的10倍还要大,无解,返回0
- 当总环数刚好等于10倍枪数,唯一解,返回1
- 然后考虑当前枪会达到i环,递归到下一个子问题,中间用计数器记上总数
具体实现:
/*** int GetN(int Sum, int n)* 获得可能的方法述* 采用递归的方式* @param[in] 所给总分Sum 所给次数n* @param[out]T x*/int GetN(int Sum, int n){int k=0;if (Sum>n*10) return 0;if (Sum==n*10)return 1;if (n == 1 && Sum >= 0 && Sum <= 10) //若只打一枪,并且环数在0-10之间,只有一种可能性,返回1return 1;if (n==1 && Sum > 10 )return 0;else{for(int i = 0; i <= 10; i++){k += GetN(Sum-i, n - 1);}return k;}}
main中测试:
int main(){cout<<GetN(90,10)<<endl;return 0;}
测试结果如图:

格雷码是在数电中第一次接触到的,当时还为怎么记所困扰,现在看来,很简单。
格雷码的定义是,每次变化只翻转一位。
通过观察,会发现:

- 当只有一位时,输出0,1
- 递归后,把当前列前一半填0,当前列后一半填1,前几列逆序
所以,代码如下:
void TraGray(int n){int m=pow(2,n);//退出条件if (n==1){result[0][0]=0;result[1][0]=1;}else{//缩小问题规模TraGray(n-1);//当前列前一半填0for (int i=0;i<m/2;i++)result[i][n-1]=0;//当前列后一半填1for (i=m/2;i<m;i++)result[i][n-1]=1;//前几列逆序for (i=m/2;i<m;i++)for (int j=0;j< n-1;j++)result[i][j]=result[m-1-i][j];}}
测试函数:
int main(){int N,M;cout<<"请输入格雷码位数"<<endl;cin>>N;if (!(N>0&&N<=6)){cout<<"只支持6位"<<endl;N=6;}M=pow(2,N);TraGray(N);for (int i=0;i<M;i++){for (int j=N-1;j>=0;j--)cout<<result[i][j];cout<<endl;}return 0;}
测试结果:
