首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

Java解决递归造成的堆栈溢出问题

编程知识
2024年08月13日 16:19

在Java中,递归造成的堆栈溢出问题通常是因为递归调用的深度过大,导致调用栈空间不足。解决这类问题的一种常见方法是使用非递归的方式重写算法,即使用迭代替代递归。

1.方法一:非递归的方式重写算法(迭代替代递归)

下面通过一个典型的递归例子——计算斐波那契数列的第n项,来演示如何用迭代的方式避免堆栈溢出。

1.1递归版本的斐波那契数列

递归版本的斐波那契数列实现很简单,但是效率较低,尤其是对于大的n值,很容易造成堆栈溢出。

public class FibonacciRecursive {  
    public static int fibonacci(int n) {  
        if (n <= 1) {  
            return n;  
        } else {  
            return fibonacci(n - 1) + fibonacci(n - 2);  
        }  
    }  
  
    public static void main(String[] args) {  
        int n = 40; // 尝试较大的数,比如40,可能会导致堆栈溢出  
        System.out.println("Fibonacci(" + n + ") = " + fibonacci(n));  
    }  
}

1.2迭代版本的斐波那契数列

迭代版本的斐波那契数列避免了递归调用,因此不会造成堆栈溢出。

public class FibonacciIterative {  
    public static int fibonacci(int n) {  
        if (n <= 1) {  
            return n;  
        }  
        int a = 0, b = 1;  
        for (int i = 2; i <= n; i++) {  
            int temp = a + b;  
            a = b;  
            b = temp;  
        }  
        return b;  
    }  
  
    public static void main(String[] args) {  
        int n = 90; // 即使n很大,也不会导致堆栈溢出  
        System.out.println("Fibonacci(" + n + ") = " + fibonacci(n));  
    }  
}

在迭代版本中,我们使用了两个变量ab来保存斐波那契数列中的连续两个数,通过循环来计算第n项的值。这种方法避免了递归调用,因此不会造成堆栈溢出,即使n的值很大。

1.3小结

通过迭代替代递归是解决递归造成的堆栈溢出问题的常用方法。在实际开发中,如果递归深度可能非常大,建议首先考虑使用迭代的方式来实现算法。

2.方法二:尾递归优化

尾递归是一种特殊的递归形式,递归调用是函数的最后一个操作。在支持尾递归优化的编程语言中(如Scala、Kotlin的某些情况下,以及通过编译器优化或特定设置的Java),尾递归可以被编译器优化成迭代形式,从而避免堆栈溢出。

然而,标准的Java编译器并不自动进行尾递归优化。但是,我们可以手动将递归函数改写为尾递归形式,并使用循环来模拟递归调用栈。

以下是一个尾递归优化的斐波那契数列示例,但请注意,Java标准编译器不会优化此代码,所以这里只是展示尾递归的形式。实际上,要避免Java中的堆栈溢出,还是需要手动将其改写为迭代形式或使用其他技术。

public class FibonacciTailRecursive {  
    public static int fibonacci(int n, int a, int b) {  
        if (n == 0) return a;  
        if (n == 1) return b;  
        return fibonacci(n - 1, b, a + b); // 尾递归调用  
    }  
  
    public static void main(String[] args) {  
        int n = 40; // 在标准Java中,这仍然可能导致堆栈溢出  
        System.out.println("Fibonacci(" + n + ") = " + fibonacci(n, 0, 1));  
    }  
}

实际上,在Java中避免堆栈溢出的正确方法是使用迭代,如之前所示。

3.方法三:使用自定义的栈结构

另一种方法是使用自定义的栈结构来模拟递归过程。这种方法允许你控制栈的大小,并在需要时增加栈空间。然而,这通常比简单的迭代更复杂,且不太常用。

以下是一个使用自定义栈来计算斐波那契数列的示例:

import java.util.Stack;  
  
public class FibonacciWithStack {  
    static class Pair {  
        int n;  
        int value; // 用于存储已计算的值,以避免重复计算  
  
        Pair(int n, int value) {  
            this.n = n;  
            this.value = value;  
        }  
    }  
  
    public static int fibonacci(int n) {  
        Stack<Pair> stack = new Stack<>();  
        stack.push(new Pair(n, -1)); // -1 表示值尚未计算  
  
        while (!stack.isEmpty()) {  
            Pair pair = stack.pop();  
            int currentN = pair.n;  
            int currentValue = pair.value;  
  
            if (currentValue != -1) {  
                // 如果值已经计算过,则直接使用  
                continue;  
            }  
  
            if (currentN <= 1) {  
                // 基本情况  
                currentValue = currentN;  
            } else {  
                // 递归情况,将更小的n值压入栈中  
                stack.push(new Pair(currentN - 1, -1));  
                stack.push(new Pair(currentN - 2, -1));  
            }  
  
            // 存储计算过的值,以便后续使用  
            stack.push(new Pair(currentN, currentValue));  
        }  
  
        // 栈底元素存储了最终结果  
        return stack.peek().value;  
    }  
  
    public static void main(String[] args) {  
        int n = 40;  
        System.out.println("Fibonacci(" + n + ") = " + fibonacci(n));  
    }  
}

在这个示例中,我们使用了一个栈来模拟递归过程。每个Pair对象都存储了一个n值和一个对应的斐波那契数值(如果已计算的话)。我们通过将较小的n值压入栈中来模拟递归调用,并在需要时从栈中取出它们来计算对应的斐波那契数值。这种方法允许我们控制栈的使用,并避免了递归造成的堆栈溢出问题。

From:https://www.cnblogs.com/TS86/p/18357391
本文地址: http://shuzixingkong.net/article/1061
0评论
提交 加载更多评论
其他文章 Cookie、localStorage 和 sessionStorage 的区别及应用实例
在前端开发中,持久化数据存储是一个非常常见的需求。为了实现这一点,浏览器提供了多种方式,包括 Cookie、localStorage 和 sessionStorage。这三者各有优劣,适用于不同的场景 1. Cookie Cookie 是浏览器存储少量数据的一种机制,通常由服务器生成并发送到客户端。
Cookie、localStorage 和 sessionStorage 的区别及应用实例 Cookie、localStorage 和 sessionStorage 的区别及应用实例
《熬夜整理》保姆级系列教程-玩转Wireshark抓包神器教程(4)-再识Wireshark
1.简介 按照以前的讲解和分享路数,宏哥今天就应该从外观上来讲解WireShark的界面功能了。 2.软件界面 由上到下依次是标题栏、主菜单栏、主菜单工具栏、显示过滤文本框、打开区、最近捕获并保存的文件、捕获区、捕获过滤文本框、本机所有网络接口、学习区及用户指南等。 2.1启动界面 首次打开启动 W
《熬夜整理》保姆级系列教程-玩转Wireshark抓包神器教程(4)-再识Wireshark 《熬夜整理》保姆级系列教程-玩转Wireshark抓包神器教程(4)-再识Wireshark 《熬夜整理》保姆级系列教程-玩转Wireshark抓包神器教程(4)-再识Wireshark
python连接钉钉自动化提交OA审批
一、准备工作 1、安装阿里云支持包,点击跳转:https://open.dingtalk.com/document/resourcedownload/download-server-sdk 2、注册钉钉开发者账号,点击链接:https://open.dingtalk.com/ 3、获取AK,SK 4
Python网页应用开发神器fac 0.3.0全新版本发布
大家好我是费老师,在Python生态中,有很多以Python为主要开发语言,实现网页应用开发的框架,其中最为知名的有Dash、flet、streamlit、gradio、nicegui等。 如果综合考虑流行度、开发效率、开发自由度、相关生态成熟度、可拓展性、安全性等各方面的能力,Dash是其中天花板
Python网页应用开发神器fac 0.3.0全新版本发布 Python网页应用开发神器fac 0.3.0全新版本发布 Python网页应用开发神器fac 0.3.0全新版本发布
Jenkins配置分布式构建环境——添加固定Agent并使用JNLP启动Agent详解
1、概述 在《Jenkins部署架构概述&#160;》这篇博文中对Jenkins部署架构进行了讲解。对于分布式架构,Jenkins包括固态Agent和动态Agent两种方案。 固定Agent(常用于虚拟机):Agent容器一直运行,任务构建完成后不会销毁,创建完成后将一直占用集群资源,配置过程较简单
Jenkins配置分布式构建环境——添加固定Agent并使用JNLP启动Agent详解 Jenkins配置分布式构建环境——添加固定Agent并使用JNLP启动Agent详解 Jenkins配置分布式构建环境——添加固定Agent并使用JNLP启动Agent详解
我们常用的地铁卡/银行卡,竟然运行着一个 Java 虚拟机
我们日常使用 NFC 卡可以用来刷地铁,进出门禁,但是你有没有想过, 当我们使用一个 NFC 的 IC 卡刷卡进入地铁的时候,此时系统是如何知道我这个卡上有多少充值余额的? 这个薄薄的 NFC 卡到底有什么魔力,除了可以刷卡进地铁,还可以去银行取钱,进出小区门禁。 今天我在看到一些物联网的知识时,在
DRM:清华提出无偏差的新类发现与定位新方法 | CVPR 2024
论文分析了现有的新类别发现和定位(NCDL)方法并确定了核心问题:目标检测器往往偏向已知的目标,忽略未知的目标。为了解决这个问题,论文提出了去偏差区域挖掘(DRM)方法,以互补的方式结合类无关RPN和类感知RPN进行目标定位,利用未标记数据的半监督对比学习来改进表征网络,以及采用简单高效的mini-
DRM:清华提出无偏差的新类发现与定位新方法 | CVPR 2024 DRM:清华提出无偏差的新类发现与定位新方法 | CVPR 2024 DRM:清华提出无偏差的新类发现与定位新方法 | CVPR 2024
.NET周刊【8月第1期 2024-08-04】
国内文章 EF Core性能优化技巧 https://www.cnblogs.com/baibaomen-org/p/18338447 这篇文章介绍了在代码层面上优化EF Core实例池和拆分查询的方法。首先,文章建议使用DbContext实例池来重复利用实例,避免资源浪费,并提供相关使用示例。其次
.NET周刊【8月第1期 2024-08-04】 .NET周刊【8月第1期 2024-08-04】 .NET周刊【8月第1期 2024-08-04】