按需算法 return 来自 n 的 k 元素的连续组合
On-demand algorithm to return successive combinations of k elements from n
This post 展示了如何编写一种算法,一次从 n 中吐出 k 个元素的所有组合],避免排列。但是如何编写一种算法,按需给出下一个组合(显然,无需预先计算和存储它们)?它将使用有序的符号集 n 和一个整数 k 进行初始化,然后将被调用以 return 下一个组合.
伪代码或良好的英语叙述就可以满足我的目的 - 除了 Perl 和 C 和一点 Java.
,我不太流利
这里是关于如何做到这一点的散文描述。从您最喜欢的用于生成所有组合的迭代算法开始。然后把每一个循环变量都变成一个状态变量,全部打包成一个class。用k和n构造class的实例,并根据算法初始化每个状态变量。
您可以通过将这些算法转换为 Iterator Pattern 来实现您所描述的大部分算法。这要求您在连续 nextELement()
次调用之间保存算法状态。
如果您的语言支持协程,则您可以更轻松地转换代码。 Python 和 C# 都有一个 yield
关键字,可用于将控制权转移回调用函数,同时保留您正在执行的算法的状态。
原文
(跳至下面的 更新)
- 我们假设
n
元素是整数 1..n
。
- 以递增顺序表示每个
k
-组合(此表示消除了 k
-组合内的排列。)
- 现在考虑 k 组合(
n
个元素)之间的字典顺序。换句话说,{i_1..i_k} < {j_1..j_k}
如果存在索引 t
这样
i_s = j_s
所有 s < t
和
i_t < j_t
.
- 如果
{i_1..i_k}
是 k
组合,定义 next{i_1..i_k}
为下一个元素 w.r.t。字典顺序。
这里是如何计算 next{i_1..i_k}
:
- 找到最大索引
r
使得 i_r + 1 < i_{r+1}
- 如果没有索引满足此条件且
i_k < n
,则设置r := k
- 如果满足以上条件none则没有下一个(且
k
-组合等于{n-k+1, n-k+2,... ,n}
)
- 如果
r
满足第一个条件,则设置next
为{i_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
-组合(此表示消除了 k
-组合内的排列。)
- 现在考虑 k 组合(
n
个元素)之间的字典顺序。换句话说,{i_1..i_k} < {j_1..j_k}
如果存在索引 t
这样
i_s = j_s
所有 s < t
和
i_t < j_t
.
- 如果
{i_1..i_k}
是 k
组合,定义 next{i_1..i_k}
为下一个元素 w.r.t。字典顺序。
- 请注意,按此顺序,最小的
k
组合是 {1..k}
,最大的 {n-k+1, n-k+2,... ,n}
。
这里是如何计算next{i_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
This post 展示了如何编写一种算法,一次从 n 中吐出 k 个元素的所有组合],避免排列。但是如何编写一种算法,按需给出下一个组合(显然,无需预先计算和存储它们)?它将使用有序的符号集 n 和一个整数 k 进行初始化,然后将被调用以 return 下一个组合.
伪代码或良好的英语叙述就可以满足我的目的 - 除了 Perl 和 C 和一点 Java.
,我不太流利这里是关于如何做到这一点的散文描述。从您最喜欢的用于生成所有组合的迭代算法开始。然后把每一个循环变量都变成一个状态变量,全部打包成一个class。用k和n构造class的实例,并根据算法初始化每个状态变量。
您可以通过将这些算法转换为 Iterator Pattern 来实现您所描述的大部分算法。这要求您在连续 nextELement()
次调用之间保存算法状态。
如果您的语言支持协程,则您可以更轻松地转换代码。 Python 和 C# 都有一个 yield
关键字,可用于将控制权转移回调用函数,同时保留您正在执行的算法的状态。
原文
(跳至下面的 更新)
- 我们假设
n
元素是整数1..n
。 - 以递增顺序表示每个
k
-组合(此表示消除了k
-组合内的排列。) - 现在考虑 k 组合(
n
个元素)之间的字典顺序。换句话说,{i_1..i_k} < {j_1..j_k}
如果存在索引t
这样i_s = j_s
所有s < t
和i_t < j_t
.
- 如果
{i_1..i_k}
是k
组合,定义next{i_1..i_k}
为下一个元素 w.r.t。字典顺序。
这里是如何计算 next{i_1..i_k}
:
- 找到最大索引
r
使得i_r + 1 < i_{r+1}
- 如果没有索引满足此条件且
i_k < n
,则设置r := k
- 如果满足以上条件none则没有下一个(且
k
-组合等于{n-k+1, n-k+2,... ,n}
) - 如果
r
满足第一个条件,则设置next
为{i_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
-组合(此表示消除了k
-组合内的排列。) - 现在考虑 k 组合(
n
个元素)之间的字典顺序。换句话说,{i_1..i_k} < {j_1..j_k}
如果存在索引t
这样i_s = j_s
所有s < t
和i_t < j_t
.
- 如果
{i_1..i_k}
是k
组合,定义next{i_1..i_k}
为下一个元素 w.r.t。字典顺序。 - 请注意,按此顺序,最小的
k
组合是{1..k}
,最大的{n-k+1, n-k+2,... ,n}
。
这里是如何计算next{i_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