查找所有组合,使用索引j之前的至少n个项目

时间:2017-04-18 07:29:08

标签: c++ algorithm combinations subset

算法愿望

我试图编写一个算法来查找数组中项目的所有k长度组合,但这也必须在索引n之前使用j个项目,因为我们希望的这些(n,j)对中有很多对。

实施例

  1. 使用索引2之前的至少一个项目(又名{1,2,3,4})从restrictions = {{1,2}}查找所有两个项目组合。这应该导致{{1,2},{1,3},{1,4},{2,3},{2,4}}

  2. 使用索引2前的至少一个项目和{4}之前的两个项目{1,2,3,4,5,6,7}查找restrictions = {{1,2},{2,4}}中的所有三个项目组合。

  3. 的进展

    我已经能够找到从一堆中选择一个项目的所有组合。

    void add_combinations_to(vector<int> prefix, vector<vector<int>>::iterator start, 
                             vector<vector<int>>::iterator stop, vector<vector<int>>& ret) {
        if(start == stop) {ret.push_back(prefix); return;}
        for(auto v : *start) {
            auto next = prefix;
            next.push_back(v);
            add_combinations_to(next, start + 1, stop, ret);
        }
    };
    
    int main() {
        vector<vector<int>> v{{1,2},{3,4}};
        vector<vector<int>> ret;
        vector<int> init{};
        add_combinations_to(init, v.begin(), v.end(), ret);
        // ret now contains {{1,3},{1,4},{2,3},{2,4}}
    }
    

    我觉得有一种方法可以扩展它。现在我需要退一步,但建议会很棒。在大多数情况下,这只是一个

1 个答案:

答案 0 :(得分:2)

与许多序列枚举问题一样,这个问题产生了标准的词典枚举算法(引自Algorithm for "consolidating" N items into K):

  • 从按字典顺序排列的最小序列开始
  • 虽然可能:
    • 一个。向后扫描以找到可以增加的最后一个元素。 (&#34;可能是&#34;意味着增加该元素仍会产生某些有效序列的前缀。)
    • 湾将该元素增加到下一个可能的最大值
    • ℃。使用尽可能小的后缀填写序列的其余部分。

如果没有其他限制,则j序列p == p0, p1, …, pj−1的长度可以是长度的前缀 - k {if} n事件的k−j < n−pj−1组合pj−1 < n − k + j) }。我们可以将其重写为/* Finds the (lexicographically) next prefix of a k-combination from n * values, and returns the length of that prefix. * If there is no possible next prefix, returns 0. * Note: k is implicit in the length of the combination */ size_t nextPrefix(std::vector<size_t>& comb, size_t n) { size_t k = comb.size(); for (size_t j = k; j;) { --j; if (comb[j] < n - k + j) { ++comb[j]; return j + 1; } } return 0; }

因此,对于不受限制的k,n组合,以下简单函数将完成上述通用算法的步骤a和b:

ki

为了继续解决实际问题,我们注意到表单的任何限制都包含niki值的ni&#34;最终将完全相同的测试,因为该限制可以改为&#34; n - 长度前缀是<k, n>值的组合。&#34;

因此,我们可以使用对向量替换上述函数中的size_t nextPrefix(std::vector<size_t>& comb, std::vector<std::pair<size_t, size_t>> const& restrictions) { for (size_t j = comb.size(); j;) { --j; /* Test all applicable conditions */ if (std::all_of(restrictions.begin(), restrictions.end(), [&j, &comb](std::pair<size_t, size_t> const& rest) { return j >= rest.first or comb[j] < rest.second - rest.first + j;})) { ++comb[j]; return j + 1; } } return 0; } 参数(其中最后一对精确为void firstSuffix(std::vector<size_t>& comb, size_t pfx_length) { size_t val = pfx_length ? comb[pfx_length - 1] + 1 : 0; for (auto it = comb.begin() + pfx_length; it != comb.end(); ++it) *it = val++; } ):

int main() {
  std::vector<std::pair<size_t, size_t>> restrictions = {{1, 2}, {2, 4}, {3, 7}};
  size_t k = std::max_element(restrictions.begin(), restrictions.end())->first;
  if (k == 0) return 0; /* Empty set */
  std::vector<size_t> comb(k);
  std::size_t pfx = 0;
  do {
    firstSuffix(comb, pfx);
    for (auto const& val : comb) std::cout << std::setw(3) << val;
    std::cout << '\n';
  } while (pfx = nextPrefix(comb, restrictions));
  return 0;
}

(这显然不是最优的。我们可以只计算一个最大值的简单向量,然后根据该最大值测试该元素,而不是检查每个元素的所有限制。)

要实际生成序列,我们需要能够填写后缀(通用算法的最后一步)并构造循环:

(global-set-key [(control tab)] 'indent-rigidly)

然后我们可以编写循环:

(global-set-key (kbd "C-<tab>") 'indent-rigidly)

live on coliru