题 源
牛客网-》题库-》专项练习-》递归
拣选的试题 与 答案记录
1.
队列 在 程序调用时 必不可少,因此 递归 离不开队列
:递归离不开 栈
2.
执行完如下语句后,i的值为 48
#include <stdio.h>
int f(int x)
{
return ((x>0)?x*f(x-1):2);
}
int main()
{
int i = f(f(2));
printf("i = %d\n",i); // 48
}
3-1.
采用 递归方式,对 顺序表 进行快速排序,下列关于递归次数 的叙述,正确的是
A 递归次数 与 初始数据 的 排列次序 无关
B 每次划分后,先处理 较长的分区 可以减少 递归次数
C 每次划分后,先处理 较短的分区 可以减少 递归次数
D 递归次数 与 每次划分后得到的 分区处理顺序 无关
3-2.
对 n个记录的线性表 进行 快速排序,为了减少算法的递归深度,如下叙述正确的是 A
A每次分区后,先处理较短的部分
B 每次分区后,先处理较长的部分
C 与 算法 每次分区后 的 处理顺序 无关
解析:
在快速排序中,需要使用递归来分别处理左右子段,递归深度可以理解为系统栈保存的深度,先处理短的分段再处理长的分段,可以减少时间复杂度;
如果按长的递归优先的话,那么短的递归会一直保存在栈中,直到长的处理完。短的优先的话,长的递归调用没有进行,他是作为一个整体保存在栈中的,所以递归栈中的保留的递归数据少一些。
举例分析:
现在有这么个序列:123456789
假设每次划分出短序列的长度为1
即第一次划分
短序列:1
长序列:23456789
如果优先处理短序列1
则栈中仅用保存23456789,深度为1
然后23456789出栈,划分
短序列:2
长序列:3456789
同样的先处理短序列
栈中保存3456789,深度为1
类推下去,处理完整个序列,栈的最大深度都为1
假如每次划分出的短序列长度为2呢
短序列:12
长序列:3456789
优先处理短序列12
栈中保存3456789 深度为1
12只能划分为同样长度的序列1和2
先处理左边的
栈保存2 此时栈中有3456789 和 2 深度为2
然后2 出栈 处理
接着3456789出栈
划分为短序列34
长序列 56789
处理短序列34
栈中保存56789
类推下去,处理完整个序列,栈的最大深度都为2
也就是说栈的最大深度取决于划分出来的短序列的长度 (前提是先处理短序列)
那么先处理长序列呢
短序列:1
长序列:23456789
如果优先处理长序列序列23456789 短序列入栈,长序列划分为2和3456789
2入栈,3456789划分。。。
8入栈,9处理
此时栈中有12345678 深度为8
短序列:12
长序列:3456789
12入栈 3456789划分为34和56789
34入栈 56789划分为56和789
56入栈 789划分为78和9
9入栈 78划分为7和8
7入栈 8处理
此时栈中有12 34 56 9 7 深度为5
很明显先处理长序列 栈的深度要大于 先处理短序列栈的深度。
4.
int x(int n)
{
if(n<=3){
return 1;
}else{
return x(n-2)+x(n-4)+1;
}
}
int main()
{
int i = x(x(8));
printf("i = %d\n",i);
}
递归算法 x(x(8)) 需要调用 18 次函数x(int n)?
5.
递归函数 中的 形参 是
A自动变量
B 外部变量
C 静态变量
D 可根据需要 自定义 存储类型
解析:
递归借助栈来实现,自动变量存储在栈中,随着递归的进行,自动创建和销毁。
自动变量 可以大体 等价于 局部变量,但是也不完全相同。
外部变量 和 静态变量存放在静态存储区,不能作为递归函数的参数。
6.
递归函数 最终会 结束,那么这个函数一定 B
A 使用了 局部变量(应该是 自动变量)
B 有一个分支 不调用自身
C 使用了 全局变量 或者 使用了 一个或多个 参数
D 没有循环调用
7.
一个 递归算法 必须包括(B 终止条件 和 递归部分)
A 递归部分
B 终止条件 和 递归部分
C 迭代部分
D 终止条件 和 迭代部分
8.
4个圆盘的 汉诺塔,总的移动次数是 15
解析:
汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
设移动n个盘子的汉诺塔问题需要g(n)次移动操作来完成。由展示移动过程算法可知g(n)应是三部分之和。
(1) 将n个盘上面的n-1个盘子借助C桩从A桩移到B桩上,需g(n-1)次移动;
(2) 然后将A桩上第n个盘子移到C桩上(1次);
(3) 最后,将B桩上的n-1个盘子借助A桩移到C桩上,需g(n-1)次。
因而有递归关系:
g(n)=2*g(n-1)+1
初始条件(递归出口):
g(1)=1
即 1、3、7、15、31。。。即g(n) = 2^n -1
9.
n!后面有2499个0,6!=1*2*3*4*5*6=720,720后面有1个0,n=10000,求n!。
解析:
方法1 短除法:
10000/5=2000 有2000个能被5整除
2000/5=400 这2000个里面能被5整除有400个(2000个已被5除过1次。能除第二次的有400)
400/5=80 同理 80个
80/5=16 同理 16个
16/5=3余1 同理 3个
结果2000+400+80+16+3=2499
方法2:
阶乘末尾一个零表示进位,相当于是乘以10,而`10=2*5` ,所以可以产生10的有以0,2,4,5,6,8结尾的数字。这里2是足够的,关键是5的个数。现在分析:
每个5个,会有一个0,比如5,10,15,20,...
每隔5X5,会多产生一个0,比如25,50,75,...(这里的5只在上一种情况算了一个5,因此在这里加上)
每隔5X5X5游会多出一个5
...
现在算10000的阶乘有几个零:10000/5 + 10000/25 + 10000/125 + 10000/625 + 10000/3125 = 2499
10-1.
任何一个 递归过程 都可以转换成 非递归过程(对 )
10-2.
凡是递归定义的数据结构,都可以用 递归算法 来实现它的操作(对)
10-3.
递归程序的一般优化手段为 尾递归优化
A 尾递归 优化
B 循环 优化
C 堆栈 优化
D 停止值 优化
解析:
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。 尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。 遗憾的是,大多数编程语言没有针对尾递归做优化。
以斐波那契数列为例子:
普通的递归版本
int fab(int n){
if(n<3)
return 1;
else
return fab(n-1)+fab(n-2);
}
具有"线性迭代过程"特性的递归---尾递归过程
int fab(int n,int b1=1,int b2=1,int c=3){
if(n<3)
return 1;
else {
if(n==c)
return b1+b2;
else
return fab1(n,b2,b1+b2,c+1);
}
}
以fab(4)为例子
普通递归fab(4)=fab(3)+fab(2)=fab(2)+fab(1)+fab(2)=3 6次调用
尾递归fab(4,1,1,3)=fab(4,1,2,4)=1+2=3 2次调用
10-4.
只有那种 使用了 局部变量的 递归过程 在转换成 非递归过程 时才必须使用 栈(错)
解析:
递归工作栈里面包括返回地址、本层的局部变量和递归调用的形参代换用实参,所以正常情况下,无论递归过程有没有使用局部变量,转换为非递归过程都需要用栈来模拟这个递归调用过程。
当然,有一些特殊递归不用栈就可以直接转换,比如尾递归、常系数递推等,无论是否有局部变量
11.
有一段15级台阶的楼梯,以小明的脚力一步最多跨3级,请问小明登上这段楼梯一共有多少种方法?
解析:
方法一:
假设走n步的方法数目为f(n),那么对于n步的阶梯,有三种情况:第一步走一步,第一步走两步,第一步走三步,
剩下的分别有f(n-1),f(n-2),f(n-3 )种走法 ,所以有:
f(n)=f(n-1)+f(n-2)+f(n-3)=2*f(n-1)-f(n-4) (对于n>=5)
而对于n<5的情况有:
f(1)=1
f(2)=2
f(3)=4
f(4)=7
于是有:
f(5)=2*7-f(1)=13
f(6)=2*13-f(2)=24
f(7)=2*24-f(3)=44
f(8)=88-f(4)=81
f(9)=2*81-f(5)=149
f(10)=298-f(6)=274
f(11)=548-f(7)=504
f(12)=1008-f(8)=927
f(13)=1854-f(9)=1854-149=1705
f(14)=3410-f(10)=3410-274=3136
f(15)=6272-f(11)=6272-504=5768
方法二:
类似斐波那契数列的思想,若所求方法表示为f(n),因为当台阶大于3时,可看做是
f(n)=f(n-1)+f(n-2)+f(n-3);//因为踏入最后一节阶梯有三种方法,最后一步是一步,两步,三步。
代码如下:
public static void main(String[] args) {
int f1 = 1;
int f2 = 2;
int f3 = 4;
int result = 0;
for(int i = 4;i<=15;i++){
result = f1+f2+f3;
f1 = f2;
f2 = f3;
f3 = result;
}
System.out.println(result);
}
12.
How many times is f() called when calculating f(10)?
int f(int x) {
if(x <= 2)
return 1;
return f(x - 2) + f(x - 4) + 1;
}
解析:
针对这样的题目在别的地方看到的比较好的方法是用树来表示
10
8 6
6 4 4 2
4 2 2 0 2 0
2 0
图中树的节点数是15,所以是调用了15次
13. 下列方法 不可以 达到 程序调优效果的是 B
A 改善数据的访问方式,提升 缓存的命中率
B 使用多线程,提高 I/O密集型操作的效率
C 利用数据库连接池 替代 直接的数据访问
D 利用 迭代 代替 递归
E 合并 多个远程调用批量发送
F 共享 冗余数据,提高访问效率
解析:
A. 提升缓存命中率可以有效加快服务响应速度
B. IO密集型表示大部分情况下IO处于繁忙状态。多线程适合于CPU等待长时间IO操作的情况,比如网络连接数据 流的读写。在IO密集型情况下IO操作都比较慢,因此需要专门开线程等待IO响应,而不影响非IO任务的执行。
C. 数据库连接池可以有效减少频繁建立连接带来的时间消耗
D. 迭代代替递归,减少栈空间的使用和中间值的保存
E. 远程调用批量发送,减少网络流量传输,其需要等待多个调用达到一定数量打包才进行请求,不能提高程序效率
F. 共享冗余,提高效率,加快访问
14.
int main(){
fork()||fork();
}共创建 3 个进程
解析:
首先,第一个fork()执行后,产生一个父进程的副本(返回值为子进程ID)和一个子进程(返回值为0),如下图所示,由||知道,父进程的左分支终止,继续执行右分支的分裂,再产生一个子进程的副本和一个子进程,即下图中的子1,因此总共产生了3个进程,如图中的圈圈
15.
Which of the following statements are true? ACD
A We can create a binary tree from given inorder and preorder traversal sequences.
B We can create a binary tree from given preorder and postorder traversal sequences.
C For an almost sorted array, insertion sort can be more effective than Quicksort.
D Suppose T(n) is the runtime of resolving a problem with n elements, T(n) = Θ(1) if n = 1; T(n) = 2T(n/2) + Θ(n) if > 1; so T(n) is Θ(n log n).
E None of the above.
16.
以下程序是用辗转相除法来计算两个非负数之间的最大公约数:
long long gcd(long long x, long long y) {
if (y == 0)
return x;
else
return gcd(y, x % y);
}
我们假设x,y中最大的那个数的长度为n,x>y,基本运算时间复杂度为O(1),那么该程序的时间复杂度为( )
A O(1)
B O(logy)
C O(n)
D O(x)
解析:
求最大公约数的最常用的算法是欧几里得算法,也称为辗转相除法.
问题定义为求i和j的最大公约数gcd(i,j),其中i和j是整数,不妨设i>j.
算法可以递归的表示:
1.如果j能整除i,那么gcd(i,j)=j;
2.j不能整除i,令r=i%j,那么gcd(i,j)=gcd(j,r).
使用C语言实现:
int gcd(int i, int j)
{
int r = i % j;
return r == 0 ? j : gcd(j, r);
}
正确性分析:
算法的步骤1,显然成立(最大公约数定义).关键是要证明步骤2.
设d是i和j的最大公约数,
那么i=md,j=nd,m和n互质(否则d不是最大公约数).
由r=i%j可以得到i=kj+r,k=⌊m/n⌋,k≥1(我们前面假设过i>j).
把i=md,j=nd代入得到md=knd+r
那么r=(m-kn)d,m-kn和m也是互质的.所以得到d是j和r的最大公约数.
时间复杂度分析:
逆着看该算法,最后的余数是0,倒数第二次余数是d,倒数第三次是kd,k>1…
由于组成了一个数列,{0,d,kd,nkd+d,…}
数列的n项加上n+1项,比n+2项要小,所以比斐波纳契数列增长的要快.
我们已知斐波纳契数列增长速度是指数,那么待分析的数列也是指数增长.
设欧几里得算法需要k次,那么j=O(2^k),则k=O(lg j).
所以欧几里得算法求最大公约数的时间复杂度是对数量级的,速度非常快.