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

发布于 2024-07-13 14:18:12 字数 2135 浏览 6 评论 0原文

出于某种特殊原因,我决定寻找一种算法,该算法可以生成 1...n 之间 k 个整数的所有可能选择,其中 k 个整数之间的顺序并不重要(n 选择 k 个东西)。

出于完全相同的原因,这根本不是原因,我也用C#实现了它。 我的问题是:

您发现我的算法或代码有任何错误吗? 而且,更重要的是,你能建议一个更好的算法吗?

请更多地关注算法而不是代码本身。 这不是我写过的最漂亮的代码,尽管请告诉您是否看到错误。

编辑: Alogirthm 解释 -

  • 我们持有 k 指数。
  • 这将创建 k 个嵌套的 for 循环,其中循环 i 的索引是索引[i]。
  • 它模拟 k for 循环,其中索引[i+1] 属于嵌套在索引[i] 循环内的循环。
  • indexs[i] 从indexs[i - 1] + 1 到n - k + i + 1 运行。

代码:

public class AllPossibleCombination
{
    int n, k;
    int[] indices;
    List<int[]> combinations = null;

    public AllPossibleCombination(int n_, int k_)
    {
        if (n_ <= 0)
        {
            throw new ArgumentException("n_ must be in N+");
        }
        if (k_ <= 0)
        {
            throw new ArgumentException("k_ must be in N+");
        }
        if (k_ > n_)
        {
            throw new ArgumentException("k_ can be at most n_");
        }

        n = n_;
        k = k_;
        indices = new int[k];
        indices[0] = 1;
    }

    /// <summary>
    /// Returns all possible k combination of 0..n-1
    /// </summary>
    /// <returns></returns>
    public List<int[]> GetCombinations()
    {
        if (combinations == null)
        {
            combinations = new List<int[]>();
            Iterate(0);
        }
        return combinations;
    }

    private void Iterate(int ii)
    {
        //
        // Initialize
        //
        if (ii > 0)
        {
            indices[ii] = indices[ii - 1] + 1;
        }

        for (; indices[ii] <= (n - k + ii + 1); indices[ii]++)
        {
            if (ii < k - 1)
            {
                Iterate(ii + 1);
            }
            else
            {
                int[] combination = new int[k];
                indices.CopyTo(combination, 0);
                combinations.Add(combination);
            }
        }
    }
}

我对这个长问题表示歉意,它可能适合写一篇博客文章,但我确实希望在这里得到社区的意见。

谢谢,
阿萨夫

Out of no particular reason I decided to look for an algorithm that produces all possible choices of k integers between 1...n, where the order amongst the k integer doesn't matter (the n choose k thingy).

From the exact same reason, which is no reason at all, I also implemented it in C#. My question is:

Do you see any mistake in my algorithm or code? And, more importantly, can you suggest a better algorithm?

Please pay more attention to the algorithm than the code itself. It's not the prettiest code I've ever written, although do tell if you see an error.

EDIT: Alogirthm explained -

  • We hold k indices.
  • This creates k nested for loops, where loop i's index is indices[i].
  • It simulates k for loops where indices[i+1] belongs to a loop nested within the loop of indices[i].
  • indices[i] runs from indices[i - 1] + 1 to n - k + i + 1.

CODE:

public class AllPossibleCombination
{
    int n, k;
    int[] indices;
    List<int[]> combinations = null;

    public AllPossibleCombination(int n_, int k_)
    {
        if (n_ <= 0)
        {
            throw new ArgumentException("n_ must be in N+");
        }
        if (k_ <= 0)
        {
            throw new ArgumentException("k_ must be in N+");
        }
        if (k_ > n_)
        {
            throw new ArgumentException("k_ can be at most n_");
        }

        n = n_;
        k = k_;
        indices = new int[k];
        indices[0] = 1;
    }

    /// <summary>
    /// Returns all possible k combination of 0..n-1
    /// </summary>
    /// <returns></returns>
    public List<int[]> GetCombinations()
    {
        if (combinations == null)
        {
            combinations = new List<int[]>();
            Iterate(0);
        }
        return combinations;
    }

    private void Iterate(int ii)
    {
        //
        // Initialize
        //
        if (ii > 0)
        {
            indices[ii] = indices[ii - 1] + 1;
        }

        for (; indices[ii] <= (n - k + ii + 1); indices[ii]++)
        {
            if (ii < k - 1)
            {
                Iterate(ii + 1);
            }
            else
            {
                int[] combination = new int[k];
                indices.CopyTo(combination, 0);
                combinations.Add(combination);
            }
        }
    }
}

I apologize for the long question, it might be fit for a blog post, but I do want the community's opinion here.

Thanks,
Asaf

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

朮生 2024-07-20 14:18:12

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

In C++ given the following routine:

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

You can then proceed to do the following:

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()));
对你再特殊 2024-07-20 14:18:12

Asaf,

您要求我们评估您的算法,但您没有解释您的算法 - 甚至在代码注释中也没有。 因此,您希望每个人都花一个小时或更长时间从代码中对算法进行逆向工程,以便我们在回答之前能够理解您的问题?

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

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

如果您必须一次拥有数组中的所有值,至少首先要计算所需的数组大小 - n! /(nk)! /k! -- 然后只是填写它。

更好的是代码可以“惰性地”只根据需要计算序列的每个成员。 请参阅相关问题侧栏中的此问题

Asaf,

You are asking us to evaluate your algorithm, but you don't explain your algorithm -- not even in code comments. So you want everyone to spend an hour or more reverse engineering the algorithm from the code, just so we can understand your question before we answer it?

Please edit your question to explain your algorithm.

One thing is obvious -- the memory footprint of your code is horrendous. For even modest values of n, the number of combinatations will easily be in the billions, which will require more memory than most computers have. Plus you are using dynamically grown arrays, which keep reallocating and copying themselves as they grow. Plus your program generates subsets in different arrays and merges them. All in all, your program will require many times the amount of memory that would be ideally needed to store the list, and it will spend most of it's time just copying data back and forth.

If you must have all the values in an array at once, at least start off by computing the size of the array you need -- n! / (n-k)! / k! -- and then just filling it in.

Even better would be code that "lazily" just computed each member of the sequence as it was needed. See this question from the related questions sidebar

近箐 2024-07-20 14:18:12

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

排列、组合、以及使用 C# 泛型的变体

This guy seems to have done serious work in combinatorics using C# (CodeProject) :

Permutations, Combinations, and Variations using C# Generics

鹊巢 2024-07-20 14:18:12

这是我不久前用 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#)写入您的列表。

Here's a relatively simple/efficient nCr program I wrote a while ago in C:

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

Okay ... readable version. =] (Not sure if this is 1:1 corresponding with the above.)

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

Instead of printing, you could yield or whatever (I don't know C#) into your list.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文