16

是否可以通过 java 的辅助函数保留信息,而不使用静态变量。

例如,

public void foo(){
    int v = 0;
    fooHelper(2);
}

public void fooHelper(int depth){
    v++;
    fooHelper(depth-1)
}

即我想更新变量 v 而不会丢失每个递归案例的信息,而不必访问函数外部的变量。

4

8 回答 8

30

忘记所有告诉您声明属性或在每个递归调用中更新可变对象的答案。在真正的函数式递归样式中,您通过将信息作为参数和/或返回类型传递来“保留”信息。

让我用一个简单的例子来说明,假设你想递归地计算一个int[]. 这里,状态(递归调用之间需要保留的信息)是数组中的当前索引和到目前为止的总和。这是如何做到的:

public int sum(int[] array) {
    return sum(array, 0, 0); 
}

private int sum(int[] array, int idx, int acc) {
    if (idx == array.length)
        return acc;
    return sum(array, idx+1, acc+array[idx]);
}

像这样称呼它:

int[] array = {1, 2, 3};
System.out.println(sum(array));

如您所见,无需声明(静态或实例)属性,也无需传递和修改可变对象(列表、映射)——我什至没有使用局部变量,因为解决问题所需的所有信息问题作为方法参数存在。

在您问题的代码中,v变量应该执行acc参数在我的回答中所做的事情,即:每次调用递归时修改累积值。最后,您只需要从辅助函数(必须没有void返回类型)返回累积值,这就是您将在foo().

于 2012-04-22T16:34:19.207 回答
1

在范围内声明的变量(例如方法)只能在此范围内访问(例如不能在另一个方法中)。

如果信息仅与方法相关,请将变量保留在方法中。如果信息与整个对象/类状态相关,请将其保留为类成员(静态/非静态)。

例如:

public void someRecursiveMethod(int num) {
    while (num < 10) {
        num++;
        someRecursiveMethod(num);
        System.out.println("Current num = " + num);
    }
}
于 2012-04-22T05:51:34.597 回答
0

您可以创建一个新类 (yuck),或者将变量作为参数传递并在 fooHelper 中返回。

于 2012-04-22T05:54:29.100 回答
0

为什么不让它成为一个实例变量(不一定是静态的)......?

public class Recursive {
    int v = 0;
    public void foo(){

        fooHelper(2);
        System.out.println(v);
    }

    public void fooHelper(int depth){
        v++;
        if(depth-1!=0)//Added this because I was getting an StackOverflowError
        fooHelper(depth-1);
    }

    public static void main(String[] args) {
        Recursive r = new Recursive();
        r.foo();
    }
}
于 2012-04-22T05:55:13.857 回答
0

您可以返回一个列表或类似的数据结构:

public List<Integer> fooHelper( int v, int depth ){
    if( depth == 0 ) return new ArrayList();

    v++;
    List<Integer> result = fooHelper( v, depth-1 );

    result.add( new Integer(v) );

    return result;
}
于 2012-04-22T05:55:56.873 回答
0

因为变量 v 是原始类型,所以对其所做的更改在函数范围之外是不可见的。您可以在类中声明变量 v,例如 State 并将状态对象传递给递归函数以获得所需的效果。

public void foo(){
    State state = new State();
    fooHelper(state, 2);
}

public void fooHelper(State state, int depth){
    state.v++;
    fooHelper(state, depth-1);
}

class State {
    int v;
}

希望能帮助到你。

于 2012-04-22T06:01:58.490 回答
0

您可以传递一个对象来存储每个递归调用的更新。类似于下面的那个。

public static void fooHelper(int depth, HashMap map){
      map.put(depth, "Call " + depth);
      if (depth > 0)
      {
        fooHelper(depth-1, map);
      }
      return;
    }
于 2012-04-22T06:02:10.817 回答
0

我认为这被称为记忆。看起来像

class Fibonacci
{
     public Map < Integer , Integer > memorized = new HashMap < > ( ) ;

     public int fib ( int n )
     {
           if ( memoized . containsKey ( n ) )
           {
                 return memoized . get ( n ) ;
           }
           else
           {
                 int fib = // calculate recursively
                 memoized . put ( n , fib ) ;
                 return fib ;
           }
     }
}

您应该能够从此算法中获得不错的(不是最佳的)性能。递归斐波那契算法性能糟糕的主要原因是 b/c 它重复计算相同的值。使用递归+记忆,它永远不会计算任何值超过一次。

感谢@Aristide 指出记忆和记忆之间的细微差别。

于 2012-04-22T06:02:35.840 回答