如何在递归中使用索引变量?

时间:2012-05-12 20:20:02

标签: java variables recursion indexing

我想在递归中使用索引变量,而不是在调用函数时将其作为参数发送。但是,如果我在开始时重置它(例如i = 0),它将在每次运行时重置。我想将它用作计数器(计算函数运行)。

5 个答案:

答案 0 :(得分:2)

首先,您显然只想初始化一次。递归中的常见模式是:

public void run(int opt) {
  run_helper(opt, 0);
}

private void run(int opt, int depth) {
  if (whatever) { run(opt, depth + 1); }
}

外部方法只做一些初始化。

您经常会看到的“解决方案”(例如,在您的问题的第一条评论中)将使用静态变量。这种方法是一种糟糕的风格,一旦你添加多线程(例如,通过制作UI版本,或在多线程Web服务器中运行它),将导致程序以各种奇怪的方式失败。最糟糕的是,它可能最初似乎有效,并且只有在有很多用户时才会巧妙地开始行为不端。 因此除了常数之外,请远离“静态”!

使用“静态”通常看起来像这样:

static int counter;

public void start() {
  counter = 0;
  recurse();
}

public void recurse() {
  counter += 1;
  if (whatever) { recurse(); }
}

现在想象两个用户同时调用start他们将覆盖彼此反击!因为静态意味着它在线程和用户之间共享。

这是非常简单易懂的解决方案

class MyTask {
  int counter = 0;

  public void recurse() {
    counter++;
    if (whatever) { recurse(); }
  }

  public int getIterations() {
    return counter;
  }
}

public void run() {
  MyTask task = new MyTask();
  task.run();
  System.out.println("Task took "+task.getIterations()+" itertions.");
}

然后,您创建一个任务,运行它,并在最后检索计数器。干净,简单,高效,可靠。 如果你有多个线程/用户,每个都有一个单独的MyTask对象,你就不会遇到任何问题。

另外,您可以添加其他统计信息,并且它们都包含在任务对象中。 “每次迭代的平均时间”? “平均递归深度”?没问题。任务对象也可用于存储结果。

此处建议使用ThreadLocal。我不同意这一点。它根本不提供任务对象的好处。试着用ThreadLocal和任务对象来实现它,你会看到差异。另外,ThreadLocal在经验上比访问堆值慢10倍(参见https://stackoverflow.com/a/4756605/1060350)。 int可能更糟糕。所以永远不会在性能关键代码路径中调用ThreadLocal#get 。如果您打算使用ThreadLocal,请在此代码路径之外使用它,并使用局部变量(或Task对象)将“local static”变量提供给关键代码路径。

答案 1 :(得分:1)

您应该使用两种方法将它分开:一个用于启动递归迭代并将计数器初始化为零,另一个是私有,即进行递归调用。这样,每次调用public方法时,计数器都会被初始化。它会是这样的(在java中):

public class Recursion{
    private int iterations=0;

    private int calcFactorial(int n){
        iterations++;
        if (n==2)
            return 2;
        else
            return n * calcFactorial(n-1);
    }

    public int factorial(int n){
        //initialize the counter
        iterations = 0;
        return calcFactorial(n);
    }

    public int getIterations(){
        return iterations;
    }
}

答案 2 :(得分:0)

最自然的事情是,如你在帖子中描述的那样,有一个辅助参数,特别是如果它用于确定何时停止递归。如果依赖于一个成员变量,你可以有一个帮助方法在调用递归方法之前重置couter,并在你想调用递归函数时调用helper-method。

如果您希望坚持使用单一方法,则必须执行以下操作:

创建名为insideMethod的成员变量,默认情况下设置为false。调用该方法时,它会检查此变量。如果为false,则为第一次调用,并且应该重置计数器,否则计数器应该递增。之后,insideMethod设置为true。返回后,insideMethod应该设置为false,前提是调用将其设置为true。

请记住制作insideMethod和索引变量ThreadLocal。

伪代码:

ThreadLocal<Boolean> insideMethod = false
ThreadLocal<Integer> index = 0

....

void recMethod(args) {
    boolean topCall = (insideMethod == false)
    insideMethod = true

    if (topCall)
        index = 0
    else
        index++

    // Body of the method...

    if (topCall)
        insideMethod = false
}

答案 3 :(得分:0)

我会使用一种简单的基于参数的方法,因为索引用作停止条件。 JavaScript实现:

var recurse = function() {
    recurseHelper(0);
};

var recurseHelper = function(iteration) {
    // run for 100 iterations, 
    if (iterationCount > 100)
        return;

    // do stuff...


    recurseHelper(iteration + 1);
}

但是,如果您只想调用一定次数的函数,为什么还要特别使用递归?你可以使用一个循环。再次在JavaScript中:

for (var i = 0; i < 100; i++) {
    // do stuff...
}

编译器可以使用展开循环而不是递归构造获得更多乐趣,具体取决于递归算法的复杂性。

答案 4 :(得分:0)

您可以使用属性,但为什么?为什么不将值作为参数传递?

public class Empty {

    private int steps = 0;  

    public static void main (String args [])
    {
        Empty e = new Empty ();
        System.out.println (e.reverse ("This is a test"));
        System.out.println ("steps: " + e.steps);
    }

    public String reverse (String s) {
        ++steps;
        if (s.length () < 2) 
            return s;
        else return reverse (s.substring (1)) + s.charAt (0);
    }
}

从我得到的评论中,您将其用作计数器,以检测何时结束递归。这看起来不太有用。难道你没有一个可以从参数派生的条件,比如list.length()等吗?

当然,调用该方法两次会进一步增加计数器。您可以重置它,如果两个线程没有同时使用它,并且内部对象中的包装方法可能有助于防止在不重置计数器的情况下调用它。

但这比传递反击更多的样板。

参数是一种更清晰的计数调用的解决方案。

如果你想在调试时防止堆栈溢出,一个古玩的替代方法是调用一个随机函数,并返回1万个案例中的一个。