您的位置:首页 > 其它

一个例子看懂递归

2016-03-08 22:00 204 查看
一、为什么要搞定递归

在计算机科学与技术中,递归思想是简单而且复杂的。它可以将复杂的数学问题用简单的代码实现,但是要理解它却是需要复杂的思考。大多数算法中都巧妙的使用了,或者可以使用递归来完成,比如排序算法中的快速排序、堆排序、归并排序等,数据结构中树的遍历、平衡树的判断、二叉查找树的建立与维护以及图的遍历与最短路径求解等,动态规划、贪心算法等等,实在事太多太多,就不一一列举了。所以掌握好递归对于学习好算法与数据结构的重要性不言而喻。

二、初见递归

要想学好递归就得先了解函数调用在内存中的实现方式:函数调用是通过栈的方式实现的。递归简单来说就是在函数内部调用其本身,借用栈的特性,在一定的递归终止条件下,达到正向循环和反向循环的目的。这里有几个重点:1)函数调用与运行的顺序,2)递归终止条件。前者关系到我们在函数内部对数据进行操作的位置,后者关系到我们递归循环达到的范围。所以在书写递归函数之前,务必要将其弄清楚。

三、例子

例子:实现一个函数检查二叉树是否平衡。平衡树定义:任意一个节点,其两棵子树的高度差不超过1。

递归解法:如上所说,我们要解决的其实是找到每个节点左右子树最大高度,并进行比较。所以要利用递归解决问题,要先搞清楚递归的两个重点:1)函数调用于运行的顺序,2)递归终止条件。

对于前者,是由我们的目的决定的:我们想要得到每个节点对应的左右子树的高度。这种一般是在递归调用之后执行操作(包括树的高度计算,或者返回结果),可以对应到树的后序遍历。

对于后者,是由我们的数据结构特性决定的:树的叶子节点的左右左右孩子节点为空。其实到这里,利用递归算法解这个题的思路就已经形成了。

首先,在递归调用前,作出判断,如果当前节点是空,则返回0,表示当前节点为叶子节点,其对应子树高度为0。其次,分别将当前节点的左右孩子作为参数进行递归调用。最后,在递归调用后,比较前两个递归调用的返回值大小,并得到较大的,其即为当前节点的最大左右子树高度。并将最大高度加1并返回。完毕!

就是这么简单。其实完全文字叙述可能有点难以理解,不过如果有一定的基础,还是比较通俗易懂的。如果有时间,我以后会加上图解。

代码如下:

int getHeight(TreeNode *root){
if(root == null)
return 0;

int left = getHeight(root->left);
int right = getHeight(root->right);

int height = Math.max(left, right);

return height + 1;
}
最后这是对于一个节点的左右子树高度的判断,我们还需要对整棵树的每个节点进行判断。所以我们还需要如下代码:

bool checkBalanced(TreeNode *root){
if(root == null)
return true;
if(Math.abs(getHeight(root->left) - getHeight(root->right)) > 1)
return false;
return checkBalanced(root->left) && checkBalanced(root->right);
}


以上算法的时间复杂度是O(NlogN)。对该算法进行很小的修改即可达到线性时间O(N)的效果。大家可以自行思考。

四、再看递归

经过以上分析(其实这部分是要结合我设计的图来说明的,最近比较忙,图估计要晚些时候是上了,那就先完成这个部分吧。),我们可以看到,递归的一个比较重要的特性,栈的运行顺序。所以我们会在书写递归程序时有类似下面的总结:

1)首先弄清楚我们要对数据处理的顺序(对应于前面说的递归的第一个重点);

2)其次弄清楚数据结构的特性,或者说递归的终止条件(对应于前面说的递归的第二个重点);

3)在检查代码时,将每次递归过程想象成一个完全相同的(装了同样的代码,仅仅是传入的参数可能不同)矩形框,矩形框中的一行对应于函数中的一行执行代码。从每个递归调用出会衍生出新的矩形框;

4)在第一次达到递归终止条件时,代表该矩形框可以被抛弃(函数调用运行结束),并返回调用他的矩形框中,在调用处的下一行继续运行,直到返回。后续运行,依次类推。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  递归 算法 二叉树