从 n 返回 k 个元素的连续组合的按需算法

Posted

技术标签:

【中文标题】从 n 返回 k 个元素的连续组合的按需算法【英文标题】:On-demand algorithm to return successive combinations of k elements from n 【发布时间】:2015-02-26 03:00:27 【问题描述】:

This post 展示了如何编写一种算法来一次性吐出 nk 元素的所有组合,从而避免排列。但是,如何编写一种算法,按需给出下一个组合(显然,无需预先计算和存储它们)?它将使用有序的符号集 n 和整数 k 进行初始化,然后将被调用以返回下一个组合。

对于我的目的来说,伪代码或良好的英语叙述就可以了 - 除了 Perl 和 C 以及一点 Java,我还不太流利。

【问题讨论】:

另见***.com/questions/4436353/… 【参考方案1】:

原文写法

(跳到下面的更新


    假设n 元素是整数1..n。 按递增顺序表示每个k-combination(此表示消除了k-combination 内的排列。) 现在考虑 k 组合(n 元素)之间的字典顺序。换句话说,i_1..i_k < j_1..j_k 如果存在索引t 这样 i_s = j_s 代表所有 s < ti_t < j_t。 如果i_1..i_kk 组合,则将nexti_1..i_k 定义为w.r.t 的下一个元素。字典顺序。

这是计算nexti_1..i_k的方法:

找到最大的索引r 使得i_r + 1 < i_r+1 如果没有索引满足这个条件和i_k < n,设置r := k 如果以上条件都不满足,则没有下一个(k-组合等于n-k+1, n-k+2,... ,n) 如果r满足第一个条件,则设置nexti_1, ..., i_r-1, i_r + 1, i_r+1, ..., i_k 如果r = k(第二个条件),设置next := i_1, ..., i_k-1, i_k + 1.

更新(非常感谢@rici 改进解决方案)

    假设n 元素是整数1..n。 按递增顺序表示每个k-combination(此表示消除了k-combination 内的排列。) 现在考虑 k 组合(n 元素)之间的字典顺序。换句话说,i_1..i_k < j_1..j_k 如果存在索引t 使得 i_s = j_s 代表所有 s < ti_t < j_t. 如果i_1..i_kk 组合,则将nexti_1..i_k 定义为w.r.t 的下一个元素。字典顺序。 请注意,在这个顺序中,最小的k-组合是1..k,最大的n-k+1, n-k+2,... ,n

这里是如何计算nexti_1..i_k

找到最大的索引r,这样i_r 可以增加1。 增加索引r 处的值,并将以下元素重置为从i_r + 2 开始的连续值。 重复直到没有位置可以增加。

更准确地说:

如果i_k < n,将i_k 增加1(即,将i_k 替换为i_k + 1) 如果i_k = n,找到最大的索引r,使得i_r + 1 < i_r+1。然后将i_r 增加1 并将以下位置重置为i_r + 2, i_r + 3, ..., i_r + k + 1 - r 重复直到到达n-k+1, n-k+2,... ,n

注意算法的递归字符:每次递增最低有效位置时,尾部都会重置为以刚刚递增的值开始的字典最小序列。


Smalltalk 代码

SequenceableCollection >> #nextChoiceFrom: n
    | next k r ar |
    k := self size.
    (self at: 1) = (n - k + 1) ifTrue: [^nil].
    next := self shallowCopy.
    r := (self at: k) = n
      ifTrue: [(1 to: k-1) findLast: [:i | (self at: i) + 1 < (self at: i+1)]]
      ifFalse: [k].
    ar := self at: r.
    r to: k do: [:i | 
      ar := ar + 1.
      next at: i put: ar].
    ^next

【讨论】:

你打败了我,所以我会提供一个简化。前两个项目符号可能是:“找到最大的 r 使得 i_r - r &lt; n - k。”第四个子弹应该是“设置在i_1, ..., i_r-1, i_r + 1, i_r + 2, ..., i_r + k + 1 - r旁边”,第五个子弹只是第四个的特例,所以可以减少到三个子弹。 @rici:我仍在尝试了解 Leandro 的解决方案,但我认为它适用于 任何 组具有排序的字符,尽管他说假设n 元素是整数。我认为,您建议的简化要求它们是整数 1..n,因为您对实际值 (i_r - r) 执行算术运算。我对此是否正确? (编辑:哎呀,没关系,我看到解决方案确实至少依赖于增加元素的能力) 啊!我得到它。实际上,前两个项目符号是在寻找潜在的“进位”并定位最左边的元素,如果i_k 增加,则会受到从右到左的进位“涟漪”影响。找到该元素后,它可以将元素递增到紧邻的左侧,然后将右侧的元素设置为点 #2 要求的逐渐更高的值(“以递增顺序表示每个 k 组合......”)。所以我确实认为@rici 的简化是有道理的。 @Chap:是的,从技术上讲,他的公式只要求您能够比较两个元素并找到一个元素的后继元素。并且可以修改为只需要后继操作。但在实践中,您将在索引向量上执行它,将修改镜像到符号向量上。这是 @chap:你可以这样想:想象一根有 n 个凹口和 k 个珠子的杆。珠子必须停留在槽口中。你从尽可能向左的所有珠子开始。每转一圈,你都会找到最右边的可以向右移动的珠子——通常是最右边的珠子——然后你把它向右移动一个格子,然后把它右边的所有珠子(如果有的话)尽可能向左移动.继续,直到所有的珠子都尽可能靠右。【参考方案2】:

这里是如何做到这一点的散文描述。从您最喜欢的生成所有组合的迭代算法开始。然后把每个循环变量变成一个状态变量,全部打包成一个类。用kn构造一个类的实例,并根据算法初始化每个状态变量。

【讨论】:

【参考方案3】:

您可以通过将它们转换为Iterator Pattern 来实现您所描述的大部分算法。这需要您在连续的nextELement() 调用之间保存算法的状态。

如果您的语言支持协程,您可以更轻松地转换代码。 Python 和 C# 都有一个 yield 关键字,可用于将控制权转移回调用函数,同时保留您正在执行的算法状态。

【讨论】:

以上是关于从 n 返回 k 个元素的连续组合的按需算法的主要内容,如果未能解决你的问题,请参考以下文章

最多具有K个元素的最大连续子数组

20180718

java题目好难啊?求大侠解答,谢谢、、 设计算法求解从集合1...n中选取k(k<=n)个元素的所有组合。

[算法]去掉字符串中连续出现的k个0子串

523. 连续的子数组和

523. 连续的子数组和