问题 列出1 ... n之间k个整数的所有可能组合(n选择k)


出于没有特别的原因,我决定寻找一种算法,该算法产生1 ... n之间k个整数的所有可能选择,其中k整数之间的顺序无关紧要(n选择k thingy)。

从完全相同的原因,这完全没有理由,我也用C#实现了它。我的问题是:

你在我的算法或代码中看到任何错误吗?而且,更重要的是, 你能建议一个更好的算法吗?


6076
2018-02-14 03:05


起源

重复 stackoverflow.com/questions/127704/... - ShreevatsaR


答案:


在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;
}

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

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()));

9
2017-10-25 20:18





阿萨夫,

您要求我们评估您的算法,但您不解释您的算法 - 即使在代码注释中也是如此。所以你希望每个人花一个小时或更长时间从代码中反向设计算法,这样我们才能在回答之前理解你的问题?

请编辑您的问题以解释您的算法。

有一件事是显而易见的 - 你的代码的内存占用是可怕的。对于n的适度值,组合的数量很容易达到数十亿,这将需要比大多数计算机更多的内存。此外,您正在使用动态增长的阵列,这些阵列随着它们的增长不断重新分配和复制。此外,您的程序会在不同的数组中生成子集并合并它们。总而言之,您的程序将需要许多次存储列表所需的内存量,并且它将花费大部分时间来回复制数据。

如果你 必须 一次拥有数组中的所有值,至少从计算所需数组的大小开始 - n! /(n-k)! / k! - 然后填写它。

更好的是“懒惰地”只是根据需要计算序列的每个成员的代码。看到 这个问题来自相关问题边栏


2
2018-02-14 04:40



你是对的 - 足迹和解释算法。我不介意第一个,因为我写这个纯粹的乐趣。我编辑了添加解释。 - Asaf R
你提到我的问题涉及一个稍微不同的问题,但我同意“懒惰”计算将节省空间。谢谢! - Asaf R


这家伙似乎已经使用C#(CodeProject)在组合学方面做了认真的工作:

使用C#泛型的排列,组合和变体


1
2018-03-19 16:24





这是我之前在C中写过的一个相对简单/高效的nCr程序:

main(n,k){float t=0,r=1;for(scanf("%d, %d",&n,&k);t++<k;r*=(1+n-t)/t);printf("%.0f\n",r);}

好的...可读的版本。 =] (不确定这是否与上述相符1:1。)

void nCr(int n, int k) {
    float curK = 0, r = 1;
    while(curK < k) {
        ++curK;
        printf("%.0f\n", r);
        r *= (1 + n - curK) / curK;
    }
}

你可以,而不是打印 yield 或者其他(我不知道C#)进入你的清单。


-1
2018-02-14 04:20



据我了解,此代码打印每个r到k的可能组合(aka nCr)的数量。我没有看到如何改变它打印 一切 组合,没有结束类似于我的算法(k嵌套for循环)。你能详细说明一下吗? - Asaf R