author: hjjdebug
date: 2025年 06月 10日 星期二 14:28:58 CST
descrip: 递归与循环的互换性
程序的基本结构是顺序,选择和循环, 也分别对应着顺序语句,选择语句和循环语句.
顺序语句,一条一条向下走就行.
选择语句,会判断条件,或者继续顺序执行,或者跳过一段代码后执行.
循环语句, 就是goto back; 跳转到一个旧地址重复执行.
从汇编的角度看,有了选择语句,就可以实现循环语句,
但是从上层结构看,通常把选择语句看成是if-else 语句和switch 语句.
而把循环语句用for循环和while循环来表示.
c++语言又扩展了一种迭代的描述: for(iterator it=begin,it!=end,it++); for(m : arr);
迭代方式也代表一种循环结构.
循环的核心结构是一个回跳语句go-back,才能实现代码的重复执行.
所有的循环都可以用递归来表达, 同样,所有的递归也可以用循环来表达.
递归的核心是一个call-back, 实现执行地址向低地址回跳.
就是说通常代码执行程序地址都是依次向下递长,顺序执行,一但发生调转就要注意了.
往前跳,可能是if-else 或switch 语句, 往后跳,就是循环. 一个函数内往自己的脑袋顶上call调用,就是递归.
递归的定义大概是说在一个函数、过程或数据结构的定义中,直接或间接地调用自身的方法
因为递归牵扯到调用, 所以还是以函数递归来说明比较方便.
函数递归:
在一个函数F(i)中, 其中i是输入参数, 调用了函数F(i-1),这就是函数递归.
从汇编的角度看, 一个递归函数(是一个函数调用入口),在执行的过程中,忽然又call_back自己
的函数入口, 这就是递归函数了, 如果是go_back到某处, 那就是循环了. 循环和递归本质上就这么一点点差别,
所以可以互相替换.
call_back 有堆栈入栈, ret有堆栈出栈的操作,包括函数调用参数也可能用堆栈传送.
go_back 则没有栈操作,直接回跳.
开始学习递归时,总觉得递归是规律性很强的代码, 例如费波那伽公式F(n)=F(n-1)+F(n-2);
连乘公式 n! = n * (n-)!, 连加公式 sum=1+2+3+…+n, 还有2叉数的左递归,右递归.
以为写递归必先找递推公式,
而公式这种有规律的东西太难找了. 除了我们熟悉的几个公式,你造一个或者发现一个公式出来试试?! 很难找,所以很少用递归写代码.
其实递归等价于循环, 是另一种代码复用方式, 是一种程序执行时的call_back,不需要什么寻找递推公式.
破除了这种心理障碍,写递归函数也就很平常了.
我们考虑 F(i)=foo1+F(i-1)+foo2; 的递归函数,
这个函数需要抽象理解,其中foo1,foo2代表简单陈述语句. i-1可以理解为距离退出条件更近一步的意思.
整个的执行过程像一个V字型,foo1被展开执行了n遍, foo2被展开执行了n遍
foo1为V 的左边执行代码,堆栈依次下降被执行,也是先序执行或正序执行或调用执行.结果是 foo1(i), foo1(i-1),…foo1(1),foo1(0);
foo2为V 的右边执行代码,堆栈依次上升被执行,也是后序执行或倒序执行或返回执行,结果是 foo2(0), foo2(1),…foo2(i-1),foo2(i);
从它的执行过程被展开我们可以看出,递归可以用循环来代替, foo1,foo2就是执行体,但要巧妙安排.
例: 打印1到10 10个数据,用循环和递归怎样表示
循环:
#include
int main(void)
{
for(int i=1;i<10;i++)
{
printf("i:%d\n",i);
}
return 0;
}
递归:
$ cat main.cpp
#include
void F(int i)
{
if(i==10) return; //递归退出条件
printf("i:%d\n",i);
F(++i);
}
int main(void)
{
F(1);
return 0;
}
$ cat main.cpp
#include
int Sum2(int i)
{
if(i==1) return 1; //递归退出条件
return i+Sum2(i-1); //执行体 i+(i-1)+...1, v左侧下降
}
int Sum1(int i)
{
int d=0;
while(i>0) //循环退出条件
{
d+=i; //执行体 5+4+...+1
i--;
}
return d;
}
int main(void)
{
int n1=Sum1(5);
int n2=Sum2(5);
printf("n1:%d\n",n1);
printf("n2:%d\n",n2);
return 0;
}
执行结果
$ ./tt
n1:15
n2:15
$ cat main.cpp
#include
int Fac2(int i)
{
if(i==1) return 1; //递归退出条件
return i*Fac2(i-1); //执行体 i*(i-1)*...1, v左侧下降
}
int Fac1(int i)
{
int d=1;
while(i>0) //循环退出条件
{
d*=i;
i--;
}
return d;
}
int main(void)
{
int n1=Fac1(5);
int n2=Fac2(5);
printf("n1:%d\n",n1);
printf("n2:%d\n",n2);
return 0;
}
执行:
$ ./tt
n1:120
n2:120
这一次,反而是递归好写,循环难写,要合适的设置变量记录状态.构建循环体.
#include
int Fib2(int i)
{
if(i==1) return 1; //递归退出条件
if(i==2) return 1; //递归退出条件
return Fib2(i-1)+Fib2(i-2); //执行体
}
int Fib1(int i)
{
int a=1; //f(1)=1
int b=1; //f(2)=1
int c=1; //c=f(1)+f(2)
int l=2;
while(l<i) //循环退出条件
{
c=a+b; //计算下一个f
a=b; //把数值向前移动
b=c;
l++; //递长循环次数
}
return c;
}
int main(void)
{
int n1=Fib1(8);
int n2=Fib2(8);
printf("n1:%d\n",n1);
printf("n2:%d\n",n2);
return 0;
}
执行结果:
$ ./tt
n1:21
n2:21
其它就不多举例了.
核心说明了递归和循环只是call_back 和 jump_back 的区别.
递归堆栈沿V型左边递,沿V型右边归. 循环无堆栈变化
它们都完成了重复执行某个代码块的任务. 都需要有退出条件.