一直有一个感受就是,当我们在某些问题抽象的定义解答时候感到疑惑是,不妨看一下具体问题的解答,更加有助于我们理解问题。首先看一下腾讯的一道招聘的测试题。
1、面试题是一道程序编程题,要求使用递归的方法生成一个N位长度的格雷码,对格雷骂的定义是,相邻的两个格雷码只有一位的数字有差别。首先看一看我写的关于该题目的C程序代码:#include "stdio.h"
#include"stdlib.h"}
在该递归函数中主体分为了三部分,第一部分是用来判断下标是否越界,用以增强程序的鲁棒性。第二部分是递归函数的唯一出口,即当数组a的N位都完成赋值,即输出存储的格雷码。第三部分是递归函数未达到出口条件,还需要修改相关的变量,继续向下递归。在此处分别对a【pos】,进行赋值0和1,然后分别递归下去。
其实在这个gray()函数中,修改第三部分else中的相关内容,就可以生成一个N位长度,十进制数字的全排列组合。处理方法即是分别对a【pos】赋值0.....9,然后在每一种赋值情况下分别递归下去。
2、下面看一个关于递归方法求二叉树的深度。
int DepthBiTree_digui(SBT * T,int n,int * depth) // n表示此时递归深度,即树的深度,depth用来记录树的最终深度
{
if(!T)
* depth=0;
else
{
if(T->lchild)
DepthBiTree_digui(1,T->lchild,n+1,&(* depth));
if(T->rchild)
DepthBiTree_digui(1,T->rchild,n+1,&(* depth));
if(* depth<n)
* depth=n;
}
return ok;
}
在这个函数中,递归是否向下继续递归,则是根据所在根节点是否有左右子树为依据,而函数的出口有两个,一个是空树,即返回深度为0,一个是所在根节点没有左右子树,此刻检查此时深度,是否比以depth记录的更深,如果更深,则更新depth的值。在这种方法中,是通过先序遍历,来完成整个二叉树的遍历,在遍历过程中求得二叉树的深度。
3、二叉树的三种递归遍历算法
void PreOrder(BiTree T) //先序遍历算法
{
if(T)
{
visit(T); //访问根节点
PreOrder(BiTree T->lchild); //递归遍历左子树
PreOrder(BiTree T->rchild); //递归遍历右子树
}
}
void InOrder(BiTree T) //中序遍历算法
{
if(T)
{
InOrder(BiTree T->lchild); //递归遍历左子树
visit(T); //访问根节点
InOrder(BiTree T->rchild); //递归遍历右子树
}
}
void PostOrder(BiTree T) //后序遍历算法
{
if(T)
{
PostOrder(BiTree T->lchild); //递归遍历左子树
PostOrder(BiTree T->rchild); //递归遍历右子树
visit(T); //访问根节点
}
}
4、记得以前看过一个农夫过河的问题,该问题也可以很轻易的用递归算法来求解,每次在经过判断后,对可行解分别进行递归向下继续深入,当达到了过河的目的后,即输出整个路劲即可,在该问题的解答中,即是对递归方法的深入和出口都做了限定处理。
总结:Func()
{
if(/*满足一定条件,不再递归*/)
{
//此处即是递归函数的出口,可以根据具体问题做相应处理
}
else
{
//在这里是函数递归未达到一定要求,还需要向下继续递归,在递归前,可对相应递归条件进行处理。
}
}
由此可见,递归方法的使用是很灵活的,根据我们解决的不同问题,我们可以对相应部分进行修改。但是可以总结出递归函数大体包括两个部分,如上述func函数代码,包括一个递归深入的入口,和一个满足条件的出口。这两部分是具有很大的灵活性,我们做不同的处理,可以得到不同的效果。现在反过来看递归函数的定义:直接或间接调用函数本身,则该函数称为递归函数。是不是就好理解一些。
注意:但也应该注意,递归函数虽然使用起来易于理解和形式简单,但其实现原理是程序内部的栈,因此大量复杂的递归调用,会消耗很多资源,影响程序的效率。通常能用递归方法处理的问题,也可以使用循环来处理,只是循环处理在形式上更加复杂和难以理解,但循环方法没有函数调用传值这些,因此效率要高一些。