用C ++中的递归替换N级for循环

时间:2017-07-07 01:05:13

标签: c++ for-loop recursion tail-recursion

我现在已经尝试了一段时间来想出一种计算所有字符串组合的方法。与大多数在网络上组合的方法不同,算法必须产生每种组合,包括其中所有组合元素不在单一组合中的组合。即,如果我将'Hello','New'和'World'组合在一起,我正在寻找的组合是:

HelloNewWorld
HelloNew
HelloWorld
Hello
NewWorld
New
World

我大学的一位教授确实提出了一个快速而肮脏的解决方案,但它正在使用嵌套for循环。

#include <iostream>
#include <vector>
#include <array>
#include <string>

int main()
{
std::vector<std::array<std::string, 2>> vec(3);
vec[0] = {"Hello", ""};
vec[1] = {"New", ""};
vec[2] = {"World", ""};
for (int i = 0; i < 2; i++)
    for (int j = 0; j < 2; j++)
        for (int k = 0; k < 2; k++)
            std::cout << vec[0][i] + vec[1][j] + vec[2][k] << std::endl;
}

正如您可能想象的那样,我希望有一种方法可以让它实际上有些可用和便携。我知道递归会有可能,我只是不知道如何实现它。最好的是,如果可能的话,我想使这个尾递归,因为计划是计算非常大的组合。以递归方式执行此操作的最佳方法是什么?是否可以轻松实现尾递归?

4 个答案:

答案 0 :(得分:2)

通过使用k=1k=N的所有组合,您可以非常有效地执行此操作,以获得N个元素的向量。使用Howard Hinnant的库here,您可以非常有效地使用它。就我而言,我已将库命名为sampling.h,这是唯一的外部依赖项,可以用here完整查看。

#include "sampling.h"
#include <iostream>
#include <vector>


/**
 *  This function can take any container that has a bidirectional
 *  iterator (std::list, std::deque, std::vector) that contains elements
 *  of type std::string or similar, that must implement an `operator+`
 *  and `operator<<` for printing.
 */
template <typename BiDirStringContainer>
void print_combinations(BiDirStringContainer& container)
{
    auto first = container.begin();
    auto last = container.end();
    for (size_t i = 1; i <= container.size(); ++i) {
        auto mid = first + i;
        for_each_combination(first, mid, last, [](auto f, auto l) {
            std::string w;
            for (; f != l; ++f) {
                w += *f;
            }
            std::cout << w << std::endl;
            return false;
        });
    }
}


int main(void)
{
    std::vector<std::string> words = {
        "Hello",
        "New",
        "World",
    };
    print_combinations(words);

    return 0;
}

使用C ++ 14标准进行编译并运行它输出:

Hello
New
World
HelloNew
HelloWorld
NewWorld
HelloNewWorld

这正是你的帖子所描述的。由于lambda是一个自定义仿函数,并且可以存储状态,因此您可以使用组合执行任何操作:存储副本,打印它们等。

这比没有重大工作的标准库或者标准库的建议要快得多。例如,std::next_combinationstd::next_permutation(前者未包括在内,但建议使用here)。我强烈建议阅读Howard Hinnant的完整博客文章:它具有启发性。他实施的时间复杂性和粗暴的速度超过了大多数其他建议。如果您需要高性能组合或排列,他已经为您完成了工作。

答案 1 :(得分:2)

在每个级别,无论是否使用当前单词打印结果,当它到达所有单词的末尾时,它都会递归:

#include <iostream>
#include <string>
#include <vector>

void recurse(std::vector<std::string> &values,size_t level,std::string str) {
  if (level<values.size()) {
    recurse(values,level+1,str+values[level]);
    recurse(values,level+1,str);
  } else {
    std::cout<<str<<"\n";
  }
}

int main(int argc, char*argv[]) {
  if (argc<2)
    std::cout<<argv[0]<<" <word> [<word> [...]]\n";
  else {
    std::vector<std::string> values;
    for(int i=1;i<argc;++i) {
      values.push_back(argv[i]);
    }
    recurse(values,0,"");
  }
  return 0;
}

当使用./a.out Hello New World运行时产生:

HelloNewWorld
HelloNew
HelloWorld
Hello
NewWorld
New
World

答案 2 :(得分:0)

如果唯一的可能性是出现或不出现一个单词,那就有两种可能性。所以对于n个单词,你有2 ^ n个组合。因此,您只需计算从0(包括)到2 ^ n-1(包括)的2 ^ n个数字,并将每个位映射到一个字。

不需要递归,只需要一个循环计数。

答案 3 :(得分:0)

如果我理解正确,你想生成一个字符串的所有组合。在这种情况下,您可以使用BFS以及一个集合和一个队列来生成组合,我将尝试解释。

说你的字符串是ABCD。您有一个添加ABCD的队列和一个您现在添加ABCD的集合

while the queue is not empty
1) you pop the top element
2) you generate substrings of that popped element
  a) if that substring is not in the set add it to the queue

要在步骤2中生成子字符串,请执行以下操作

for(int i =0;i<string.length();i++)
{
  string substr1 = string.substr(0,i);
  string substr2 = string.substr(i,string.length()-1);
  string substring = substr1+substr2;
}

ABCD(输入字符串)上执行此操作会生成BCDACDABD以及ABC。现在将这些3添加到集合和队列

现在,您将BCDACDABD添加到该集合中。可以说BCDqueue.front()。您弹出它并生成CDBDBC,然后将它们添加到set和队列中。当您下次点击ACD时,您会生成CDADAC,但现在您不会将CD添加到队列中,因为它位于集合中。< / p>

修改

我看到了您的问题,我的答案适用于字符串,但您可以在vector<string>上使用相同的原则生成所有组合ABCD只是Hello(A)World(B)...