【问题标题】:Converting recursive permutation generator to iterative将递归置换生成器转换为迭代
【发布时间】:2011-10-06 16:58:48
【问题描述】:

我在将这种用于显示给定整数集的所有排列的递归算法转换为迭代算法时遇到了一些困难。

void getPermutationsR(int v[], int n, int i) 
{
    if (i == n)
    {
        //Display contents of v
    } 
    else
    {
        for (int j=i; j<n; j++) 
        {
            swap (v, i, j);
            getPermutationsR(v, n, i+1);
            swap (v, i, j);
        }
    }
}

这是我目前的尝试,完全错误,但如果不使用本机迭代算法来解决问题,我看不到任何纠正方法。我的一半尝试让我“弹出”多于“推送”(当我尝试访问空堆栈中的元素时导致错误),另一半我“推送”多于“弹出”(无限循环)。

void getPermutationsI(int v[], int n, int i) 
    {
    stack<int> iStack;
    stack<int> jStack;

    iStack.push(i);
    jStack.push(i);

    while(iStack.size() > 0)
    {
        if (iStack.top() == n)
        {
            jStack.pop();
            iStack.pop();
            //Display contents of v
        }
        else
        {
            for (int j = iStack.top(); j < n; j++)
            {
               //swap 
                               //something to do with jStack
            }
            //swap 
            iStack.push(i+1);
        }
    }
}

【问题讨论】:

    标签: c++ algorithm recursion permutation


    【解决方案1】:

    您需要阅读TAOCP的第7.2.1.2章。


    编辑:
    根据 cmets 中的讨论,基于堆栈的递归消除也很好(尽管在我看来这不是一种纯粹的迭代方法)。 这是基于堆栈的版本的草稿:

    void getPermutationsNR(int v[]) 
    {
        struct task
        {
            enum { swap, reccall } tasktype;
            int i, j;
        }
        stack<task> stack;
        stack.push(new task() {tasktype=reccall, i=0}); // initial task
        while (!stack.empty) // run task interpreter
        {
            task t = stack.pop();
            switch (t.tasktype)
            {
              case reccall:
                if (t.i == n) {/*display contents*/; break;}
                for (int j=t.i; j<n; j++)
                {
                    stack.push(new task() {tasktype=swap, t.i, j});
                    stack.push(new task() {tasktype=reccall, t.i+1});
                    stack.push(new task() {tasktype=swap, t.i, j});
                }
                break;
              case swap:
                swap(v, t.i, t.j);
                break;
            }
        }
    }
    

    【讨论】:

    • 尽管对于明显的家庭作业问题,我更喜欢轻推可复制算法的答案,但在没有关于该问题的任何进一步信息的情况下将某人发送到 4 卷的书籍根本不是一个有用的答案。 -1来自我。
    • @sbi:首先,TAOCP 是 7 卷,而不是 4 卷。其次,OP 提出的问题错误地认为可以通过使用递归解决方案来完成正确的算法,但事实并非如此。 TAOCP 是一本书,它完整地解释了问题,并尽可能地分析了算法。所以我的参考是 the 唯一正确的。不过,您的意见可能会有所不同。然后---继续自己回答这个问题。比 Knuth 教授做得更好。
    • 我明白你的意思。仍然。如果您将本章的内容总结为与问题相关,然后放入该链接,我很乐意投票。仅仅直接链接到一些文本就够糟糕了。链接到您向某人推荐的关于文本的文章几乎是恶毒的。
    • @sbi:在亚马逊上搜索 TAOCP 很简单。我的链接在那里是为了提示 OP TAOCP 是开发人员的圣经,只是 a 书。对整章进行总结并不是一件容易的事,我以前没有做过,很抱歉,我不会为了点赞而做。不过,请记住您的反对意见,会在这里介绍比第 7.2.1.2 章更好的东西吗?
    • 我的印象是不可能解决我的问题,我将通过阅读“TAOCP 的第 7.2.1.2 章”来了解原因——我打算这样做。但这需要我有这本书,目前我需要知道我是否在浪费时间。
    【解决方案2】:

    您可以使用 STL:

    a[]={0,1,2,....n-1};
    do{
        for(int i=0;i<n;++i)
          //  print v[ a[i] ]
        //print EOLN
    }
    while(next_permutation(a,a+n));
    

    未测试。

    【讨论】:

    • 重点是写递归算法的迭代版本,而不是解决实际问题。
    • @Vlad - 我认为这很明显:这并不能回答问题。使用next_permutation 生成的排列顺序与发布的算法不同,因此不一样。
    【解决方案3】:

    您可以使用堆栈进行迭代。在此堆栈中,您存储您的 j 变量。这应该可以。

    void getPermutationsI(int v[], int n)
    {
        int stack[100] = {0, 1, 2, 3, 4}; // you can do better, but I was lazy
        int k = 0;
        while (k >= 0)
        {
            if (k == n)
            {
                for (int i = 0; i < n; ++i)
                    printf("%d ", v[i]);
                printf("\n");
    
                --k;
                swap(v[k], v[ stack[k] - 1 ]);
            }
            else
            {
                if (stack[k] < n)
                {
                    swap(v[k], v[ stack[k] ]);
                    ++stack[k];
                    ++k;
                }
                else
                {
                    stack[k] = k;
                    --k;
                    swap(v[k], v[ stack[k] - 1 ]);
                }
            }
        }
    }
    

    【讨论】:

    • 您的解决方案不是基于 OP 的方法,是吗?
    • @Vlad - 我认为是。据我所知,它以与他的递归函数相同的顺序打印排列。想法是一样的。
    • 嗯,不完全。至少 OP 的算法在每一步进行两次交换,当堆栈深度为 n 时,除了打印什么都不做。
    • @Vlad - 如果您认为一个步骤是堆栈级别的实例,我也会在同一个步骤上进行两次交换。 OP 的算法进行一次交换,然后向上移动堆栈,然后进行另一次交换。这和我做的一样。而且它也不只是打印,你认为这是唯一发生的事情,因为从函数返回的细节(向下移动堆栈)是抽象的。我的--k 正在向下移动堆栈,递归算法会自动执行此操作。至于交换,这也发生在 OP 的算法中返回/向下移动堆栈后。
    • 从递归到迭代时,您必须四处移动,这无济于事。通过使用递归,许多逻辑被编程语言抽象出来,你必须在迭代实现中加以弥补。如果您printf 调用了什么以及在两个程序中的哪个位置,我想您会发现就逻辑/想法而言,两者是完全等价的。
    【解决方案4】:

    您遇到的挑战是函数调用和循环结构混合在一起。很难解开这些。

    首先让我们开始用递归替换所有流操作的控制。

    // You'll want to start with getPermutionsR(v, n, 0, 0)
    void getPermutationsR(int v[], int n, int i, int j) 
    {
        if (i == n)
        {
            //Display contents of v
        }
        else if (j == n) {
            // By doing nothing, we break out of the loop
        }
        else
        {
            // This was your recursive call inside of the loop.
            // Note that I'm sending you to to the first loop iteration here.
            swap (v, i, j);
            getPermutationsR(v, n, i+1, i+1);
            swap (v, i, j);
    
            // And the next loop iteration
            getPermutationsR(v, n, i, j+1);
        }
    }
    

    接下来让我们添加更多状态,以便在 if 条件内只有一个递归调用。

    // You'll want to start with getPermutionsR(v, n, 0, 0, 1)
    void getPermutationsR(int v[], int n, int i, int j, bool firstCall)
    {
        if (i == n)
        {
            //Display contents of v
        }
    
        int x = i;
        int y = j+1;
        if (firstCall) {
            swap (v, i, j);
            x = i+1;
            y = i+1;
        }
    
        // My one recursive call.  Note that i=n implies j=n.
        if (j < n) {
            getPermutationsR(v, n, x, y, !firstCall);
        }
    
        if (firstCall) {
            swap (v, i, j);
        }
    }
    

    现在我们已经完成了这项工作,我们可以将它以一种可以直接将其转换为迭代版本的形式存在。这是转型

    void recursiveForm (params, recursiveState) {
        topHalf...
        if (cond) {
            recursiveForm(...)
        }
        bottomHalf...
    }
    

    变成

    void iterativeForm(params) {
        initializeStacks...
        pushStacks...
        topHalf...
        while (stacks not empty) {
            if (cond) {
                pushStacks...
                topHalf...
            }
            else {
                bottomHalf...
                popStacks...
            }
        }
    }
    

    所以应用该模式,我们会得到如下结果:

    // You'll want to start with getPermutionsR(v, n, 0, 0, 1)
    void getPermutationsI(int v[], int n)
    {
        stack<int> iStack;
        stack<int> jStack;
        stack<bool> firstCallStack;
    
        // pushStacks with initial value
        iStack.push(0);
        jStack.push(0);
        firstCallStack.push(1);
    
        // topHalf...
        if (iStack.top() == n)
        {
            //Display contents of v
        }
    
        int x = iStack.top();
        int y = jStack.top()+1;
        if (firstCallStack.top()) {
            swap (v, iStack.top(), jStack.top());
            x = iStack.top()+1;
            y = iStack.top()+1;
        }
    
        while iStack.size() > 0) {
            // if (cond) {
            if (jStack.top() < n) {
                //pushStacks...
                iStack.push(x);
                jStack.push(y);
                firstCallStack.push(!firstCallStack.top());
    
                // topHalf...
                if (iStack.top() == n)
                {
                    //Display contents of v
                }
    
                x = iStack.top();
                y = jStack.top()+1;
                if (firstCallStack.top()) {
                    swap (v, iStack.top(), jStack.top());
                    x = iStack.top()+1;
                    y = iStack.top()+1;
                }
            }
            else {
                // bottomHalf...
                if (firstCallStack.top()) {
                    swap (v, iStack.top(), jStack.top());
                }
            }
        }
    }
    

    (警告,所有代码都未经测试,甚至可能无法编译。但想法是正确的。而且绝对是相同的算法。)

    【讨论】:

      【解决方案5】:

      在 Python 中:

      def perm_stack(s):
          st = []
      
          st.append((s,0))
      
          while not len(st)==0:
      
              t = st.pop()
              if t[1]==len(s):
                  print t[0]
              else:
                  for i in range(t[1], len(s)):
                      s1 = swap(t[0], t[1], i)
                      st.append((s1, t[1]+1))
      

      【讨论】:

        猜你喜欢
        • 2013-12-12
        • 2016-01-26
        • 2017-03-05
        • 1970-01-01
        • 2011-03-11
        • 2016-06-29
        相关资源
        最近更新 更多