将分而治之的递归算法转换为迭代版本

Posted

技术标签:

【中文标题】将分而治之的递归算法转换为迭代版本【英文标题】:Transforming a divide and conquer recursive algorithm into an iterative version 【发布时间】:2019-06-12 19:07:48 【问题描述】:

我想将数组上的递归算法转换为迭代函数。它不是尾递归算法,并且有两个递归调用,然后是一些操作。 该算法是一种分而治之的算法,其中在每一步将数组拆分为两个子数组,并将某个函数 f 应用于前面的两个结果。在实践中 f 很复杂,所以迭代算法应该使用函数 f,对于一个最小的工作示例,我使用了一个简单的加法。

下面是python中递归程序的最小工作示例。

import numpy as np

def f(left,right):
    #In practice some complicated function of left and right
    value=left+right
    return value

def recursive(w,i,j):
    if i==j:
        #termination condition when the subarray has size 1
        return w[i]
    else:
        k=(j-i)//2+i
        #split the array into two subarrays between indices i,k and k+1,j
        left=recursive(w,i,k)
        right=recursive(w,k+1,j)

        return f(left,right)

a=np.random.rand(10)
print(recursive(a,0,a.shape[0]-1))

现在,如果我想以迭代方式编写此代码,我意识到我需要一个堆栈来存储中间结果,并且在每一步我都需要将 f 应用于堆栈顶部的两个元素。我只是不确定如何构造将元素放入堆栈而不递归的顺序。这是一个肯定不是最佳解决方案的尝试,因为似乎应该有一种方法可以删除第一个循环并仅使用一个堆栈:

def iterative(w):
    stack=[]
    stack2=[]
    stack3=[]
    i=0
    j=w.shape[0]-1
    stack.append((i,j))
    while (i,j)!=(w.shape[0]-1,w.shape[0]-1):
        (i,j)=stack.pop()
        stack2.append((i,j))
        if i==j:
            pass
        else:
            k=int(np.floor((j-i)/2)+i)
            stack.append((k+1,j))
            stack.append((i,k))
    while len(stack2)>0:
        (i,j)=stack2.pop()
        if i==j:
            stack3.append(w[i])
        else:
            right=stack3.pop()
            left=stack3.pop()
            stack3.append(f(left,right))
    return stack3.pop()

编辑:我感兴趣的真正问题是将一个不同大小的张量数组作为输入,操作 f 求解涉及这些张量的线性程序并输出一个新张量。我不能简单地迭代初始数组,因为在这种情况下 f 的输出大小呈指数增长。这就是为什么我使用这种分而治之的方法来减小这个大小。递归程序可以正常工作,但对于大尺寸会显着减慢,这可能是由于 python 打开并跟踪的帧。

【问题讨论】:

通常使用堆栈,这就是递归使用调用堆栈所做的事情。在这种特定情况下,请使用sum。我相信做到这一点的方法需要了解递归算法的作用并使用迭代从头开始重新设计。所以答案需要查看您的实际算法。 使用“私有”堆栈不会将您的程序转换为迭代程序(至少纯迭代,取决于迭代的定义),它只是一种欺骗和呈现递归程序的方式到迭代程序之一。 顺便说一句,你真的不需要导入 numpy 来做整数除法。只需k = (j + i) // 2 谢谢我已经修改了这个并扩展了我感兴趣的实际算法。 【参考方案1】:

下面我将程序转换为使用延续 (then) 和蹦床 (run/recur)。它演变出一个线性迭代过程,并且不会溢出堆栈。如果您没有遇到堆栈溢出问题,这对您的具体问题没有多大帮助,但它可以教您如何展平分支计算。

这种将正常功能转换为连续传球风格的过程可能是机械式的。如果您稍微眯起眼睛,您会发现该程序与您的大部分元素相同。内联 cmets 并排显示代码 -

import numpy as np

def identity (x):
  return x

def recur (*values):
  return (recur, values)

def run (f):
  acc = f ()
  while type (acc) is tuple and acc [0] is recur:
    acc = f (*acc [1])
  return acc

def myfunc (a):
  # def recursive(w,i,j)
  def loop (w = a, i = 0, j = len(a)-1, then = identity):
    if i == j:                # same
      return then (w[i])      # wrap in `then`
    else:                     # same
      k = (j - i) // 2 + i    # same
      return recur \          # left=recursive(w,i,k)
        ( w
        , i
        , k
        , lambda left:
          recur               # right=recursive(w,k+1,j)
            ( w
            , k + 1
            , j
            , lambda right:
                then          # wrap in `then`
                  (f (left, right)) # same
            )
        )
  return run (loop)

def f (a, b):
    return a + b              # same

a = np.random.rand(10)        # same
print(a, myfunc(a))           # recursive(a, 0, a.shape[0]-1)

# [0.5732646  0.88264091 0.37519826 0.3530782  0.83281033 0.50063843 0.59621896 0.50165139 0.05551734 0.53719382]

# 5.208212213881435

【讨论】:

以上是关于将分而治之的递归算法转换为迭代版本的主要内容,如果未能解决你的问题,请参考以下文章

归并排序算法及其JS实现

图解排序算法之归并排序

图解排序算法之归并排序

图解排序算法之归并排序

图解排序算法之归并排序

归并排序(Merge sort)