【问题标题】:Java: trouble understanding recursive method callingJava:难以理解递归方法调用
【发布时间】:2018-10-17 23:01:12
【问题描述】:

我制作了一个类似“Oregon Trail”的游戏,它使用“游戏结束”的方法来询问用户是否想再玩一次。

主要问题: 我的老师提到了一些模糊的东西,如果游戏循环足够多次,我们最终会得到一个 stackOverflow。这对我来说很有意义,因为游戏继续按照我的方式将方法嵌套在彼此内部,每次调用“新游戏”方法时都会添加到堆栈中,因为外部方法仍在等待完成。

我总结了一个例子来说明我的意思。假设用户输入有暂停等,我应该如何确保我的内存利用率不会随着我在其他方法中调用方法而不断增长?我认为这个词是“递归的”,这就是我的标题。

如果有人能推荐正确的处理方式,我将不胜感激。

public class Testing
{
    public static void main(String[] args) {
        System.out.println("main method");
        gameStart();
    }
    
    private static void gameStart()
    {
        System.out.println("some other method called");
        gameOver();
    }
    
    private static void gameOver()
    {
        System.out.println("game over called"); //I would ask the user if they want to play again.
        //keeping it concise to illustrate my point, instead of using an if statement
        gameStart();//starting the cycle I'm concerned about. Assume the user indicated they would like to play again.
    }
}

【问题讨论】:

  • 你必须在赋值中使用递归还是允许使用循环?因为在我看来,使用循环是处理这种情况最简单、最“自然”的方式。
  • 我会用循环解决这个特殊问题,而不用担心递归。现在,我们是否使用 LISP...
  • 您可以使用 try-catch 和 StackOverflowError,然后在 catch 块中处理溢出。但是,捕获错误是不好的做法,所以我不建议这样做。
  • 我必须使用递归。我制作的程序中有一堆循环,但我们应该这样做,而不是把整个游戏放在一个while或for循环中。
  • 对了,谢谢大家帮助我。我只是想弄清楚如何在不重载堆栈的情况下继续调用相互调用的方法。比如,当我以某种方式调用新方法时,我可以清除堆栈,保留全局变量吗?

标签: java recursion methods


【解决方案1】:

递归需要一个不会继续调用的条件。
递归最常见于方法调用自身的地方,例如计算斐波那契数列,其中

fib(n) == fib(n-1) + fib(n-2)

fib(0) 定义为 0,因此您不必计算。
fib(1) 定义为 1,因此您不必计算。
每隔一个数字由调用自身的fib() 方法计算两次,但它避免了对两个定义的情况进行递归调用,其中没有什么可计算的。在伪代码中

int fib(int n)
{
    if (n == 0) return 0; // doesnt have to recursively call
    if (n == 1) return 1; // same
    return fib(n-1) + fib(n-2);
}

在您的情况下,您有两个方法可以相互调用,但是您没有条件可以使调用从中逃脱。

有一种可能是gameOver() 仅在比赛以平局结束时调用gameStart(),类似于

public class Testing
{
    public static void main(String[] args) {
        System.out.println("main method");
        gameStart();
    }

    private static void gameStart()
    {
        System.out.println("some other method called");
        gameOver();
    }

    private static void gameOver()
    {
        System.out.println("game over called");
        if (gameTied()) {
            gameStart();
        }
    }
}

如果你只是问“你想再玩一次吗?” -- 最好在main 中完成,类​​似于

public static void main(String[] args) {
    System.out.println("main method");
    String playGame = "Yes";
    while (playGame.equalsIgnoreCase("Yes") {
        gameStart();
        playGame = ask("Play again?");
    }
}

【讨论】:

  • 这很有意义。感谢你们对我的帮助。我真诚地感谢它。您正在等待 gameStart() 完成以测试再次播放的条件。非常感谢你帮助我。我仍然想知道是否可以启动一个方法并取消它嵌套的方法,但我认为这是不可能的。
【解决方案2】:

为了避免无限递归,您可以切换到迭代并为当前决定如何继续的那些方法引入返回值(当前通过直接调用相应的操作)。 让这些方法返回一些符号接下来要做什么,例如使用枚举。 然后编写一个循环,根据返回值调用正确的方法。

示例(缩写,我假设您知道 Java 语法):

enum Action { Start, ShowEnd, Quit }

主要:

Action nextAction = Action.Start;
while (action != Action.Quit)
{
    switch (action)
    {
        case Start:
            nextAction = gameStart();
            break;
        case ShowEnd:
            nextAction = gameEnd();
            break;
        // ToDo: write more actions!
        default:
            break;
    }
}

这假定每个这样的方法都会执行,直到决定下一步要采取的行动为止。

这样你的调用堆栈总是很平坦,因为执行总是返回到主方法,然后分支到其他方法。

【讨论】:

  • 并不完全理解这个答案。但是,我致力于理解您所写的内容。到目前为止,我对 java 语法非常了解,但我不明白你缩写的“枚举动作”行。那应该是不同的班级吗?我还没有开始使用多个课程,但我明天可以到那里。看了5分钟,没看懂。我向你保证,我想了解你所做的一切。待定...
  • 这对我很重要。但是,我还没有了解您所缩写的概念。我只想说谢谢你花时间回答我。你真好。
  • 好的,枚举在这里不是必需的。您可以使用简单的数字来指示下一个操作(数字不像枚举那样具有描述性) - 将Action.StartStart 替换为1,将ShowEnd 替换为2,将Action.Quit 替换为3 .如果用户决定再次播放,您的方法gameEnd() 应该返回1(意思是开始),如果用户想退出,它应该返回3(意思是退出)。 main 方法将在下一次循环迭代中进行评估并执行所需的操作。
  • 在现实世界中,这对应于这种情况:您 (main) 是老板,有几个专门的工人。你让他们中的一个人执行他们的任务(调用方法),你会得到一份报告,其中包含下一步做什么的建议(nextAction)。然后你会看那个(switch),选择可以执行建议操作的工作人员(case ...)并让它执行(方法调用)......重复(while)直到建议说你应该停下来。
  • 你描述的方式很清楚。我还没有学习枚举,但我今晚会。这与其他一些答案一起使我的生活更轻松。谢谢你。对我来说,这不仅仅是网站上的一些观点。你以我自己的老师无法做到的方式帮助了我。只是,非常感谢。
【解决方案3】:

当您编写递归代码时,您应该确保除了再次调用该函数之外还有某种结束条件。例如,我使用if(gamePlayedThisManyTimes <= 1) return;gameOver 方法添加了一个结束条件。运行以下代码时,您为方法 gameStart 提供的值将决定您玩多少游戏,gameOver 将在调用“gameStart”以最终达到递归结束条件时递减该值。

public static void main(String[] args)
{
    System.out.println("main method");
    gameStart(10);
}

private static void gameStart(int playGameThisManyTimes)
{
    System.out.println("Game " + playGameThisManyTimes + " started...");
    System.out.println("some other method called");
    gameOver(playGameThisManyTimes);
}

private static void gameOver(int gamePlayedThisManyTimes)
{
    System.out.println("game over called for " + gamePlayedThisManyTimes); //I would ask the user if they want to play again.

    if(gamePlayedThisManyTimes <= 1)
        return;
    else
        gameStart(gamePlayedThisManyTimes - 1);
}

输出

main method
Game 10 started...
some other method called
game over called for 10
Game 9 started...
some other method called
game over called for 9
Game 8 started...
some other method called
game over called for 8
Game 7 started...
some other method called
game over called for 7
Game 6 started...
some other method called
game over called for 6
Game 5 started...
some other method called
game over called for 5
Game 4 started...
some other method called
game over called for 4
Game 3 started...
some other method called
game over called for 3
Game 2 started...
some other method called
game over called for 2
Game 1 started...
some other method called
game over called for 1

【讨论】:

  • 在我理解你写的内容之前,我不想发表评论。谢谢你的详细回答。我只花了一分钟就明白了。我非常感谢您为帮助我所做的努力。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-09
  • 1970-01-01
  • 1970-01-01
  • 2016-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多