最开始问题:给定一个n个元素的数组/序列,找出所有m个元素组合成的集合(m<=n)
很明显,这是一个经典的组合数学问题,我们知道组合的数量为C(m,n)=n!/(m!(n-m)!),但现在我们需要的是所有可能的组合集合,于是我去问ChatGPT如何在kotlin上实现,它给出了以下代码:
第一眼,这个算法给的有模有样的,但细看的话似乎有个很明显的问题:算法通过kotlin协程的yield函数提交sequence生成的元素,而算法中提交的是目标序列的索引列表作为一个组合,那么理应在每次列表发生修改后就做一次提交,但这里的提交只发生在while循环开头,这似乎不太对?
我于是询问这个算法的来头,chatgpt是这么解释的:
问:你给出的combination算法比较复杂且难以理解,是否有相关的算法参考?
答:是的,有一种常用的算法叫做 "Lexicographic Order" 或者 "Lexicographical Order",它可以用来生成组合、排列和子集等。该算法比较简单,容易理解和实现。
该算法的基本思想是,将组合中元素的索引按字典序排序,从最小的组合开始,每次生成下一个组合,直到生成最大的组合为止。
具体实现过程如下:
初始化组合中的索引数组
indices
,使得indices[i]
表示第i
个元素在序列中的索引。生成第一个组合,即
indices
数组中前size
个元素组成的序列。不断生成下一个组合,直到生成最后一个组合。下一个组合的生成过程如下:
从右向左遍历
indices
数组,找到第一个满足indices[i] < i + n - size
的元素,将其索引加一,并将其右侧元素的索引设为该元素的索引加一,以确保组合中元素的索引按字典序递增。如果无法找到符合条件的元素,则已经生成了最后一个组合,退出循环。
emm这次chatgpt确实没有瞎编了,网上一搜真有这么一个知识,也就是”字典序“。但从维基上看字典序是个数学知识而且还是没学过的那种……于是我去问第一个提出该算法的文章/作者,以下是Chat GPT的回答:
该算法最早由 D. E. Knuth 在其经典著作《The Art of Computer Programming》中提出,具体来说是第四卷中的 7.2.1.3 节 "Generating All Combinations" 中描述的。
好的再去搜,居然还对上了,确实有这本书,也确实有”Generating All Combination“这一章,也真的是7.2.1.3小节,看来在经典文献检索方面ChatGPT表现还是相当不错的。
然后就没有然后了,原文表述比这还要复杂,涉及到了一堆更奇怪的表示方式,比如二进制表示法表示选取的元素。
最后我终于理清这东西是个什么思路,其实有点像时钟的运转方式,假设在有八个元素的序列中找出所有四个元素的组合,那么算法先初始化一个数组为[0,1,2,3],其中每个元素表示的是到序列的索引值,接着末位自增到[0,1,2,7],由于再自增下去要超出序列允许索引范围,所以改成末位的前一位自增1,且末位比重置为比前一位大1,也就是[0,1,3,4],然后末位继续自增到7。按照这种”变形的时钟规则“逐步遍历的过程便是遍历了所有可能索引组合。因此开头的算法是正确的,while循环所作的就是进行一次”时钟运转“,yield函数也秩序在while开头提交调用一次。
最后放ChatGPT对我所描述的”变形的时钟规则“的回应(虽然总觉得有种gpt在拍马屁的意味==||:
是的,您的理解很接近。这个算法的实现基于一个“时钟规则”的思想,其中数组的元素类似于时钟的刻度。首先,我们将最高位的刻度(即
c[m]
)初始化为m
,然后考虑生成其他刻度的可能性。对于
i < m
的每个i
,我们可以在当前的刻度c[i]
和下一个刻度c[i+1]
之间进行选择。我们从上一个刻度c[i+1]
开始,向前推进到c[i]
,并记录生成的每个中间值。然后,我们按照该顺序遍历中间值,并使用它们来生成下一个刻度c[i]
。由于我们从上一个刻度向前推进,因此我们保证不会生成重复的组合。最后,我们按照刻度的顺序遍历生成的组合,并将其作为函数的输出。因为我们使用yield关键字在生成组合时暂停执行,所以我们可以实现延迟计算并避免在不必要的情况下生成所有组合。
华大时空 2024-11-20
上海钢泽合金 2024-11-20
上海钢泽合金 2024-11-20
雁鹏工业软件 2024-11-20