[英]Turning recursive algorithm to generate all combinations into an iterative
誰能幫助我將這種算法變成一種迭代算法。 我知道遞歸只是迭代加上堆棧,但是到目前為止,我還沒有提出合適的解決方案。
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;
}
我不會給你答案,但是我會給你關鍵技巧,以合理的方式來做。
您的思維障礙的一部分是,您有兩種類型的控制交織在一起。 第一個是您的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_LOOP
, EXIT_LOOP
, EXIT_FUNCTION
, ENTER FUNCTION
。 (請注意,堆棧中的最后一項將首先發生。)然后在調用堆棧上添加函數調用的參數,以便在進行遞歸調用時它們就在其中。 EXIT_LOOP
:執行chosen.pop_back()
並增加當前調用框架中的i
。 (這很重要,呼叫幀需要分開存放!) EXIT_FUNCTION
:擺脫頂部調用框架,它已完成。 一旦您認為自己了解這種策略,就去學習Forth 。 :-)
如果您只需要迭代算法,那么我認為您的方向錯誤。 確實不需要堆棧。
如果出於某種原因確實需要一個堆棧,請忽略休息。
為了便於說明,我以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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.