【问题标题】:iterative version of easy recursive algorithm简单递归算法的迭代版本
【发布时间】:2010-10-02 04:46:52
【问题描述】:

我想我有一个非常简单的问题。 我遇到了这个问题,可以很容易地用递归函数解决,但我无法迭代解决。

假设您有任何布尔矩阵,例如:

M:

111011111110
110111111100
001111111101
100111111101
110011111001
111111110011
111111100111
111110001111

我知道这不是一个普通的布尔矩阵,但它对我的示例很有用。 你可以注意到那里有某种零路径......

我想创建一个函数来接收这个矩阵和一个存储零的点,并将同一区域中的每个零转换为 2(假设矩阵可以存储任何整数,即使它最初是布尔值)

(就像在 Paint 或任何图像编辑器中绘制区域时一样)

假设我用这个矩阵 M 和右上角的坐标为零调用函数,结果将是:

111011111112
110111111122
001111111121
100111111121
110011111221
111111112211
111111122111
111112221111

好吧,我的问题是如何迭代地做到这一点...... 希望我没有搞砸太多

提前致谢!

曼努埃尔

ps:如果您能用 C、S、python 或伪代码显示该函数,我将不胜感激:D

【问题讨论】:

    标签: recursion iteration


    【解决方案1】:

    有一种标准技术可以将特定类型的递归算法转换为迭代算法。它被称为tail-recursion

    此代码的递归版本看起来像(伪代码 - 没有边界检查):

    paint(cells, i, j) {
       if(cells[i][j] == 0) {
          cells[i][j] = 2;
          paint(cells, i+1, j);
          paint(cells, i-1, j);
          paint(cells, i, j+1);
          paint(cells, i, j-1);
       }
    }
    

    这不是简单的尾递归(不止一个递归调用),因此您必须添加某种堆栈结构来处理中间内存。一个版本看起来像这样(伪代码,java-esque,同样,没有边界检查):

    paint(cells, i, j) {
        Stack todo = new Stack();
        todo.push((i,j))
        while(!todo.isEmpty()) {
           (r, c) = todo.pop();
           if(cells[r][c] == 0) {
              cells[r][c] = 2;
              todo.push((r+1, c));
              todo.push((r-1, c));
              todo.push((r, c+1));
              todo.push((r, c-1));              
           }          
        }
    }
    

    【讨论】:

      【解决方案2】:

      伪代码:

      Input: Startpoint (x,y), Array[w][h], Fillcolor f
      
      Array[x][y] = f
      bool hasChanged = false;
      repeat
        for every Array[x][y] with value f:
          check if the surrounding pixels are 0, if so:
            Change them from 0 to f
            hasChanged = true
      until (not hasChanged)
      

      【讨论】:

        【解决方案3】:

        为此,我将使用 Stack ou Queue 对象。这是我的伪代码(类似python):

        stack.push(p0)
        while stack.size() > 0:
            p = stack.pop()
            matrix[p] = 2
            for each point in Arround(p):
               if matrix[point]==0:
                  stack.push(point)
        

        【讨论】:

        • 使用堆栈/队列对象仍然是递归的,您自己管理堆栈,而不是使用编程语言为您做家务。
        • 错误。递归意味着函数调用或引用自身。 en.wikipedia.org/wiki/Recursion。我同意你的观点,编译后的代码最终在功能上是相同的,这不是严格递归的。一个理由是非递归版本可以在单遍解析器中解析。
        • @Nick:在函数式编程中,递归过程是使用非常量空间进行延迟操作的过程,而迭代过程使用恒定空间。因此,使用堆栈或队列是一个递归过程 (mitpress.mit.edu/sicp/full-text/book/…)。
        【解决方案4】:

        将递归函数转换为迭代函数的最简单方法是利用堆栈数据结构来存储数据,而不是通过递归调用将其存储在调用堆栈中。

        伪代码:

        var s = new Stack();
        
        s.Push( /*upper right point*/ );
        
        while not s.Empty:
        
            var p = s.Pop()        
            m[ p.x ][ p.y ] = 2
        
            s.Push ( /*all surrounding 0 pixels*/ )
        

        【讨论】:

        • 查看对 ascobol 帖子的评论 - 从技术上讲,这仍然是递归的。
        • 您使用的“递归”定义与其他人不同。
        【解决方案5】:

        并非所有递归算法都可以转换为迭代算法。通常只有具有单个分支的线性算法可以。这意味着具有两个或多个分支的树算法和具有更多路径的 2d 算法在不使用堆栈的情况下极难转换为递归(这基本上是作弊)。

        例子:

        递归:

        listsum: N* -> N
        listsum(n) ==
          if n=[] then 0 
                  else hd n + listsum(tl n)
        

        迭代:

        listsum: N* -> N
        listsum(n) ==
          res = 0;
          forall i in n do
            res = res + i
          return res
        

        递归:

        treesum: Tree -> N
        treesum(t) ==
          if t=nil then 0
          else let (left, node, right) = t in
            treesum(left) + node + treesum(right)
        

        部分迭代(尝试):

        treesum: Tree -> N
        treesum(t) ==
          res = 0
          while t<>nil 
            let (left, node, right) = t in
              res = res + node + treesum(right)
              t = left
          return res
        

        如您所见,有两条路径(左和右)。可以将这些路径之一转换为迭代,但要将另一个转换为迭代,您需要保留可以使用堆栈完成的状态:

        迭代(带栈):

        treesum: Tree -> N
        treesum(t) ==
          res = 0
        
          stack.push(t)
          while not stack.isempty()
            t = stack.pop()
            while t<>nil 
              let (left, node, right) = t in
                stack.pop(right)
                res = res + node + treesum(right)
                t = left
        
          return res
        

        这可行,但递归算法更容易理解。

        【讨论】:

        【解决方案6】:

        如果迭代比性能更重要,我会使用以下算法:

        1. 设置初始2
        2. 扫描矩阵以在 2 附近找到 0
        3. 如果找到这样的 0,请将其更改为 2,然后在步骤 2 中重新开始扫描。

        这很容易理解,不需要堆栈,但是非常耗时。

        【讨论】:

          【解决方案7】:

          一个简单的迭代方式是使用队列。

          1. 将起点插入队列
          2. 从队列中获取第一个元素
          3. 设置为 2
          4. 将所有仍为 0 的邻居放入队列中
          5. 如果队列不为空,则跳转到 2。

          【讨论】:

            猜你喜欢
            • 2011-01-18
            • 1970-01-01
            • 1970-01-01
            • 2010-09-19
            • 2011-02-08
            • 2016-06-14
            • 2019-06-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多