C++ 使用索引j之前的至少n项查找所有组合 算法欲望

C++ 使用索引j之前的至少n项查找所有组合 算法欲望,c++,algorithm,combinations,subset,C++,Algorithm,Combinations,Subset,我正在尝试编写一个算法来查找数组中所有k项的长度组合,但也必须在索引j之前使用n项,因为我们需要的(n,j)对就有很多 例子 使用索引2之前的至少一项(akarestrictions={{1,2,3,4})查找{1,2,4}中的所有两项组合。这将导致{{1,2}、{1,3}、{1,4}、{2,3}、{2,4} 从{1,2,3,4,5,6,7}中查找所有三个项目组合,使用索引2之前的至少一个项目和索引4之前的两个项目,也称限制={{1,2},{2,4} 进展 我已经能够找到从一堆集合中选择一个项目

我正在尝试编写一个算法来查找数组中所有
k
项的长度组合,但也必须在索引
j
之前使用
n
项,因为我们需要的
(n,j)
对就有很多

例子
  • 使用索引2之前的至少一项(aka
    restrictions={{1,2,3,4}
    )查找
    {1,2,4}
    中的所有两项组合。这将导致
    {{1,2}、{1,3}、{1,4}、{2,3}、{2,4}

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

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

    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}}
    }
    
    void添加组合到(向量前缀,向量::迭代器开始,
    向量::迭代器停止、向量和ret){
    if(start==stop){ret.push_back(前缀);return;}
    用于(自动v:*启动){
    自动下一步=前缀;
    下一步,向后推(v);
    将组合添加到(下一步,开始+1,停止,返回);
    }
    };
    int main(){
    向量v{{1,2},{3,4};
    向量ret;
    向量init{};
    将组合添加到(init,v.begin(),v.end(),ret);
    //ret现在包含{1,3}、{1,4}、{2,3}、{2,4}
    }
    

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

    问题,与许多序列枚举问题一样,这一问题符合标准词典枚举算法(引自:

    • 从词典编纂的最小可能序列开始
    • 虽然可能:
      • a。向后扫描以找到可以增加的最后一个元素。(“可能是”意味着增加该元素仍然会导致某些有效序列的前缀。)
      • b。将该元素增加到可能的下一个最大值
      • c。用尽可能小的后缀填充序列的其余部分
    如果没有其他限制,则长度-
    j
    sequence
    p==p0,p1,…,pj−1
    可以是长度为
    k
    的前缀,它是
    n
    事物的组合
    k−j
    。我们可以将其重写为
    pj−1

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

    /* 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;
    }
    
    (这显然不是最优的。我们不必检查每个元素的所有限制,只需计算一个简单的最大值向量,然后根据该最大值测试元素。)

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

    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;
    }
    
    void firstSuffix(标准::向量和梳,大小\u t pfx\u长度){
    尺寸=pfx长度?梳[pfx长度-1]+1:0;
    用于(自动it=comb.begin()+pfx_length;it!=comb.end();+it)
    *it=val++;
    }
    
    然后我们可以写循环:

    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;
    }
    
    intmain(){
    向量限制={1,2},{2,4},{3,7};
    size\t k=std::max\u元素(restrictions.begin(),restrictions.end())->first;
    如果(k==0)返回0;/*空集*/
    std::向量梳(k);
    标准:尺寸\u t pfx=0;
    做{
    第一个后缀(梳、pfx);
    
    对于(auto const&val:comb)std::cout,似乎可以简化限制。对于您的示例{1,2,3,4,5,6,7},我将使用
    std::next_permutation
    。似乎可以简化限制。对于您的示例数据集={1,2,3,4,5,6,7},限制={1,2},{2,4}。限制可以是{1,2},(2-1),4},因为{1,2}假设前2中至少使用了一项,所以对于{2,4}只需要使用一项。示例2的解决方案是{1,2,3},{1,2,4},{1,3,4},{2,3,4},非常棒!感谢您的深入回答和所有解释。它有助于了解导致一个好的解决方案的背景主题。当然,示例代码很棒。