【问题标题】:Refactor this recursive method?重构这个递归方法?
【发布时间】:2010-09-19 17:10:18
【问题描述】:

我对递归的想法很陌生,这实际上是我第一次尝试编写递归方法。

我尝试实现一个递归函数 Max,它传递一个数组,以及一个保存数组大小的变量,以便打印最大的元素。

它有效,但它感觉不对!

我还注意到,我似乎比我的同学们使用静态修饰符的次数要多得多……

任何人都可以就如何改进我的代码提供任何一般性提示和反馈吗?

public class RecursiveTry{

static int[] n = new int[] {1,2,4,3,3,32,100};
static int current = 0;
static int maxValue = 0;
static int SIZE = n.length;

public static void main(String[] args){
    System.out.println(Max(n, SIZE));
}   

public static int Max(int[] n, int SIZE) {
    if(current <= SIZE - 1){
        if (maxValue <= n[current]) {
            maxValue = n[current];
            current++;
            Max(n, SIZE);                       
        }
        else {
            current++;
            Max(n, SIZE);
        }
    }
    return maxValue;
}

}

【问题讨论】:

  • 喜欢“不做作业”的标签。我不介意家庭作业,只要你给它一个好机会,就像上面一样...... :)

标签: java recursion


【解决方案1】:

“max”函数是错误类型的东西编写递归函数 - 并且您使用静态值作为“current”和“maxValue”的事实使您的函数不是真正的递归函数。

为什么不做一些更适合递归算法的事情,比如阶乘?

【讨论】:

  • 还要注意,这取决于语言——例如在 Haskell 中,递归实现 max 函数是完全合理的。
  • 如果您可以定义递归关系,则可以实现递归解决方案。递归最大函数是完美的。
【解决方案2】:

您使用静态变量来保持函数外部的状态将是困难的来源。

在伪代码中递归实现 max() 函数的示例可能是:

function Max(data, size) {
    assert(size > 0)
    if (size == 1) {
        return data[0]
    }
    maxtail = Max(data[1..size], size-1)
    if (data[0] > maxtail) {
        return data[0]
    } else {
        return maxtail
    }
}

这里的关键是对 Max() 的递归调用,您可以在其中传递所有除了第一个元素,并且小于大小的元素。一般的想法是这个函数说“这个数据中的最大值要么是第一个元素,要么是数组其余部分中的最大值,以较大者为准”。

此实现不需要函数定义之外的静态数据。

递归实现的一个标志是所谓的“终止条件”,它可以防止递归永远进行(或者,直到堆栈溢出)。在上述情况下,size == 1 的测试是终止条件。

【讨论】:

  • 正要写类似的,太慢了! vpotok,static修饰符一般用于定义常量。
【解决方案3】:

首先,让我们处理静态范围问题……您的类正在定义一个对象,但从未实际实例化一个对象。由于 main 是静态作用域的,首先要做的是获取一个对象,然后执行它的方法,如下所示:

public class RecursiveTry{

    private int[] n = {1,2,4,3,3,32,100};

    public static void main(String[] args){
        RecursiveTry maxObject = new RecursiveTry();
        System.out.println(maxObject.Max(maxObject.n, 0));
    }

    public int Max(int[] n, int start) {
        if(start == n.length - 1) {
            return n[start];
        } else { 
            int maxRest = Max(n, start + 1);
            if(n[start] > maxRest) {
                return n[start];
            }
            return maxRest;
        }
    }

}

所以现在我们有一个名为 maxObject 的 RecursiveTry 对象,它不需要静态范围。我不确定使用递归找到最大值是否有效,因为传统循环方法中的迭代次数大致相等,但使用递归时使用的堆栈量更大。但是对于这个例子,我会减少很多。

递归的优点之一是您的状态通常不需要像在迭代中那样在重复测试期间保持不变。在这里,我承认使用变量来保存起点,因为传递一个包含除第一个项目之外的所有项目的新 int[] 的 CPU 密集度较低。

【讨论】:

  • 同意。他并不真正需要一个对象,除非您试图拥有某种模板化函数,该函数接受一个通用对象并找到它的最大值。我认为这个实现使用整数就可以了。
  • 是的......只是把它卡在我的 IDE 中,它被炸得很可怕......我现在正在修复它!
  • 好的......有一个运行的例子!
【解决方案4】:

您实际上是在编写迭代版本,但使用尾递归进行循环。此外,通过将这么多变量设为静态,您实际上是在使用全局变量而不是对象。这是一种更接近典型递归实现的尝试。当然,在现实生活中,如果您使用 Java 之类不优化尾调用的语言,您将使用循环实现“Max”函数。

public class RecursiveTry{
  static int[] n;

  public static void main(String[] args){
        RecursiveTry t = new RecursiveTry(new int[] {1,2,4,3,3,32,100});
        System.out.println(t.Max());
  }       

  RecursiveTry(int[] arg) {
    n = arg;
  }

  public int Max() {
    return MaxHelper(0);
  }

  private int MaxHelper(int index) {
    if(index == n.length-1) {
      return n[index];
    } else {
      int maxrest = MaxHelper(index+1);
      int current = n[index];
      if(current > maxrest)
        return current;
      else
        return maxrest;
    }
  }
}

【讨论】:

    【解决方案5】:

    “没有作业”?

    无论如何。第一件事。

    static int[] n = new int[] {1,2,4,3,3,32,100};
    static int SIZE = n.length;
    

    与它们共享名称的 Max() 的参数无关。将这些移到 main 并丢失“静态”说明符。当从 main() 内部调用 Max() 的第一个实例时,它们只使用一次。它们的范围不应超出 main()。

    没有理由让所有 Max() 调用共享一个“当前”索引。 “当前”应该是 Max() 的本地。但是,Max() 的连续重复如何知道要使用的“当前”值是什么? (提示:Max() 已经在向其他 Max() 的下一行传递一些数据。将“当前”添加到此数据。)

    maxValue 也是如此,尽管这里的情况有点复杂。您不仅需要将当前的“maxValue”向下传递,而且当递归完成时,您必须将其一直传递回第一个 Max() 函数,该函数会将其返回给 main()。您可能需要查看其他一些递归示例并花一些时间研究这个。

    最后,Max() 本身是静态的。但是,一旦您消除了引用外部数据(静态变量)的需要;没关系。这只是意味着您可以调用 Max() 而无需实例化对象。

    【讨论】:

      【解决方案6】:

      让你的函数依赖于静态变量并不是一个好主意。下面是递归 Max 函数的可能实现:

      int Max(int[] array, int currentPos, int maxValue) {
          // Ouch!
          if (currentPos < 0) {
              raise some error
          }
          // We reached the end of the array, return latest maxValue
          if (currentPos >= array.length) {
              return maxValue;
          }
          // Is current value greater then latest maxValue ?
          int currentValue = array[currentPos];
          if (currentValue > maxValue) {
              // currentValue is a new maxValue
              return Max(array, currentPos + 1, currentValue);
          } else {
              // maxValue is still a max value
              return Max(array, currentPos + 1, maxValue);
          }
      }
      ...
      
      int[] array = new int[] {...};
      int currentPos = 0;
      int maxValue = array[currentPos] or minimum int value;  
          maxValue = Max(array, currentPos, maxValue);
      

      【讨论】:

      • 我建议做一个小改动,只要我们是冗长的?为什么不合并并将“return Max(...)”行移出条件,并使用“newMaxValue”等新变量作为第三个参数? (当然,还要在 if/else 块中决定它的值。)
      • 请注意,aku 将部分计算(maxValue)传递到堆栈上,而不是使用 instance 变量。 +1 符合递归的“精神”。 (在 C# 或 Java 中并行化也更容易一些。并不是说你会并行化这个小程序!)
      • aib,currentValue 变量只是为了便于阅读,即我想添加 cmets。我觉得在这种情况下不需要 newMaxValue
      • 现在我又看了一遍,似乎没有那么冗长。一定是正确大写的 cmets 让我失望了 :)。是的,让它保持它的精神。
      【解决方案7】:

      正如其他人所观察到的,不需要递归来实现 Max 函数,但使用熟悉的算法来试验新概念可能是有益的。所以,这里是简化的代码,下面有解释:

      public class RecursiveTry
      {
          public static void main(String[] args)
          {
              System.out.println(Max(new int[] {1,2,4,3,3,32,100}, 0, 0));
          }   
      
          public static int Max(int[] n, int current, int maxValue) 
          {
              if(current < n.Length)
              {
                  if (maxValue <= n[current] || current == 0))
                  {
                      return Max(n, current+1, n[current]);
                  }
                  return Max(n, current+1, maxValue);
              }
              return maxValue;
         }
      }
      

      所有的静态状态都是不必要的;相反,所有内容都在堆栈上传递。 Max函数的内部逻辑是流线型的,我们用两种不同的方式递归只是为了好玩

      【讨论】:

      • 给我展示一个不能重构为迭代解决方案的递归函数。
      • @[Pax Diablo]:理论上它们是等价的;对于这么简单的事情,迭代是首选(更高效、更少代码、更容易理解、更少堆栈使用等)
      • @Pax:快速排序怎么样?你如何使用迭代来实现它?
      • @[DasBoot]:一般来说,您将堆栈中的所有内容保存在队列中,并自己管理队列
      【解决方案8】:

      在Scheme中这可以写得很简洁:

      (define (max l)
          (if (= (length l) 1)
              (first l)
              (local ([define maxRest (max (rest l))])
                (if (> (first l) maxRest)
                    (first l)
                    maxRest))))
      

      当然,这使用链表而不是数组,这就是为什么我没有将 size 元素传递给它的原因,但我觉得这将问题提炼为本质。这是伪代码定义:

      define max of a list as:
          if the list has one element, return that element
          otherwise, the max of the list will be the max between the first element and the max of the rest of the list
      

      【讨论】:

      • 我确信它也可以用 Perl 简洁地编写,但任何人都能够阅读它吗? :-)
      【解决方案9】:

      这是为您准备的 Java 版本。

      public class Recursion {
      
          public static void main(String[] args) {
              int[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
              System.out.println("Max: " + max(0, data));
          }
      
          public static int max(int i, int[] arr) {
              if(i == arr.length-1) {
                  return arr[i];
              }
      
              int memo = max(i+1, arr);
              if(arr[i] > memo) {
                  return arr[i];
              }
              return memo;
          }
      }
      

      递归关系是数组的最大元素要么是第一个元素,要么是数组其余部分的最大值。当您到达数组的末尾时,就达到了停止条件。请注意使用 memoization 将递归调用(大致)减少一半。

      【讨论】:

        【解决方案10】:

        递归获取数组最大值的更好方法是实现quicksort(这是一种很好的递归排序算法),然后只返回第一个值。

        这里有一些Java code for quicksort

        【讨论】:

        • 快速排序不是 o(n log n) 吗?递归/迭代解决方案是 o(n) 更好,因为不需要以排序列表结束。您也可以将每个整数写入以该整数命名的文件(例如 7 写入 xxx00007.txt),然后执行 findfirst - 这可行,但很垃圾。
        【解决方案11】:

        我能得到的最小代码大小:

        public class RecursiveTry {
            public static void main(String[] args) {
                int[] x = new int[] {1,2,4,3,3,32,100};
                System.out.println(Max(x, 0));
            }   
        
            public static int Max(int[] arr, int currPos) {
                if (arr.length == 0) return -1;
                if (currPos == arr.length) return arr[0];
                int len = Max (arr, currPos + 1);
                if (len < arr[currPos]) return arr[currPos];
                return len;
            }
        }
        

        一些事情:

        1/ 如果数组大小为零,则返回最大值 -1(您可以使用另一个标记值,例如 -MAX_INT,或引发异常)。为了代码清晰,我在这里假设所有值都为零或更多。否则我会在代码中加入各种不必要的东西(关于回答问题)。

        2/ 如果终止情况是无数据而不是最后一个数据,我认为大多数递归都“更干净”,因此当我们完成数组时,我返回一个保证小于或等于最大值的值.其他人可能会有不同的看法,但这不会是他们第一次或最后一次犯错:-)。

        3/ 递归调用只是获取列表其余部分的最大值并将其与当前元素进行比较,返回两者中的最大值。

        4/“理想”的解决方案是在每次递归调用时传递一个修改后的数组,这样您只需将第一个元素与列表的其余部分进行比较,就不需要 currPos。但这会效率低下,并且会平息 SO 的愤怒。

        5/ 这不一定是最好的解决方案。可能是由于过度使用 LISP 及其 CAR、CDR 和那些无休止的括号而损害了灰质。

        【讨论】:

          猜你喜欢
          • 2012-05-04
          • 2012-11-08
          • 2010-10-10
          • 2020-10-22
          • 1970-01-01
          • 2021-04-08
          • 1970-01-01
          • 2018-05-03
          • 2017-11-09
          相关资源
          最近更新 更多