旋转递归算法以将所有组合生成为迭代

时间:2018-10-01 21:29:15

标签: c++ algorithm

任何人都可以帮助我将此算法转换为迭代算法。我知道递归只是迭代加上堆栈,但是到目前为止我还没有想出合适的解决方案。

void rGenCombs(int n, int k, vector<int>& chosen, int idx,
               vector<vector<int>>& combs) {
    if (chosen.size() == k) {
        combs.push_back(chosen);
        return;
    }
    for (int i = idx; i <= n; ++i) {
        chosen.push_back(i);
        rGenCombs(n, k, chosen, i + 1, combs);
        chosen.pop_back();
    }
}

vector<vector<int>> genCombsRec(int n, int k) {
    vector<vector<int>> combs;
    vector<int> chosen;
    rGenCombs(n, k, chosen, 1, combs);
    return combs;
}

更新,我现在有这个。问题是我不知道该写哪个循环。我猜应该可以通过一个简单的while循环以某种方式实现。

vector<vector<int>> genCombs(int n, int k) {
    vector<int> numStack, chosen;
    vector<vector<int>> combs;
    numStack.push_back(1);
    while (!numStack.empty()) {
        if (chosen.size() == k) {
            combs.push_back(chosen);
            chosen.pop_back();
            continue;
        }
        chosen.push_back(numStack.back());
        if (numStack.back() <= n) {
            numStack.push_back(numStack.back() + 1);
        } else {
            numStack.pop_back();
        }
    }
    return combs;
}

解决方案

对于不需要堆栈的其他迭代算法,我得出以下结论:

int getNextIncIndex(const vector<int>& combs, int n) {
    int k = static_cast<int>(combs.size());
    for (int i = k - 1; i >= 0; --i) {
        int distFromRight = k - i - 1;
        if (combs[i] < n - distFromRight) {
            return i;
        }
    }
    return -1;
}

vector<vector<int>> genCombs(int n, int k) {
    vector<vector<int>> combs;
    vector<int> comb(k, 1);
    iota(comb.begin(), comb.end(), 1);
    while (true) {
        for (int i = comb[k - 1]; i <= n ; ++i) {
            comb[k - 1] = i;
            combs.push_back(comb);
        }
        int incIdx = getNextIncIndex(comb, n);
        if (incIdx == -1) {
            break;
        } else {
            iota(comb.begin() + incIdx, comb.end(), comb[incIdx] + 1);
        }
    }
    return combs;
}

2 个答案:

答案 0 :(得分:2)

我不会给你答案,但是我会给你关键技巧,以合理地机械地完成它。

您的智力障碍的一部分是,您有两种类型的控制交织在一起。第一个是您的for循环。第二个是递归。如何从for循环的内部跳到外部循环并递归,而又回到for循环的内部?很容易感到困惑。

但是引入的不是一个堆栈,而是两个 堆栈。一个堆栈是用来跟踪您需要执行的操作。另一个用于呼叫帧中的所有数据。最终代码的关键部分如下所示:

while (0 < actions.size()) {
    action thisAction = actions.pop_back();
    switch (thisAction.type) {
        case ENTER_FUNCTION:
            ...
            break;
        case ENTER_LOOP:
            ...
            break;
        case EXIT_LOOP:
            ...
            break;
        case EXIT_FUNCTION:
            ...
            break;
    }
}

现在,您以统一的方式跟踪循环和函数调用。不再混乱。

这是您在每个部分中要做的事情。

  • ENTER_FUNCTION:检查是否存在,确定是否有循环,然后将其设置为开始并将ENTER_LOOP附加在操作堆栈上。 (如果您不会循环播放,请执行if。)
  • ENTER_LOOP:测试循环条件。如果匹配,则建立循环,并在操作堆栈上附加ENTER_LOOPEXIT_LOOPEXIT_FUNCTIONENTER FUNCTION。 (请注意,堆栈中的最后一项将首先发生。)然后在调用堆栈上添加函数调用的参数,以便在进行递归调用时它们就在那里。
  • EXIT_LOOP:执行chosen.pop_back()并增加当前调用框架中的i。 (这很重要,呼叫帧必须分开放置!)
  • EXIT_FUNCTION:摆脱掉最前面的调用框架,它就完成了。

一旦您认为自己了解这种策略,就去学习Forth。 :-)

答案 1 :(得分:1)

如果您只需要迭代算法,我认为您的方向错误。真的不需要堆栈。

如果出于任何原因要做想要堆放,请忽略其余部分。

出于说明目的,我使用n=6, k=3运行您的代码:

1 2 3 
1 2 4 
1 2 5 
1 2 6 
1 3 4 
1 3 5 
1 3 6 
1 4 5 
1 4 6 
1 5 6 
2 3 4 
2 3 5 
2 3 6 
2 4 5 
2 4 6 
2 5 6 
3 4 5 
3 4 6 
3 5 6 
4 5 6 

您会看到一个简单的模式,它导致了简单的算法:

  • 采取最后一个位置。增加它。这将给出下一个组合。

  • 一旦达到最高点,即该位置不能再增加,请增加下一个“可递增”位置,并std::iota移到右边。

  • 重新开始,继续前进直到不再有可递增的位置。

一个非常肮脏但可行的实现,还有很大的提升空间:

#include <numeric>

int find_incrementable(std::vector<int>& current, int n)
{
    int pos;
    current.push_back(n + 1);   // Dirty hack
    for (pos = current.size() - 2; pos >= 0; --pos) {
        if (current[pos] + 1 < current[pos + 1]) {
            break;
        }
    }
    current.pop_back();
    return pos;
}

std::vector<std::vector<int>> genCombsIter(int n, int k)
{
    std::vector<std::vector<int>> combs;
    std::vector<int> current(k);
    std::iota(current.begin(), current.end(), 1);
    combs.push_back(current);

    int position = k - 1;
    int incrementable;
    while ((incrementable = find_incrementable(current, n)) >= 0) {
        if (incrementable == position) {
            current[position] += 1;
        } else {
            if (incrementable == -1) {
                break;
            }
            std::iota(current.begin() + incrementable, current.end(), current[incrementable] + 1);
            position = k - 1;
        }
        combs.push_back(current);
    }
    return combs;
}