如何递归地实现组合?

时间:2019-06-23 16:22:36

标签: lua tail-recursion

我正在通过阅读Ierusalimschy的在Lua中编程(第4版)来自学Lua,并进行练习。练习6.5是

  

编写一个函数,该函数接受一个数组并打印该数组中元素的所有组合。

在此简洁的陈述之后,这本书给出了一个提示,该提示使我们清楚地表明,期望做的是编写一个打印所有 C n 的函数, m )从 n 个元素数组中组合的 m 个元素。

我实现了如下所示的combinations函数:

function combinations (array, m)

  local append = function (array, item)
    local copy = {table.unpack(array)}
    copy[#copy + 1] = item
    return copy
  end

  local _combinations
  _combinations = function (array, m, prefix)
    local n = #array
    if n < m then
      return
    elseif m == 0 then
      print(table.unpack(prefix))
      return
    else
      local deleted = {table.unpack(array, 2, #array)}
      _combinations(deleted, m - 1, append(prefix, array[1]))
      _combinations(deleted, m, prefix)
    end
  end

  _combinations(array, m, {})

end

它可以正常工作,但不是尾递归。

有人可以给我看一个尾递归函数,其功能与上面的combinations一样吗?

(出于价值,我使用的是Lua 5.3。)

注意::我认识到该练习不需要该函数具有尾递归功能。出于好奇,这是我添加的一项要求。

编辑:我稍微简化了函数,但删除了几个嵌套函数,它们并没有增加太多。

2 个答案:

答案 0 :(得分:1)

还有第三种选择,没有蛇吃它的尾巴。尽管使用尾调用进行递归不会导致堆栈溢出,但我还是避免出于个人喜好而这样做。我使用while循环和一个堆栈,该堆栈保存每次迭代的信息。在循环中,您从堆栈中弹出下一个任务,执行工作,然后将下一个任务推入堆栈。我觉得它看起来更干净,并且可以更直观地看到嵌套。

这是我将您的代码转换为编写代码的方式:

function combinations(sequence, item)
    local function append(array, item)
        local copy = {table.unpack(array)}
        copy[#copy + 1] = item
        return copy
    end

    local stack = {}
    local node = { sequence, item, {} }

    while true do
        local seq = node[ 1 ]
        local itm = node[ 2 ]
        local pre = node[ 3 ]

        local n = #seq
        if itm == 0 then
            print(table.unpack(pre))
        elseif n < itm then
            -- do nothing
        else
            local reserve = {table.unpack(seq, 2, #seq)}
            table.insert(stack, { reserve, itm, pre })
            table.insert(stack, { reserve, itm-1, append(pre, seq[ 1 ]) })
        end

        if #stack > 0 then
            node = stack[ #stack ] -- LIFO
            stack[ #stack ] = nil
        else
            break
        end
    end
end

您几乎可以将这种while循环堆栈/节点技术用于任何递归方法。这是一个示例,用于打印深层嵌套的表:https://stackoverflow.com/a/42062321/5113346

以您的输入示例为例,我的版本提供了相同的输出: 1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5

请原谅我,如果它不能与其他传递的参数一起使用,因为我没有尝试解决练习的答案,而只是在原始帖子中重写了代码。

答案 1 :(得分:0)

好的,我想我找到了一种方法:

function combinations (array, m)

  local dropfirst = function (array)
    return {table.unpack(array, 2, #array)}
  end

  local append = function (array, item)
    local copy = {table.unpack(array)}
    copy[#copy + 1] = item
    return copy
  end

  local _combinations
  _combinations = function (sequence, m, prefix, queue)
    local n = #sequence
    local newqueue
    if n >= m then
      if m == 0 then
        print(table.unpack(prefix))
      else
        local deleted = dropfirst(sequence)
        if n > m then
          newqueue = append(queue, {deleted, m, prefix})
        else
          newqueue = queue
        end
        return _combinations(deleted, m - 1,
                             append(prefix, sequence[1]),
                             newqueue)
      end
    end
    if #queue > 0 then
       newqueue = dropfirst(queue)
       local newargs = append(queue[1], newqueue)
       return _combinations(table.unpack(newargs))
    end

  end

  _combinations(sequence, m, {}, {})

end

我认为此版本是尾递归的。不幸的是,它不能像我的原始非尾递归版本那样以很好的顺序打印结果(更不用说代码的复杂性了),但是却不能拥有全部!


编辑:不,一个可以拥有一切!下面的版本是尾递归的,以与原始非尾递归版本相同的顺序打印结果:

function combinations (sequence, m, prefix, stack)
  prefix, stack = prefix or {}, stack or {}

  local n = #sequence
  if n < m then return end

  local newargs, newstack
  if m == 0 then
    print(table.unpack(prefix))
    if #stack == 0 then return end
    newstack = droplast(stack)
    newargs = append(stack[#stack], newstack)
  else
    local deleted = dropfirst(sequence)
    if n > m then
       newstack = append(stack, {deleted, m, prefix})
    else
       newstack = stack
    end
    local newprefix = append(prefix, sequence[1])
    newargs = {deleted, m - 1, newprefix, newstack}
  end

  return combinations(table.unpack(newargs)) -- tail call

end

它使用以下辅助功能:

function append (sequence, item)
  local copy = {table.unpack(sequence)}
  copy[#copy + 1] = item
  return copy
end

function dropfirst (sequence)
  return {table.unpack(sequence, 2, #sequence)}
end

function droplast (sequence)
  return {table.unpack(sequence, 1, #sequence - 1)}
end

示例:

> combinations({1, 2, 3, 4, 5}, 3)
1   2   3
1   2   4
1   2   5
1   3   4
1   3   5
1   4   5
2   3   4
2   3   5
2   4   5
3   4   5

具有讽刺意味的是,该版本通过实现其自己的堆栈实现了尾递归,因此我不确定它最终是否会比非尾递归版本更好。然后,我猜该函数的stack实际上是驻留在堆中(对吗?),因为Lua的表是按引用传递的(对吗?),所以毕竟这也许是一种改进。 (如果我错了,请纠正我!)