【问题标题】:all combinations of k elements out of nn 个元素中 k 个元素的所有组合
【发布时间】:2011-02-23 18:34:05
【问题描述】:

有人可以提供一个函数的链接或伪代码来查找 n 中 k 个元素的所有组合吗?可能在 STL 中。我不需要计算 n 选择 k,我需要列出所有大小为 k 的数字的向量。

谢谢

【问题讨论】:

  • 什么是“我不需要计算 n 选择 k,我需要列出所有大小为 k 的数字的向量。”意思是?不管怎样,写一个next_combination函数就是straightforward
  • 您是否尝试过自己做这件事?听起来像你要求我们做你的功课。
  • 我建议将其移至programmers.stackexchange.com
  • @Andrei 我认为这是面向算法的,因此更适合这里。 Programmers.stackexchange 就像“我的团队中没有人了解组合数学,我们如何自学呢?”

标签: c++ stl combinatorics


【解决方案1】:

在 C++ 中给出以下例程:

template <typename Iterator>
inline bool next_combination(const Iterator first, Iterator k, const Iterator last)
{
   /* Credits: Thomas Draper */
   if ((first == last) || (first == k) || (last == k))
      return false;
   Iterator itr1 = first;
   Iterator itr2 = last;
   ++itr1;
   if (last == itr1)
      return false;
   itr1 = last;
   --itr1;
   itr1 = k;
   --itr2;
   while (first != itr1)
   {
      if (*--itr1 < *itr2)
      {
         Iterator j = k;
         while (!(*itr1 < *j)) ++j;
         std::iter_swap(itr1,j);
         ++itr1;
         ++j;
         itr2 = k;
         std::rotate(itr1,j,last);
         while (last != j)
         {
            ++j;
            ++itr2;
         }
         std::rotate(k,itr2,last);
         return true;
      }
   }
   std::rotate(first,k,last);
   return false;
}

然后您可以继续执行以下操作:

// 9-choose-3 
std::string s = "123456789";
std::size_t k = 3;
do
{
   std::cout << std::string(s.begin(),s.begin() + k) << std::endl;
}
while(next_combination(s.begin(),s.begin() + k,s.end()));

或者对于 int 的 std::vector:

// 5-choose-3 
std::size_t n = 5;
std::size_t k = 3;

std::vector<int> ints;
for (int i = 0; i < n; ints.push_back(i++));

do
{
   for (int i = 0; i < k; ++i)
   {
      std::cout << ints[i];
   }
   std::cout << "\n";
}
while(next_combination(ints.begin(),ints.begin() + k,ints.end()));

【讨论】:

  • 你能提供一些关于你的代码是如何工作的直觉吗?我试图遵循这个逻辑,但老实说,我没有任何理由相信它有效。
  • @templatetypedef:如果您在第一个示例中打印出整个字符串,您可能会看到算法是如何运行的。该字符串在任何时候都包含两个已排序的一半。
  • 可能值得注意的是,当 k 等于 0 时 do...while 是不正确的,因为此时不应生成任何组合。
  • 上面的代码由于未使用的赋值和itr1的修改存在某种错误:itr1 = last; --itr1; itr1 = k;
【解决方案2】:

http://howardhinnant.github.io/combinations.html

搜索“for_each_combination”。如果你找到更快的东西,请告诉我。与我经常看到的其他算法不同,这个算法不需要元素类型为 LessThanComparable。

【讨论】:

  • 像往常一样,您的图书馆很棒。泰。人们不会介意在循环中添加 std::execution::par [wink wink]
【解决方案3】:

创建一个具有 n - k 个零后跟 k 个 1 的辅助向量。零表示不包含原始容器中的元素,而一表示包含该元素。

现在在辅助向量上使用 std::next_permutation 得到下一个组合。

【讨论】:

    【解决方案4】:

    这是一个可以完成工作的伪代码的惰性示例......

    void nChooseK(array[n],k){
        recurse("",array[n],k);      
    }
    
    void recurse(initialString,array[n],k){
        if(k == 0){
            print initialString;
            return;
         }
        for(i=0;i<n;i++){
            tmpArray = array[0...i-1]+array[i+1...];//the array without the object to remove
            recurse(initialString + array[i], tmpArray,k-1)
        }        
    }
    

    【讨论】:

    • 从技术上讲,您正在生成每个子集 k!次。如果tmpArray 被创建为array[i+1,...],则每个可能的集合只生成一次。
    • 巴勃罗好主意,这是真的。
    【解决方案5】:

    您可以使用std::next_permutation,但它是 n!而不是n选择k。您可以在创建它们后过滤它们。但是这个解决方案是 O(n!),并不是很完美。以下是试错解决方案:

    int factorial(int value)
    {
        int result = 1;
    
        for(int i = 1; i <= value; i++)
        {
            result *= i;
        }
    
        return result;
    }
    
    std::set<std::set<int>> binomial_coefficient(std::vector<int> input, int k)
    {
        std::set<std::set<int>> solutions;
    
        for(unsigned int i = 0; i < factorial(input.size()); i++)
        {
            std::next_permutation(input.begin(), input.end());
    
            solutions.insert(std::set<int>(input.begin(), input.begin() + k));
        }
    
        return solutions;
    }
    

    【讨论】:

    • 将算法减速n! 比“不是很完美”还要糟糕。对于n 的任何合理值,这意味着它不会在宇宙死亡之前完成。虽然在这种情况下,factorial 函数只会溢出int 的范围并产生垃圾。
    猜你喜欢
    • 1970-01-01
    • 2010-09-12
    • 2013-01-15
    • 1970-01-01
    • 1970-01-01
    • 2021-12-06
    相关资源
    最近更新 更多