java递归简单例子_尾递归优化

上一篇文章《谈递归时,我们在谈论什么》,我们讲解了递归调用的过程是怎么样的。还有这篇文章:《面试官的一次面试经历分享》里谈到了尾递归的问题。我们今天就来讲一下尾递归的事情。

在scala编程中,或者说在函数式编程中,是鼓励大家使用递归,而不是循环来解决问题。这是因为循环会引入变量,而变量是函数式编程中被视为洪水猛兽一样的存在。那是另外一个话题,在我的scala课程中会详细讲解。这里就不多做解释了。我们还是聚集本节课的重点,如何写一个高性能的递归。

先来看一下什么是尾递归。如果调用者在调用一个递归函数并且取得返回值以后,不再进行其他的操作,而是直接将这个值返回出去,那么这就是一个尾递归。拿面试经历分享的那个例子来说,

int fib(int n) {
    if (n < 2)
        return 1;
    else
        return fib(n - 1) + fib(n - 2);
}

在调用了fib(n-1)和fib(n-2)之后,还要进行一次求和计算。那么,这就不是一个尾递归。而我的另一个写法:

public 

在递归调用了fib函数以后,没有其他任何的计算,直接就将调用得到的返回值return出去了。这就是一种尾递归的写法。

尾递归有什么好处呢?通过对递归调用过程的分析,我们知道了,每次调用一个函数的时候,系统都会重新开辟一个与这个函数相对应的栈帧。可以想象,如果递归调用的深度比较大,栈帧会开辟很多,一来是浪费空间,二来性能也必然会下降很多(有很多读写内存操作)。相反,如果使用循环,则只在一个函数栈空间里,不会开辟更多的空间,所以使用循环,性能要远远好于递归。

但是函数式编程语言中(例如Haskell),没有for, while这样的关键字,也没有变量这种东西,所有的循环操作都要靠递归来实现。这就带来了性能上的矛盾。为了解决这个问题,编译器大多提供一种能力:在把源码编译成机器码的时候,用户编写的尾递归程序会被转变成循环。这是编译器自动去做的,用户并不感知。我们拿scala来验证一下。

先看简单递归版本:

def 

会打出来很多调用栈,因为要执行多次递归。例如:

java

可以看到,在运行过程中,创建了很多fib函数的栈。尾递归版本的结果又如何呢?

def 

这次的执行结果,只有一次打印:

java

也就是说,这个递归并没有真正展开成普通的递归,而是转成循环在执行了。整个过程没有引入一个变量。完美。

搞明白了尾递归是什么,我们才完成了一半,还要看一下,是不是所有的循环都很方便地改成尾递归呢?我们看第二部分:

循环改成尾递归

把循环改成尾递归,有一个通用的技巧,那就是把循环的写法中,需要使用变量的地方,都改成递归参数。在计算时需要使用的变量并不会变少,但我们却不再操心变量的赋值。再以fibnacci函数举例,循环版本是这么写的:

int fib(int n) {
    int a = 1, b = 0;
    for (int i = 0; i < n; i++) {
        int t = b;
        b = a;
        a = a + t;
    }
    return a;
}

就是说,循环过程中,我们需要a, b 两个变量,那我们就把这两个变量转为递归函数的参数就可以了。

同样的办法,我们再来改写一下,面试经历分享这篇文章中power函数。先写它的循环版本:

public 

我们看到,在递归的过程中,使用了三个变量result, t 和 i,那么我们的尾递归函数在定义的时候就带上这三个参数即可。

def pow_helper(n : Int, r : Double, t : Double, i : Int) : Double = {
    if (n < i) {
        r 
    }   
    else {
        if ((n & i) > 0)
            pow_helper(n, r * t, t * t, i << 1); 
        else
            pow_helper(n, r, t * t, i << 1); 
    }   
}

def pow_2(m : Double, n : Int) : Double = {
    return pow_helper(n, 1.0, m, 1);
}

由于计算过程中,m不再起作用,所以我们在定义pow_helper的时候,就把m省略掉了。对比一下,循环实现和尾递归实现,你会发现,result, t, i 这三个变量只是换了一种形式,以前是局部变量,现在则是函数参数。

这是把循环改成尾递归的通用方法,如果循环本身逻辑就很复杂,改写起来可能也会变得很复杂。这个要根据具体情况来具体分析。

另外,一个问题的尾递归写法也有很多种写法,比如power函数,还可以这么写:

// 这是一种完全的尾递归:即递归函数返回以后,不需要再做任何计算了。
// 编译器可以将这个函数转成完全
// 的循环计算。而不是递归计算。
def pow2(x : Double, y : Double, n : Int) : Double = {
        if (n == 0)
                y
        else if ((n & 1) == 1)
                pow2 (x, x * y, n - 1);
        else
                pow2 (x * x, y, n / 2);
}

看懂上面的程序,做为今天的家庭作业吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
无限级树(Java递归) 2007-02-08 10:26 这几天,用java写了一个无限极的树,递归写的,可能代码不够简洁,性能不够好,不过也算是练习,这几天再不断改进。前面几个小图标的判断,搞死我了。 package com.nickol.servlet; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.nickol.utility.DB; public class category extends HttpServlet { /** * The doGet method of the servlet. * * This method is called when a form has its tag value method equals to get. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("utf-8"); response.setContentType("text/html"); PrintWriter out = response.getWriter(); out .println(""); out.println(""); out.println(" Category" + "" + "body{font-size:12px;}" + "" + "" + ""); out.println(" "); out.println(showCategory(0,0,new ArrayList(),"0")); out.println(" "); out.println(""); out.flush(); out.close(); } public String showCategory(int i,int n,ArrayList frontIcon,String countCurrent){ int countChild = 0; n++; String webContent = new String(); ArrayList temp = new ArrayList(); try{ Connection conn = DB.GetConn(); PreparedStatement ps = DB.GetPs("select * from category where pid = ?", conn); ps.setInt(1, i); ResultSet rs = DB.GetRs(ps); if(n==1){ if(rs.next()){ webContent += "";//插入结尾的减号 temp.add(new Integer(0)); } webContent += " ";//插入站点图标 webContent += rs.getString("cname"); webContent += "\n"; webContent += showCategory(Integer.parseInt(rs.getString("cid")),n,temp,"0"); } if(n==2){ webContent += "\n"; }else{ webContent += "\n"; } while(rs.next()){ for(int k=0;k<frontIcon.size();k++){ int iconStatic = ((Integer)frontIcon.get(k)).intValue(); if(iconStatic == 0){ webContent += "";//插入空白 }else if(iconStatic == 1){ webContent += "";//插入竖线 } } if(rs.isLast()){ if(checkChild(Integer.parseInt(rs.getString("cid")))){ webContent += "";//插入结尾的减号 temp = (ArrayList)frontIcon.clone(); temp.add(new Integer(0)); }else{ webContent += "";//插入结尾的直角 } }else{ if(checkChild(Integer.parseInt(rs.getString("cid")))){ webContent += "";//插入未结尾的减号 temp = (ArrayList)frontIcon.clone(); temp.add(new Integer(1)); }else{ webContent += "";//插入三叉线 } } if(checkChild(Integer.parseInt(rs.getString("cid")))){ webContent += " ";//插入文件夹图标 }else{ webContent += " ";//插入文件图标 } webContent += rs.getString("cname"); webContent += "\n"; webContent += showCategory(Integer.parseInt(rs.getString("cid")),n,temp,countCurrent+countChild); countChild++; } webContent += "\n"; DB.CloseRs(rs); DB.ClosePs(ps); DB.CloseConn(conn); }catch(Exception e){ e.printStackTrace(); } return webContent; } public boolean checkChild(int i){ boolean child = false; try{ Connection conn = DB.GetConn(); PreparedStatement ps = DB.GetPs("select * from category where pid = ?", conn); ps.setInt(1, i); ResultSet rs = DB.GetRs(ps); if(rs.next()){ child = true; } DB.CloseRs(rs); DB.ClosePs(ps); DB.CloseConn(conn); }catch(Exception e){ e.printStackTrace(); } return child; } } --------------------------------------------------------------------- tree.js文件 function changeState(countCurrent,countChild){ var object = document.getElementById("level" + countCurrent + countChild); if(object.style.display=='none'){ object.style.display='block'; }else{ object.style.display='none'; } var cursor = document.getElementById("cursor" + countCurrent + countChild); if(cursor.src.indexOf("images/tree_minus.gif")>=0) {cursor.src="images/tree_plus.gif";} else if(cursor.src.indexOf("images/tree_minusbottom.gif")>=0) {cursor.src="images/tree_plusbottom.gif";} else if(cursor.src.indexOf("images/tree_plus.gif")>=0) {cursor.src="images/tree_minus.gif";} else {cursor.src="images/tree_minusbottom.gif";} var folder = document.getElementById("folder" + countCurrent + countChild); if(folder.src.indexOf("images/icon_folder_channel_normal.gif")>=0){ folder.src = "images/icon_folder_channel_open.gif"; }else{ folder.src = "images/icon_folder_channel_normal.gif"; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值