当前位置: 首页 > 科技观察

C语言的“递归函数”这么难懂,为什么不舍弃呢?

时间:2023-03-16 19:30:55 科技观察

初学者在学习C语言的过程中,遇到“递归”这个概念,往往会感到迷茫。坦白说,“递归”在编程语言中确实是一个比较难理解的概念,“递归”能解决的问题,一般的循环语句也能解决。在某种程度上,C语言中的“递归”相当于一个循环语句。既然如此,为什么C语言不“抛弃”那个难以理解的“递归”呢?为什么C语言不抛弃“递归”?其实,“递归”难不难理解,就看程序员看问题的角度了。应该明白,C语言程序是为人们现实生活的需要服务的。在我看来,如果脱离实际问题,只从编程语言的角度看问题,“递归”确实很难理解。相反,如果结合实际解决的问题,有时“递归”反而更清晰易懂。请看下面的C语言代码示例:intfactorial(intn){if(0==n)return1;elsereturnn*factorial(n-1);}factorial()函数是一个典型的递归函数,虽然它的代码很简单,但如果只从编程语言的角度来理解这个函数,确实有点难——当n!=0时,函数似乎永远在嵌套自己,虽然粗略的一步步分析可以得到输出功能,但稍有不慎就会出错。现在换个角度考虑factorial()函数,试着分析一下这个函数要解决的“实际需求”。不难看出factorial()函数其实是说:如果n等于0,则f(n)等于1,即f(0)等于1如果n不等于0,则f(n)等于n*f(n-1)将这两句话转化为数学语言,即:如果factorial()函数的数学描述限于nas不小于0,那么这显然就是数学中整数阶乘的定义,即factorial(n)函数的输出为n!。可见递归函数factorial()其实是在“描述”n的数学定义!在C语言中,所以从这个角度来看,“递归”似乎并没有那么难理解。当然也可以自己写程序计算n!C语言中使用循环语句。读者可以自己写,应该能发现循环语句计算n!实施不同于设计思维的角度。所以,从上面的例子可以看出,C语言中的“递归”并不是一种语法,而是一种“编程思维”,所以“舍弃”是谈不上的。当然,严格来说,C语言也支持“递归”。至少递归函数属于C语言的一种语法。这其实与C语言的基本设计思想有关:自从C语言诞生以来,就一直坚持着一个特点——越简单越好,给程序员更大的自由度。所以,既然“递归”思想是个好思想,C语言当然要提供递归函数来支持。再看一个例子为了加深对递归的理解,这里再举一个例子,请看下面的C语言代码:#includevoidtest(intleft,intright){if(left>=right)return;intmid=(left+right)/2;printf("前:%d%d%d\n",左,中,右);测试(左,中);测试(中+1,右);printf("后:%d%d%d\n",left,mid,right);}intmain(){test(0,5);return0;}test()函数C语言代码test()函数显然是一个递归函数,它的代码也是比较简单,但是里面递归调用了两次,稍微复杂一点。下面我们将从编程语言和实际需求的角度来分析test()函数的作用。首先,从编程语言的角度来看,test()函数显然会被多次调用。为了便于讨论,我们会在每次调用test()函数时,在函数名前加上后缀“_xx”。在main()函数中第一次调用test()函数,进入test()函数后,程序会先递归调用test(left,mid);该行可以写成:test_0(0,5):output"before:025",calltest_1(0,2)test_1(0,2):output"before:012",calltest_2(0,1)test_2(0,1):输出"before:001",调用test_3(0,0)test_3(0,0):因为0>=0,所以直接返回test_2(0,1)处这一点需要注意的是,test_0~3()都是执行到“test(left,mid)”,所以会在“test(mid+1,right)”时返回。返回test_2(0,1)时,left=0,right=1,mid=0,所以test(mid+1,right)会直接返回,输出"after:001";然后返回test_1(0,2),同理,输出“after:012”;然后返回test(mid+1,right);test_0(0,5)行;此时,left=0,right=5,mid=2,则接下来的过程会进行如下:test_4(3,5):output"before:345",calltest_5(3,4)test_5(3,4):output"before:334",calltest_6(3,??3)test_6(3,3):因为3>=3,所以直接返回test_5(3,4)此时,应该需要注意的是test4~6()是在执行"test(mid+1,right)"时递归调用的,所以他们会返回到"printf("after:..."。之后函数返回到test_5(3,4)),此时left=3,right=4,mid=3,所以输出"after:334",并返回test_4(3,5),输出"after:345",并返回test_0(0,5),输出"after:025"。整理一下,得到上面的C语言程序输出如下:before:025before:012before:001after:001after:012before:345before:334after:334after:345after:025可见,从编程语言的角度分析递归函数确实是一个艰巨的任务,如果给test(0,105)输入的函数的参数比较宽,can基本上被认为是不可分析的。现在我们试着从实际需求的角度来理解递归函数test()的作用,应该可以很容易的得到如下信息:当输入left不小于right时,函数直接返回;否则,test()函数会打印left和right及其平均Integer值mid下,设置right=mid,重复上述过程;设置left=mid+1,然后重复上面的过程打印left,mid,right之后,稍微了解一下,基本上应该能看懂test()函数的作用现在:mid就是二进制的中间值左右的,所以test(left,mid)可以看成二分后的左半部分,test(mid+1,right)可以看成二分后的右半部分,所以test()的函数作用是一直二分left~right区间,直到不能再二分(left>=right)。这个过程是一步步递归的,所以test()函数也会逐渐输出二分法的结果。以分析“after:...”输出为例,test(0,5)会依次输出:001012334345025,这与从程序语言角度的分析结果是一致的。理解了这一点,现在我们介绍一个应用实例,假设有一个数组{3,2,5,1,4},调用test(0,5)对数组进行逐步划分,过程应该如图下图:划分数组的过程就到这里,我们从实际需要的角度来分析递归函数。可见,理解递归函数其实就是理解它的设计,从更高的角度理解它的意义。总结“递归”在C语言中不能算是一种文法,它更像是一种思想,所以说“C语言为什么不抛弃”递归“是没有意义的”。本文还通过两个例子,从编程语言和实际应用的角度,探讨了如何分析C语言中的递归函数。可见递归有时可以简洁的解决问题。但是,不得不承认,递归确实是一个很难理解的概念,而且递归函数也很难调试,并且会消耗大量的栈空间。因此,在实际的C语言程序开发中,除非递归能带来极大的便利,否则并不推荐使用递归,而是尽可能使用循环来解决问题。