`

Java 尾递归

阅读更多
递归与尾递归

关于递归操作,相信大家都已经不陌生。简单地说,一个函数直接或间接地调用自身,是为直接或间接递归。例如,我们可以使用递归来计算一个单向链表的长度:

package com.jiaozg.algorithm.tailrecursively;

public class Node {
	
	private int value;
	private Node next;
	
	public Node(int value, Node next) {
		this.value = value;
		this.next = next;
	}

	public int getValue() {
		return value;
	}

	public Node getNext() {
		return next;
	}
}


编写一个递归的GetLength方法:
public static int GetLengthRecursively(Node head)
	{
	    if (head == null) return 0;
	    return GetLengthRecursively(head.getNext()) + 1;
	}

在调用时,GetLengthRecursively方法会不断调用自身,直至满足递归出口。对递归有些了解的朋友一定猜得到,如果单项链表十分长,那么上面这个方法就可能会遇到栈溢出,也就是抛出StackOverflowException。这是由于每个线程在执行代码时,都会分配一定尺寸的栈空间(Windows系统中为1M),每次方法调用时都会在栈里储存一定信息(如参数、局部变量、返回地址等等),这些信息再少也会占用一定空间,成千上万个此类空间累积起来,自然就超过线程的栈空间了。不过这个问题并非无解,我们只需把递归改成如下形式即可(在这篇文章里我们不考虑非递归的解法):
public static int GetLengthTailRecursively(Node head, int acc)
	{
	    if (head == null) return acc;
	    return GetLengthTailRecursively(head.getNext(), acc + 1);
	}

GetLengthTailRecursively方法多了一个acc参数,acc的为accumulator(累加器)的缩写,它的功能是在递归调用时“积累”之前调用的结果,并将其传入下一次递归调用中——这就是GetLengthTailRecursively方法与GetLengthRecursively方法相比在递归方式上最大的区别:GetLengthRecursive方法在递归调用后还需要进行一次“+1”,而GetLengthTailRecursively的递归调用属于方法的最后一个操作。这就是所谓的“尾递归”。与普通递归相比,由于尾递归的调用处于方法的最后,因此方法之前所积累下的各种状态对于递归调用结果已经没有任何意义,因此完全可以把本次方法中留在堆栈中的数据完全清除,把空间让给最后的递归调用。这样的优化1便使得递归不会在调用堆栈上产生堆积,意味着即时是“无限”递归也不会让堆栈溢出。这便是尾递归的优势。

有些朋友可能已经想到了,尾递归的本质,其实是将递归方法中的需要的“所有状态”通过方法的参数传入下一次调用中。对于GetLengthTailRecursively方法,我们在调用时需要给出acc参数的初始值:
GetLengthTailRecursively(head, 0)

为了进一步熟悉尾递归的使用方式,我们再用著名的“菲波纳锲”数列作为一个例子。传统的递归方式如下:

如果对“菲波纳锲”不熟的话,可以参考我以前写的一篇博文http://jiaozhiguang-126-com.iteye.com/blog/1678806
public static int FibonacciRecursively(int n)
{
    if (n < 2) return n;
    return FibonacciRecursively(n - 1) + FibonacciRecursively(n - 2);
}

而改造成尾递归,我们则需要提供两个累加器:
public static int FibonacciTailRecursively(int n, int acc1, int acc2)
{
    if (n == 0) return acc1;
    return FibonacciTailRecursively(n - 1, acc2, acc1 + acc2);
}


参考:http://www.cnblogs.com/JeffreyZhao/archive/2009/03/26/tail-recursion-and-continuation.html
分享到:
评论

相关推荐

    Java8使用lambda实现Java的尾递归

    主要介绍了Java8使用lambda实现Java的尾递归的相关资料,需要的朋友可以参考下

    jvm-tail-recursion:Java字节码中的尾部递归调用的优化器库

    例子倒数至零一个简单的尾递归函数,倒数为零:前后static void count( int n) { if (n == 0 ) { return ; } count(n - 1 ); } static void count( int n) { while ( true ) { if (n == 0 ) { return ; } n = n - 1 ...

    javaee笔试题-JavaExercise:Java学习的代码,包括设计模式、se、ee以及相关测试代码

    尾递归 Clojure 算法 动态规划 图 链表 树 Java Java SE 源码实现(设计模式) Collection ArrayList LinkedList Set HashSet TreeSet Map HashMap 泛型 Iterator 线程 反射 虚拟机 Java8 新特性 Java EE 源码 JUnit...

    JVM平台上的Scheme语言实现JSchemeMin.zip

    作为R7RS的实现,JSchemeMin支持Scheme的所有标准特性,包括头等公民地位的过程、尾递归优化、继续、用户定义记录、库(包括R7RS附录A中全部语法和过程,不只base)、异常和健康宏展开。作为基于JVM的实现,...

    优雅的使用javascript递归画一棵结构树示例代码

    递归和尾递归 简单的说,递归就是函数自己调用自己,它做为一种算法在程序设计语言中广泛应用。其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。一般来说,递归需要有边界条件...

    nqueens-local-search:Java中的局部搜索和模拟退火算法

    N皇后区用模拟退火求解随机板: $ javac NQueens.java$ java NQueens要使用... 你能在 Java 中实现尾调用递归吗? 空间复杂度=邻域的大小? 时间 = 您必须查看的最坏情况板数。 这对于不完整算法来说是什么?模拟退火

    venice:威尼斯,一种受Clojure启发的沙盒Lisp方言,具有Java互操作性,可作为安全的脚本语言

    Venice支持宏,尾递归,动态代码加载,动态(线程本地)绑定。 它具有出色的Java互操作性,以及可配置的沙箱,它可以防止各种危险的JVM交互,例如读/写文件,调用System.exit(0)或任何其他恶意操作。 威尼斯是...

    判断青蛙过河leetcode-ads_java:算法和数据结构

    判断青蛙过河leetcode ads_java Algorithms and Data Structures 参考资料 《数据结构与算法经典问题解析-Java语言描述 第二版》 《数据结构与算法分析-Java语言描述 ...从尾到头打印链表 链表中倒数第k个结点 反转

    datastructure:基于java语言的数据结构及算法实现

    栈和队列2-1 栈的基本实现2-2 栈的另一个应用:括号匹配2-3 数组队列Java2-4 循环队列Java第三章 最基础的动态数据结构:链表章节Java源码3-1 链表的基本实现Java3-2 使用链表实现栈Java3-3 带有尾指针的链表:使用...

    data-structures:用Java实现的所有数据结构

    链表单链表创建,显示和遍历插入开始时最后在指定位置之后删除中开始时最后从指定位置长度迭代式递归的撤销迭代式递归的通报链表使用头和尾指针创建,显示和遍历仅使用尾指针创建,显示和遍历插入开始时最后在指定...

    fibonacci:比较Java中的三种不同的Fibonacci实现

    斐波那契 这是一个简单的程序,可用于比较Java中Fibonacci算法的3种不同实现,以了解计算时间。...尾递归斐波那契 迭代斐波那契 结果表明,迭代一次是最快的,而递归一次是最慢的。 因此,递归并不总是最好的。

    java简易版开心农场源码-training.computerscience.algorithms-datastructures:培训.计算机

    java简易版开心农场开源算法和数据结构 我在 UC San Diego MicroMasters ...堆栈优化和尾递归: 这是一个递归,其中递归调用是递归函数中的最后一条指令 函数中应该只有一个递归调用 它免于系统堆栈开销

    99乘法表java源码-learn-lua:学习lua

    函数是一等类型,支持匿名函数和正则尾递归(proper tail recursion); 支持词法定界(lexical scoping)和闭包(closure); 提供thread类型和结构化的协程(coroutine)机制,在此基础上可方便实现协作试多任务; 运行...

    剑指offer(java版67题)

    面试题 3:从尾到头打印链表(考点: 链表) 2 面试题 4:重建二叉树(考点: 树) 4 面试题 5:用两个栈实现队列(考点: 栈和队列) 5 面试题 6:旋转数组的最小数字(考点:查找和排序) 6 面试题 7:斐波那契数列...

    leetcode三角形打印-DataStructuresInJava:用Java实现各种数据结构和算法

    使用带有尾指针的单向链表的队列实现。 二分搜索(不是真正的数据结构,但我猜还可以) 迭代和递归 二叉搜索树 第一次使用泛型! 哈希表 添加,获取,删除,散列(键)。 使用了非常简单的散列。 下一步可能是使用...

    JSchemeMin是一个JVM平台上的Scheme语言实现

    JSchemeMin 是一个JVM平台上... 作为R7RS的实现,JSchemeMin支持Scheme的所有标准特性,包括头等公民地位的过程、尾递归优化、继续、用户定义记录、库(包括R7RS附录A中全部语法和过程,不只base)、异常和健康宏展开。

    leetcode手册JAVA-swiftreactivepro:swiftreactivepro

    leetcode手册JAVA 没有通往斯威夫特的王道。 ——欧几里得 指数 函数式编程 声明式编程 Swift 中的现代声明式编程 什么是 Swift 中的函数式编程? 函数式编程示例 让 Swift 更实用 Swift 函数式编程简介 Swift Talks...

    快学 scala 中文版 带完整目录

    15.6.1 尾递归 .250 15.6.2 跳转表生成与内联 252 15.6.3 可省略方法 253 15.6.4 基本类型的特殊化 254 15.7 用于错误和警告的注解 255 练习 256 第16章 XML处理 A2 259 16.1 XML字面量 260 16.2 XML节点 ...

    leetcode添加元素使和等于-leetcode_record:leetcode刷题题解,基于java实现

    输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回) 链表操作 1 07 重建二叉树 输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树 递归 树 2 09 用两个栈实现队列 用两个栈实现一个队列 栈...

    C语言解析教程(原书第4版)(美) 凯利.pdf

    1.10.3 输入文件尾标志 1.10.4 输入和输出的重定向 1.11 总结 1.12 练习 第2章 词法元素、操作符和c系统 2.1 字符和词法元素 2.2 语法规则 2.3 注释 2.4 关键字 2.5 标识符 2.6 常量 2.7 字符串常量 2.8 操作符和...

Global site tag (gtag.js) - Google Analytics