【问题标题】:Prevent memory allocation in recursive combination generation防止递归组合生成中的内存分配
【发布时间】:2019-12-13 17:13:58
【问题描述】:

(对不起,标题不是最好的描述)

我正在玩图论,并生成一组给定输入数字的所有可能组合。给定输入集 {2,3,4},我可能的组合(其中有3!)是:

以下递归解决方案有效,但我不喜欢我必须“复制”输入向量以“删除”代表我正在关注的节点的元素以防止将其包含在输出中的事实再次。我要输出的元素存储在vecValues,而我当前可以选择的元素存储在vecInput

void OutputCombos(vector<int>& vecInput, vector<int>& vecValues)
{
    // When hit 0 input size, output.
    if (vecInput.size() == 0)
    {
        for (int i : vecValues) cout << i << " ";
        cout << endl;
    }
    size_t nSize = vecInput.size();
    for (vector<int>::iterator iter = begin(vecInput); iter != end(vecInput); ++iter)
    {
        auto vecCopy = vecInput;
        vecCopy.erase(find(begin(vecCopy), end(vecCopy), *iter));
        vecValues.push_back(*iter);
        OutputCombos(vecCopy, vecValues);
        vecValues.pop_back();
    }
}

void OutputCombos(vector<int>& vecInput)
{
    vector<int> vecValues;
    OutputCombos(vecInput, vecValues);
}

int main()
{
    vector<int> vecInput{ 2,3,4 };
    OutputCombos(vecInput);
    return 0;
}

正如我的状态空间树所期望的那样,输出是

2 3 4 2 4 3 3 2 4 3 4 2 4 2 3 4 3 2

请问如何在不必为每个递归调用复制向量的情况下解决这个问题?

【问题讨论】:

  • 为什么不创建图形结构?有节点和链接?
  • @bolov,我现在正在做的就是输出组合。我们可以用创建树节点来替换“当前”元素的添加;但是,这仍然无法解决必须在每个阶段复制向量才能删除元素的问题......你知道如何解决这个问题吗?

标签: c++ recursion graph-theory


【解决方案1】:

您总是可以只使用来自&lt;algorithm&gt;std::next_permutation


#include <algorithm>
#include <iostream>
#include <vector>

int main()
{
  std::vector<int> input {2, 3, 4};

  do {
        for (auto i : input) std::cout << i << " ";
        std::cout << std::endl;
     }  while(std::next_permutation(input.begin(), input.end()));

    return 0;
}

这会为您提供相同的输出。您可能想查看 next_permutation 的 a possible implementation,它涉及向量内的交换,而不是多次复制向量。

【讨论】:

  • 谢谢;我知道这个函数,但是它的使用不符合构建图表的性质。也许需要更多的细节来证明这一点。我正在研究旅行商问题。建立一个图表,显示所有可能的路线;但是,如果我们存储“当前”最小值,如果我们可以看到它的距离将超过当前最小值,我们可以防止进一步遍历某个路径。因此,提前生成所有组合是一种浪费。这个问题实际上只是询问如何生成排列的一种愚蠢的方式......希望澄清......
【解决方案2】:

我认为这可能更接近您正在寻找的内容。没有std::next_permutation 的版本不涉及复制任何向量,并允许输入保持const。但是,这样做的代价是在每次迭代中检查输出,以确保它不会两次添加相同的数字。

#include<vector>
#include<iostream>
#include<algorithm>

template<typename T>
void OutputCombinations(
    const std::vector<T>& input,
    std::vector<typename std::vector<T>::const_iterator >& output)
{
  for(auto it = input.begin(); it != input.end(); ++it)
  {
    if (std::find(output.begin(), output.end(), it) == output.end())
    {
      output.push_back(it);
      if (output.size() == input.size())
      {
        for(auto node : output) std::cout << *node << " "; 
        std::cout << std::endl;
      }
      else OutputCombinations(input, output);

      output.pop_back();
    }
  }
}

int main()
{
  std::vector<int> nodes{ 2, 3, 4, 2 };
  std::vector<std::vector<int>::const_iterator> result{};
  OutputCombinations(nodes, result);

  return 0;
}

【讨论】:

  • 啊,是的,我知道你在那里做了什么。这也可能比我的更快,因为find() 可能具有O (log n) 复杂性,而我对vecTaken 的搜索可能是O(n)。但是,如果我们在输入中有重复的值,就会出现这个问题……在旅行商问题的情况下,我们可以有重复的值(距离)。不过还是谢谢你回来找我:)
  • 感谢您发布@Wad。提示思考算法是个好问题。
  • @Wad 我已经更改它现在可以处理重复项
  • 啊,我明白你做了什么;迭代器总是唯一的。不错!
【解决方案3】:

经过大量学习,我在this article 中找到了灵感,这给了我最终的解决方案。这个想法是我们保留一个由Boolean 值组成的向量,它指示组合中是否使用了特定值;这样我们就不需要删除我们已经使用过的元素,因此没有内存分配开销。

所以,在构建分支{2,4,3} 时,如果我们到达{2,4}vecTaken 将是{true, false, true}nNumBoolsSet 将是2。所以当我们循环时,我们只会“使用”该元素在 vecInput 的索引 1 处,因为这是唯一没有按照 vecTaken 的指示使用的元素。

void OutputCombos(vector<int>& vecInput, vector<int>& vecValues, vector<bool>& vecTaken, int& nNumBoolsSet)
{
    size_t nSize = vecInput.size();
    if (nNumBoolsSet == nSize)
    {
        for (int i : vecValues) cout << i << " ";
        cout << endl;
        return;
    }
    for (vector<int>::size_type i = 0; i < nSize; ++i)
    {
        if (vecTaken[i] == false)
        {
            vecValues.push_back(vecInput[i]);
            vecTaken[i] = true;
            ++nNumBoolsSet;
            OutputCombos(vecInput, vecValues, vecTaken, nNumBoolsSet);
            vecTaken[i] = false;
            vecValues.pop_back();
            --nNumBoolsSet;
        }
    }
}

void OutputCombos(vector<int>& vecInput)
{
    vector<int> vecValues;
    vector<bool> vecTaken(vecInput.size(), false);
    int nNumBoolsSet = 0;
    OutputCombos(vecInput, vecValues, vecTaken, nNumBoolsSet);
}

int main()
{
    vector<int> vecInput{ 2,3,4 };
    OutputCombos(vecInput);
}

【讨论】:

    猜你喜欢
    • 2016-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-02
    • 1970-01-01
    • 1970-01-01
    • 2017-10-28
    • 1970-01-01
    相关资源
    最近更新 更多