算法回溯:如何在不存储状态的情况下进行递归

Posted

技术标签:

【中文标题】算法回溯:如何在不存储状态的情况下进行递归【英文标题】:Algorithm Backtracking: How to do recursion without storing state 【发布时间】:2018-04-02 22:15:11 【问题描述】:

通常在回溯中,我们采用一个辅助函数,该函数接受初始状态,每个递归调用负责自己的计算并将结果传递给下一个递归调用。理论上,我们通过不可见和可见变量来表示这一点。

例如,在一个字符串的排列中,我们将使用这个程序:

def permute(str)
  return str if str.length < 2 
  permute_helper(str, "")
end 

def permute_helper(unseen, seen)
  #base case
  if unseen.length <= 0
    p seen
    return
  else 
    (0..unseen.length-1).each do |i|
      buffer = unseen 
      buffer = buffer.split('')
      buffer.delete_at(i)
      buffer = buffer.join('')
      permute_helper(buffer, seen+unseen[i])
    end 
  end
end 

permute('abc')

Thie 将打印出所需的结果。

在最近的一次采访中,我被要求在不使用两个变量的情况下这样做。没有将状态存储在 seen 变量中。我当时想不通,但是想问下不存储状态怎么回溯?

【问题讨论】:

这是一大堆代码,归结为简单的子字符串操作。 unseen[i,1] = "" 将删除1 位置i 处的字符。对于其他场合,更喜欢unseen.charsunseen.split('') 【参考方案1】:

字符串"cd" 的排列是["cd", "dc"]。如果我们现在希望获得字符串"bcd" 的排列,我们只需将该数组的每个元素替换为三个字符串,每个字符串的"b" 在不同的位置。 "cd" 变为 "bcd""cbd""cdb""dc" 变为 "bdc""dbc""dba"。因此"bcd" 的排列是

["bcd", "cbd", "cdb", "bdc", "dbc", "dba"]

如果我们现在希望获得"abcd" 的排列,我们将上述六元素数组的每个元素替换为四个字符串,每个字符串在不同的位置使用"a"。例如,"bcd" 变为 "abcd""bacd""bcad""bcda"。递归的结构现在应该很明显了。

def permute(str)
  case str.length
  when 0, 1
    str
  when 2
    [str, str.reverse]
  else
    first = str[0]
    sz = str.size-1
    permute(str[1..-1]).flat_map  |s| (0..sz).map  |i| s.dup.insert(i,first)  
  end
end

permute('')
  #=> ""
permute('a')
  #=> "a"
permute('ab')
  #=> ["ab", "ba"]
permute('abc')
  #=> ["abc", "bac", "bca", "acb", "cab", "cba"]    
permute('abcd')
  #=> ["abcd", "bacd", "bcad", "bcda", "acbd", "cabd", "cbad", "cbda",
  #    "acdb", "cadb", "cdab", "cdba", "abdc", "badc", "bdac", "bdca",
  #    "adbc", "dabc", "dbac", "dbca", "adcb", "dacb", "dcab", "dcba"]

str 当然是“看不见”的变量。

【讨论】:

非常好的编码,卡里【参考方案2】:

@CarySwoveland 的回答通常非常棒。对于那些希望置换 array 的人,可以考虑这种函数式方法。虽然这使用了辅助 lambda all_pos,但没有使用额外的状态参数来累积结果。

def permute ((x, *xs))

  all_pos = lambda do |(y,*ys)|
    if y.nil?
      [[ x ]]
    else
      [[ x, y, *ys ]] + (all_pos.call ys) .map  |rest| [ y, *rest ] 
    end
  end

  if x.nil? or xs.empty?
    [[x]]
  else
    (permute xs) .flat_map &all_pos
  end

end

permute [1,2,3,4]

# [ [1, 2, 3, 4]
# , [2, 1, 3, 4]
# , [2, 3, 1, 4]
# , [2, 3, 4, 1]
# , [1, 3, 2, 4]
# , [3, 1, 2, 4]
# , [3, 2, 1, 4]
# , [3, 2, 4, 1]
# , [1, 3, 4, 2]
# , [3, 1, 4, 2]
# , [3, 4, 1, 2]
# , [3, 4, 2, 1]
# , [1, 2, 4, 3]
# , [2, 1, 4, 3]
# , [2, 4, 1, 3]
# , [2, 4, 3, 1]
# , [1, 4, 2, 3]
# , [4, 1, 2, 3]
# , [4, 2, 1, 3]
# , [4, 2, 3, 1]
# , [1, 4, 3, 2]
# , [4, 1, 3, 2]
# , [4, 3, 1, 2]
# , [4, 3, 2, 1]
# ]

【讨论】:

以上是关于算法回溯:如何在不存储状态的情况下进行递归的主要内容,如果未能解决你的问题,请参考以下文章

当应用程序处于状态后台或被杀死时,如何在不点击通知的情况下存储 iOS 远程通知?

如何在不推送到服务器的情况下更新中继存储

我们可以在不回溯的情况下进行 DFS 吗?

广度优先遍历和深度优先遍历的真正区别

数据--第20课-递归的应用实战二

从暴力匹配到KMP算法