【问题标题】:How to evaluate the utility of a recursive algorithm in Java?如何评估 Java 中递归算法的效用?
【发布时间】:2013-02-17 04:17:33
【问题描述】:

背景:

我正在尝试学习算法和 java。运行 320x320 的网格,100 次试验的运行速度比非递归 Quick-Union 实现快 5 倍。但是,在大约 400x400(160,000 个站点)的网格之上,我有堆栈溢出错误。

我知道 java 没有针对尾递归进行优化(更不用说非尾递归了)。但是,我认为有时可以选择递归算法而不是非递归版本,因为它可以运行得更快且同样安全。

请记住,我只是在学习这些东西,我的代码可能不是最优的。但是,我将其包括在内是为了更好地理解我的问题。

问题

评估何时可以在 Java 应用程序中安全地使用递归算法的过程是什么(假设它比非递归替代方案运行得更快)?

关于递归与联合查找实现的统计数据

(注意:2x Ratio 就是上一个当前运行时间除以上一个运行时间)

|-----------|-----------|------------|-------------|-------------|
|     N     | Recursive | Recursive  | Quick-Union | Quick-Union |
|  (sites)  |    time   |  2x Ratio  |    time     |  2x Ratio   |
|===========|===========|============|=============|=============|
|     196   |      35   |            |      42     |             |
|     400   |      25   |    0.71    |      44     |     1.05    |
|     784   |      45   |    1.80    |      46     |     1.05    |
|    1600   |     107   |    2.38    |      86     |     1.87    |
|    3136   |      48   |    0.45    |     113     |     1.31    |
|    6400   |      75   |    1.56    |     303     |     2.68    |
|   12769   |     183   |    2.44    |     858     |     2.83    |
|   25600   |     479   |    2.62    |    2682     |     3.13    |
|   51076   |    1253   |    2.62    |    8521     |     3.18    |
|  102400   |    4730   |    3.77    |   27256     |     3.20    |
|-----------|-----------|------------|-------------|-------------|

递归类

public class PercolateRecur implements Percolation {
  // the site has been opened for percolation but is not connected
  private final int OPEN = 0;
  // the site is not open for percolation (default state)
  private final int BLOCKED = -1;
  // the matrix that will be percolated. Values default to `BLOCKED = -1`
  // two sites that are connected together share the same value.
  private int[][] matrix;
  // the size of the sides of the matrix (1 to n)
  private int size;
  // whether water can flow from top to bottom of the matrix
  private boolean percolated;

  public PercolateRecur(int N) {
    percolated = false;
    size = N;
    initMatrix();
  }

  /**
   * initializes the matrix to default values
   */
  private void initMatrix() {
    matrix = new int[size+1][size+1];
    // open up the top of the matrix
    for (int x = 1; x < size+1; x++)
      matrix[x][0] = x;

    // set all values in matrix to closed
    for (int x = 1; x < size+1; x++)
      for (int y = 1; y < size+1; y++)
        matrix[x][y] = BLOCKED;
  }

  /**
   * indicates site (x,y) is a valid coordinate
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   * @return boolean
   */
  private boolean isValid(int x, int y) {
    return x > 0 && x < size+1 && y > 0 && y < size+1;
  }

  /**
   * returns value of site above (x,y)
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   * @return int value
   */
  private int above(int x, int y) {
    if (y <= 0)
      return BLOCKED;
    else
      return matrix[x][y-1];
  }

  /**
   * returns value of site below (x,y)
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   * @return int value
   */
  private int below(int x, int y) {
    if (y >= size)
      return BLOCKED;
    else
      return matrix[x][y+1];
  }

  /**
   * returns value of site left of (x,y)
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   * @return int value
   */
  private int left(int x, int y) {
    if (x <= 0)
      return BLOCKED;
    return matrix[x-1][y];
  }

  /**
   * returns value of site right of (x,y)
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   * @return int value
   */
  private int right(int x, int y) {
    if (x >= size)
      return BLOCKED;
    else
      return matrix[x+1][y];
  }

  /**
   * connects (x,y) to open adjacent sites
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   */
  private void connect(int x, int y) {
    if (isFull(x,y))
      return;
    if (above(x,y) > OPEN)
      matrix[x][y] = above(x, y);
    else if (below(x, y) > OPEN)
      matrix[x][y] = below(x, y);
    else if (left(x, y) > OPEN)
      matrix[x][y] = left(x, y);
    else if (right(x, y) > OPEN)
      matrix[x][y] = right(x, y);
    else if (matrix[x][y] == BLOCKED)
      matrix[x][y] = OPEN;
  }

  /**
   * recursively connects open sites in same group as (x,y)
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   */
  private void expand(int x, int y) {
    if (!isFull(x, y))
      return;
    if (above(x,y) == OPEN)
      openWith(x,y-1, matrix[x][y]);
    if (below(x,y) == OPEN)
      openWith(x,y+1, matrix[x][y]);
    if (left(x,y) == OPEN)
      openWith(x-1,y, matrix[x][y]);
    if (right(x,y) == OPEN)
      openWith(x+1,y, matrix[x][y]);
  }

  /**
   * opens a site (x,y) on the matrix
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   */
  public void open(int x, int y) {
    if (percolated || !isValid(x, y))
      return;
    connect(x, y);
    expand(x, y);
  }

  /**
   * opens a site with given value
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   * @param val value of point
   */
  private void openWith(int x, int y, int val) {
    matrix[x][y] = val;
    open(x, y);
  }

  /**
   * Returns whether site (x,y) is open
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   * @return true if not blocked
   */
    public boolean isOpen(int x, int y) {
    return matrix[x][y] > BLOCKED;
  }

  /**
   * Returns whether site (x,y) is full (connected to the top)
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   * @return true if is full
   */
  public boolean isFull(int x, int y) {
    return matrix[x][y] > OPEN;
  }

  /**
   * indicates whether site is blocked (not open)
   * @param x x-portion of x/y coordinate
   * @param y y-portion of x/y coordinate
   * @return true if blocked
   */
  public boolean isBlocked(int x, int y) {
    return matrix[x][y] == BLOCKED;
  }

  /**
   * indicates whether water can flow from top to bottom of matrix
   * @return true if matrix is percolated
   */
  public boolean percolates() {
    for (int x = 1; x <= size; x++)
      if (matrix[x][size] > OPEN)
        percolated = true;
    return percolated;
  }

  /**
   * prints the matrix to the command line
   */
  public void print() {
    for (int y = 1; y < size+1; y++) {
      System.out.println();
      for (int x = 1; x < size+1; x++) {
        if (matrix[x][y] == BLOCKED)
          System.out.print("XX ");
        else if (matrix[x][y] < 10)
          System.out.print(matrix[x][y] + "  ");
        else
          System.out.print(matrix[x][y] + " ");
      }
    }
    System.out.println();
  }
}

【问题讨论】:

  • “安全使用”?你这是什么意思?
  • 我在你的“递归类”中找不到递归。
  • @alfasin 我的意思是递归算法可能会产生堆栈溢出错误。
  • 我也不确定。是不是递归调用的次数与n不成指数比例?
  • 您始终可以使用 -Xss 标志增加堆栈内存分配

标签: java algorithm recursion


【解决方案1】:

在 Java 中实现递归算法的一个问题是 Java 平台没有执行标准的“尾调用消除”优化。这意味着深度递归需要一个深度堆栈。而且由于 Java 线程堆栈不会“增长”,这意味着您很容易受到堆栈溢出的影响。

有两种解决方法:

  • 增加线程堆栈大小,可以通过在命令行中使用-Xss 选项,或者通过在Thread 构造函数中显式提供(更大的)堆栈大小。

  • 在您的 Java 代码中显式实现尾调用消除...至少可以说是丑陋的。

在您的情况下,第一个解决方法将为您提供“真正的递归”的度量。第二个......好吧,算法将不再是真正的递归,但这是你可以做的,以使递归算法在 Java 中适用于“深度”问题。

注意:您始终可以将 Java 中的递归算法转换为等效的迭代算法,该算法使用“模拟堆栈”数据结构来保存递归状态。两个版本的算法复杂度应该相同。也许您也应该尝试这种方法,并将“模拟堆栈”变体作为第三对列包含在您的评估中。

【讨论】:

  • 您会推荐哪些资源来了解有关如何实现“模拟堆栈”数据结构的更多信息?
  • 那里有一个关于模拟堆栈的 pdf 的非常有趣的链接,但我想知道“模拟堆栈”是否会使这些方法极难阅读和维护。
  • 是的。这是问题的一部分。
  • 如果我理解正确,除非递归和非递归选项之间存在非常显着的性能差异,否则您不会选择递归版本,因为:(a) 开发人员必须花费是时候调查和防御可能的堆栈溢出错误了; (b) 重构代码,使得递归是安全的并且代码不可能被阅读;或 (c) 使用非递归算法。
  • @Alex - 没那么简单。在某些情况下,您知道递归不是那么深,或者您可以在给定病态输入时处理算法失败(堆栈溢出)。或者你可以只使用一个大堆栈。但事实仍然是,您需要小心使用 Java 中的递归算法。
猜你喜欢
  • 2020-07-12
  • 1970-01-01
  • 1970-01-01
  • 2012-01-04
  • 1970-01-01
  • 1970-01-01
  • 2020-07-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多