【原创】递归算法的要素总结

8个月没维护过blog了,想想看也是经历了挺多的,学了不少东西。慢慢的把自己的一些心得和总结发出来,和大家分享和改正。

好了,上面是题外话,今天的主题是递归算法的实现要素和分析。

转载请注明出处,谢谢~

一、分析和总结

写好一个递归算法,我认为主要是把握好如下三个方面:

1. 提取重复的逻辑。

2. 控制逻辑边界。

3. 恰当的退出。

1. 重复逻辑

出现重复逻辑一定是必不可少的,因为递归的精髓就是loop。但是,重复的逻辑需要抽象。抽象出来一个干净利落的可循环逻辑对程序编写的帮助很大。下面将会提到一个例子,有更多分析。

2. 控制逻辑边界

控制边界保证了程序在正确的框架下运行。因为抽象出来的逻辑需要一个框架保证其可以递归执行,“刚刚好”是它的要点。写程序也经常因为边界把控的不准确容易留下bug。

那么如何正确控制边界?一个比较好的办法,就是对边界也进行逻辑上的递归。因为递归是层层相同,那么第一层和第k层是一致的,第一层容易抽象出执行编辑,那么可以抽象第k层的执行编辑。

3. 合适退出递归

递归的退出往往和逻辑边界是相辅相成的,这一点下面的例子也会提到。

一般递归的退出有两种表现形式:

1.下层递归边界检测不符退出。

特点是在递归代码的开始,会有边界控制。

2.本层递归检查边界。

特点是在进入下层递归时检查边界。

这两种方式最大的不同在于效率,因为每层递归会有对临时数据的保存,所以减少递归层数可以降低程序损耗。

三者关系:

2和3的根本在于1的抽象逻辑,一个好的递归不仅思想上干净利落,并且在代码表现上也是简单直接。

二、代码分析

这是leetcode上面的一道题,是根据中序和后序遍历的结果来恢复二叉树。

代码如下:

 1 /**
 2  * Definition for binary tree
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 8  * };
 9  */
10 class Solution {
11 public:
12     TreeNode *buildTree(vector<int> &inorder, vector<int> &postorder) {
13         if(inorder.size() == 0)
14             return NULL;
15         int PostPos = postorder.size()-1;
16         return buildBinaryTree(inorder, 0, inorder.size(), postorder, PostPos);
17     }
18
19     TreeNode *buildBinaryTree(vector<int> &inorder, int InPosHead, int InPosTail, vector<int> &postorder, int &PostPos){
20         TreeNode *node = new TreeNode(postorder[PostPos--]);
21         int i = InPosHead;
22         //find separate pos
23         for(; i < InPosTail; i++)
24             if(inorder[i] == node->val)
25                 break;
26         //recursion bulid
27         if(i < InPosTail-1)
28             node->right = buildBinaryTree(inorder, i+1, InPosTail, postorder, PostPos);
29         if(i > InPosHead)
30             node->left = buildBinaryTree(inorder, InPosHead, i, postorder, PostPos);
31         return node;
32     }
33 };

代码的思路是:根据后序排列来确定对应中序的根节点(1),然后构建右子树和左子树(2)。 (括号的数字代表上面的三个方面)

分析:

这句话中 ”构建右左子树“表明了可以通过递归逻辑实现,并确定了边界的抽象(2)。而”根据后序排列来确定对应中序的根节点“是对递归逻辑的抽象(1)。

可以看到,我在上面两句话中都刻意强调了”右左子树“,而不是”左右子树“,这是因为后序表的逆向排列正好代表了右树优先的根节点,所以先建右子树再创建左子树是简单直接的方法。这对2的边界控制很有帮助。(恰当的抽象)

并且逻辑退出(3)放到了同层递归检测,这不仅减少程序消耗而且显示表明了结束条件,对可读性也有帮助。

再贴一份代码,这个递归的抽象逻辑和我写的稍有不同(我的是优先构建右子树,这份是不分左右顺序),但是在边界控制稍显复杂(后序表的边界控制)。具体的分析留给大家了。

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    private int postPos;

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(inorder == null)
            return null;
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        for(int i=0; i<inorder.length; i++)
            map.put(inorder[i], i);
        return helper(inorder, 0, inorder.length-1, postorder, 0, postorder.length-1, map);
    }

    private TreeNode helper(int[] inorder, int inL, int inR,
                            int[] postorder, int postL, int postR, HashMap<Integer, Integer> map){
        if(inL > inR)
            return null;
        TreeNode root = new TreeNode(postorder[postR]);
        int index = map.get(root.val);
        root.left  = helper(inorder, inL, index-1, postorder, postL, postL+index-inL-1, map);
    root.right = helper(inorder, index+1, inR, postorder, postL+index-inL, postR-1, map);
        return root;
    }
}

最后,虽然递归有逻辑简单,代码清晰的优点,但是并不建议首先考虑用递归解决问题。好的程序还是需要通过深入解析写出更快捷、更巧妙的算法,而不是把问题交给机器暴力解决。当然递归加剪枝可以避开一些不必要的搜索,不过大部分还是有替代的办法。

希望能帮初学者对递归有个认识和理解,加深对计算机解题方式的理解。

时间: 2024-10-08 20:45:10

【原创】递归算法的要素总结的相关文章

算法设计方法:递归的内涵与经典应用

摘要: 大师 L. Peter Deutsch 说过:To Iterate is Human, to Recurse, Divine.中文译为:人理解迭代,神理解递归.毋庸置疑地,递归确实是一个奇妙的思维方式.对一些简单的递归问题,我们总是惊叹于递归描述问题的能力和编写代码的简洁,但要想真正领悟递归的精髓.灵活地运用递归思想来解决问题却并不是一件容易的事情.本文剖析了递归的思想内涵,分析了递归与循环的联系与区别,给出了递归的应用场景和一些典型应用,并利用递归和非递归的方式解决了包括阶乘.斐波那契

用例要素(非原创)

1.      用例名:执行者视角,动词 ( + 宾语) 2.      执行者:在系统之外,透过系统边界与系统进行有意义交互的任何事物 u  系统边界:责任边界,非物理边界 u  任何事物:操作员.维护员.外系统.外部因素.时间 3.      前置条件:开始用例前所必需的系统及其环境的状态 4.      涉众利益:用例平衡涉众之间的利益,是涉众之间达成的契约 5.      基本路径:把基本路径单独分离,凸显用例的核心价值 u  只书写"可观测"的,说人话 u  句子必须以执行者

[原创]Qt C++下进行QGIS二次开发打开S-57格式(*.000)电子海图数据,并设置多边形要素的显示风格

不过多的废话了,直接上源码: addChartlayers()方法时"打开海图"按钮的triggered()信号所绑定的槽函数. //添加海图数据小按钮槽函数 void MainWindow::addChartlayers() { m_mapCanvas->freeze(true);//冻结或解冻地图画布对象,frozen (true) or thawed (false). Default is true. //步骤1:打开文件选择对话框 QString filename=QFi

《zw版&#183;Halcon-delphi系列原创教程》 2d照片-3d逆向建模脚本

<zw版·Halcon-delphi系列原创教程> 2d照片-3d逆向建模脚本 3D逆向建模,是逆向工程的核心要素.       3D逆向建模,除了目前通用的3D点云模式,通过2D图像实现快速3D建模,也是目前的重要手段.       2D图像的3D逆向建模,目前常用的有两种模式,一个是左右视距(或多角度取景)图片叠加处理,google的卫星地图3D化,就是这个模式.       另外一种,就是本文要介绍的3D定标模式(handeye??模式),就是在现场先拍摄一张标准3D定标图片,获取定位参

递归算法经典实例小结(C#实现)

 一 .递归算法简介 在数学与计算机科学中,递归是指在函数的定义中使用函数自身的方法. 递归算法是一种直接或者间接地调用自身算法的过程.在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解.递归算法解决问题的特点: (1) 递归就是在过程或函数里调用自身. (2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口. (3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低.所以一般不提倡用递归算法设计程序. (4) 在递归调用的过程当中

转--C++学习笔记(原创)

http://www.cnblogs.com/maowang1991/p/3290321.html 以下内容为自己一年多的C++学习心得,纯原创,转载请注明源地址. 一年多的C++学习过程中,自己阅读了很多C++经典著作,有<effective c++>,<more effective c++>,<c++ primer>等,每次阅读著作都会总结一些心得,现在拿出来和大家分享一下. 1.struct成员默认访问方式是public,而 class默认访问方式是private

二叉树3种遍历的非递归算法

http://blog.csdn.net/pipisorry/article/details/37353037 c实现: 1.先序遍历非递归算法 #define maxsize 100 typedef struct { Bitree Elem[maxsize]; int top; } SqStack; void PreOrderUnrec(Bitree t) { SqStack s; StackInit(s); p=t; while (p!=null || !StackEmpty(s)) { w

【Java笔记】——有趣的递归算法

在Java学习开始就学习到了递归,以前经常听到递归,但是却没有真正的了解过递归.学习是不断的重复的,在最初的时候,自己所听说到的不了解的,以后肯定会学到.但是前期这个了解的阶段是不可少的,现在体会是越来越深了.这篇博客就简单介绍一下递归算法,首先介绍什么是递归,然后是递归算法的代码展示,最后呈现递归的结果. 什么是递归 定义 递归是自身调用的一种编程技巧,递归作为一种算法在程序设计语言中广泛应用. 优点 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层

【数据结构】递归算法—汉诺塔

汉诺塔的问题,也是一个经典的递归算法问题. 下面是自己总结的一张整体流程图. 下面是代码,代码虽简单,但理解其内部运行原理很重要. //====================================================================== // // Copyright (C) 2014-2015 SCOTT // All rights reserved // // filename: HanNuoTa.c // description: a demo to