将递归排列生成器转换为迭代式

4

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

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);
        }
    }
}
5个回答

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);
    }
}

接下来,让我们增加更多的状态,以便我们仅在条件语句内部进行一次递归调用。

// 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());
            }
        }
    }
}

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

注意:所有代码未经测试,可能甚至无法编译。但是思路是正确的。而且它绝对是相同的算法。

0

你可以使用堆栈来使其变成迭代式。在这个堆栈中,您存储您的 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 ]);
            }
        }
    }
}

你的解决方案并不是基于原帖提出的方法,对吧? - Vlad
1
@Vlad - 我认为是这样的。就我所知,它按照他的递归函数打印排列顺序。而且思路也是一样的。 - IVlad
好吧,不完全是这样。至少原帖的算法在每一步中都进行了两次交换,并且在堆栈深度为 n 时除了打印之外什么也没做。 - Vlad
@Vlad - 如果您将步骤视为堆栈级别的实例,我也会在同一步骤上进行两次交换。OP的算法执行一次交换,然后向上移动堆栈,然后执行另一次交换。这与我的做法相同。它不仅仅是打印输出,因为从函数返回(向下移动堆栈)的细节被抽象化了。我的“--k”是向下移动堆栈,递归算法会自动执行此操作。至于交换,这也发生在OP的算法中,在返回/向下移动堆栈之后进行。 - IVlad
当从递归转换为迭代时,您必须移动事物,这是不可避免的。通过使用递归,编程语言抽象了许多逻辑,您必须在迭代实现中弥补这种抽象。如果在两个程序中都使用 printf 输出被调用和哪里被调用的内容,我认为您会发现就逻辑/思想而言,两者完全等效。 - IVlad

0

在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))

-1

您需要阅读TAOCP的第7.2.1.2章节。


编辑:
根据评论中的讨论,基于堆栈的递归消除也是不错的选择(尽管我认为这并不是纯迭代的方法)。 以下是一个基于堆栈的版本草稿:
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;
        }
    }
}

1
尽管我更喜欢提示可复制的算法来回答明显的作业问题,但是如果没有关于问题的进一步信息,就把某人送到一个四卷本的书中并不是一个有用的答案。我给你打负分。 - sbi
1
我理解你的观点。但是,如果你将章节内容总结为与问题相关,并将链接放入其中,我很乐意点赞。直接链接到某些文本已经够糟糕了。而将你要引用的文本链接到一篇关于该文本的文章几乎是恶意的。 - sbi
@sbi:在亚马逊上搜索TAOCP很容易。我提供的链接是为了提示OP,TAOCP是开发者的圣经,只是一本书而已。总结整个章节并不是一项微不足道的任务,我以前没有做过,抱歉,我不会因为一个赞而去做它。尽管如此,考虑到你的踩,你会比7.2.1.2章节更好地呈现吗? - Vlad
我有一种感觉,似乎解决我的问题是不可能的,而且我需要通过阅读《计算机程序设计艺术》第7.2.1.2章节来了解原因 - 我打算这样做。但这需要我拥有这本书,目前我需要知道是否在浪费时间。 - user461697
@ajnatural:使用栈应该不会很复杂。 - Vlad
显示剩余9条评论

-1

你可以使用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));

未经测试。


重点在于编写递归算法的迭代版本,而不是解决实际问题。 - user461697
1
@Vlad - 我觉得这很明显:这并没有回答问题。使用next_permutation生成的排列顺序与发布的算法不同,因此它们不同。 - IVlad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接