关于算法:从保存的状态恢复递归函数

Resuming a recursive function from a saved state

我有一个递归函数,该函数从一组n中产生所有长度为k的组合。通常称为" nCk"(" n选择k")。我希望将其设置为较大的值(56C22),以产生2,142,582,442,263,900个结果。由于实施方面的限制(我必须使用VBScript,并且无法保持登录计算机的时间超过我的工作时间),所以我将无法让它一劳永逸地完成。因此,我想定期保存该函数的当前状态并在以后恢复它...但是我似乎无法弄清楚该怎么做。递归困扰着我从逻辑上进行思考的能力。

我在这里仔细研究了建议的解决方案,否则搜索了"恢复递归函数"之类的方法,但无济于事。我希望通过一些指针(不是编程语言双关语)来使我走上正确的Rails。

在不包含实际代码的冗长解释中,首选实际算法(伪代码很好)。如果您想实际编写代码,我对C,C,Pascal,VB,JavaScript和VBScript最为熟悉(并且如上所述,目前正在使用VBScript)。

这是我的递归函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function nCk(aSet, iSetIndex, aSubset, iSubsetIndex)
    'Found result
    if (iSubsetIndex > ubound(aSubset)) then
        log"output.txt", join(aSubset), 1, false

        exit function
    end if

    'No more characters available
    if (iSetIndex >= ubound(aSet) + 1) then
        exit function
    end if

    'With aSet[iSetIndex]
    aSubset(iSubsetIndex) = aSet(iSetIndex)
    nCk aSet, iSetIndex + 1, aSubset, iSubsetIndex + 1

    'Without
    nCk aSet, iSetIndex + 1, aSubset, iSubsetIndex
end function  'nCk

仅供参考:我今年50岁;这不是功课。


存储递归并不是一件容易的事,因为要恢复该操作,您将需要还原堆栈,这不是一件简单的任务。我将使用一个迭代算法,它不像递归那样优雅。但是,如果需要中断/继续计算,它会奏效。

一个想法可能是:

  • 子集表示为0和1的向量。 0表示不采用元素,1-表示采用元素,因此集合{1,2,3}的[1,0,1]表示子集{1,3}。显然,只有长度为N的向量是实子集。
  • 将此向量视为一种堆栈,它表示您的"递归"状态
  • 此向量中的值-1用于触发迭代中的正确行为->类似于从递归返回/回溯。
  • 作为算法(首先,用于遍历所有子集):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    def calc_subsets(state, N):#N - number of elements in the original set
         while True: #just iterate
            if storeFlag:#you need to set this flag to store and interrupt
                store(state)
                return
            if len(state)==N and state[-1]!=-1: #a full subset is reached
                evaluate(state)
                state.append(-1)#mark for unwind

            if state[-1]==-1:#means unwind state
                state.pop()
                if not state: #state is empty
                    return #unwinded last element, we are done
                if state[-1]==1:#there is noting more to be explored
                   state[-1]=-1#mark for unwind in the next iteration
                else:# = 0 is explored, so 1 is the next to explore
                   state[-1]=1
            else: #means explore
                state.append(0) # 0 is the first to explore

    evaluate由您决定,我只打印出矢量:

    1
    2
    def evaluate(state):
        print state

    要打印3个元素的所有子集,应调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    calc_subsets([0], 3)
    >>>
    [0, 0, 0]
    [0, 0, 1]
    [0, 1, 0]
    [0, 1, 1]
    [1, 0, 0]
    [1, 0, 1]
    [1, 1, 0]
    [1, 1, 1]

    并仅打印第二部分:

    1
    2
    3
    4
    5
    6
    calc_subsets([0,1,1,-1], 3)
    >>>
    [1, 0, 0]
    [1, 0, 1]
    [1, 1, 0]
    [1, 1, 1]

    现在,该算法可以调整为仅迭代具有给定基数的所有子集。为此,必须跟踪当前子集中的元素数量,并在达到要求的子集大小时触发展开(通过将-1推入状态向量)。


    我不确定您的特定语言实现方式,但我相信在某些情况下将递归转换为迭代是很容易的。当您有一个显式堆栈时,将其写入磁盘并从上次中断的地方接起来应该不太复杂。

    一般示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    stack = [[argument1,argument2,argument3,...etc.]]

    while stack is not empty
      current_parameters = stack.pop

      aSet      = current_parameters[0]
      iSetIndex = current_parameters[1]
      ...etc.

      if ...

    else ...
        // push to stack instead of calling nCk
        stack.push([aSet, iSetIndex + 1, aSubset, iSubsetIndex + 1])

    if it's time to go home
        write stack to disc
        break