您的位置:首页 > 职场人生

由一道腾讯面试题引发的关于递归函数使用的各种情况总结

2015-09-08 10:47 423 查看
        一直有一个感受就是,当我们在某些问题抽象的定义解答时候感到疑惑是,不妨看一下具体问题的解答,更加有助于我们理解问题。首先看一下腾讯的一道招聘的测试题。

       1、面试题是一道程序编程题,要求使用递归的方法生成一个N位长度的格雷码,对格雷骂的定义是,相邻的两个格雷码只有一位的数字有差别。首先看一看我写的关于该题目的C程序代码:#include "stdio.h"

#include"stdlib.h"

void gray(int * a,int pos,int length);

void main()

{
int *a,n;
printf("输入格雷码的长度\n");
scanf("%d",&n);
a=(int *)malloc(n*sizeof(int));
gray(a,0,n);  //算法思想是,从数组的第1位开始为数组依次赋值0和1,
//在每一种赋值情况下,都向下递归,给下一位赋值,直到数组的第n位赋值完毕,则输出数组中的数字串。

}

void gray(int * a,int pos,int length)

{
int i=0;
if(pos<0||pos>length)
{
printf("数组下标操作越界!\n");
exit(1);
}
else if(pos==length)  //数组已经向下递归深度为length,此时满足输出条件。
{
for(i=0;i<length;i++)
{
printf("%d",a[i]);
}
printf("\n");
}
else            //分别在不同的情况下,向下递归。
{
a[pos]=0;
gray(a,pos+1,length); //此处使用的是pos+1而不是pos++后,再调用递归函数自身,这是有区别的。
a[pos]=1;
gray(a,pos+1,length);
}
}

在该递归函数中主体分为了三部分,第一部分是用来判断下标是否越界,用以增强程序的鲁棒性。第二部分是递归函数的唯一出口,即当数组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函数代码,包括一个递归深入的入口,和一个满足条件的出口。这两部分是具有很大的灵活性,我们做不同的处理,可以得到不同的效果。现在反过来看递归函数的定义:直接或间接调用函数本身,则该函数称为递归函数。是不是就好理解一些。

注意:但也应该注意,递归函数虽然使用起来易于理解和形式简单,但其实现原理是程序内部的栈,因此大量复杂的递归调用,会消耗很多资源,影响程序的效率。通常能用递归方法处理的问题,也可以使用循环来处理,只是循环处理在形式上更加复杂和难以理解,但循环方法没有函数调用传值这些,因此效率要高一些。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息