【问题标题】:generate all partitions of a set [closed]生成一组的所有分区[关闭]
【发布时间】:2015-06-17 13:37:24
【问题描述】:

对于A = {1, 2, 3, ..., n} 形式的一组。它被称为集合A 的划分,一组k<=n 元素遵循以下定理:

a) A 的所有分区的并集是A

b) A 的两个分区的交集是空集(它们不能共享相同的元素)。

例如。 A = {1, 2,... n}我们有分区:

{1, 2, 3}
{1, 2} {3}
{1, 3} {2}
{2, 3} {1}
{1} {2} {3}

这些理论概念在我的算法教科书中都有介绍(顺便说一下,这一小节是“回溯”一章的一部分)。我应该找到一种算法来生成给定集合的所有分区。我整天都在努力解决这个问题,但我找不到解决方案。你能解释一下这个算法是如何工作的吗?另外,能给我一个算法的伪代码草图吗?

【问题讨论】:

  • 提示:假设您拥有 {1, ..., n-1} 的所有分区的所有集合。 (顺便说一句,分区中的各个集合通常称为“部分”。)有没有办法通过以某种方式将元素 n 注入每个分区来生成 {1, ..., n} 的每个分区?这是否会生成 {1, ..., n} 的每个分区一次且仅一次?
  • 是的,到目前为止你是对的。有两件事要记住: 1. 当一个分区包含多个部分时(例如您的示例中的{1} {2}),您需要尝试将新元素添加到每个部分——因此您将创建一个全新的 每个部分的分区。 2.您还需要创建分区,其中新号码是自己的一部分。
  • 我在编辑之前回复了您的评论。您只需要考虑如何从前一个分区生成当前分区;要生成前一个,您只需调用自己(递归)。 (你还需要处理一个基本情况——当集合为空时。)如果你展开执行,这将在 4 之前插入 3。
  • @cristid9:您对将分区重写为数组的观察很重要;它导致了一个简单的迭代解决方案,我将其作为答案提交,以及此类枚举的一般策略。
  • @cristid9:正如我在回答中所说,使这个 O(1) 需要保持最大值而不是重新计算它们。在您的递归解决方案中,您可以通过将最大值作为第五个参数传递来做到这一点。在对printPartitions 的递归调用中,您传递了i > maximum ? i : maximum。通过观察在除最后一个之外的所有递归调用中,您只需传递最大值,而在最后一个调用中传递最大值+1,这可以提高效率。这稍微简化了代码。见ideone.com/6vaXaN

标签: algorithm set combinatorics backtracking


【解决方案1】:

有一个重要的观察(在评论中),一组n 元素的分区可以表示为 [p1形式的整数序列>, … pn] 其中 pi 是元素i 的分区号​​。为了使这样的序列有效,它必须遵守 p1 为 1 的规则,并且对于每个 j 其中1j≤n,有一些ij使得p em>jpi+1.或者换句话说,在序列的任何前缀中,都没有跳过整数。

现在,有一种按字典顺序枚举受约束序列的标准算法,包括以下内容:

  • 从最小的序列开始。
  • 按字典顺序查找下一个序列:
    • 向后扫描序列并找到最右边的“可递增”元素。 (可递增元素是这样一个元素,即有一些更大的元素可以替换该元素,并且直到该点的结果子序列是至少一个有效序列的前缀。)
    • 将该元素更改为下一个可行的较大元素(即产生有效前缀,如上所述),然后用可能的最小值填充其右侧的剩余元素(如果有)。
    • 如果没有可递增元素,则枚举已终止。

有几个关于搜索可递增元素的规定,这个算法最坏的情况是 O(n),当元素被递增通常接近序列的末尾。 (比如用这个算法枚举排列是O(1),只要能在O(1)中找到“下一个元素”就可以找到下一个排列。)

为了将此算法应用于分区情况,我们观察以下内容:

  1. 可能的最小序列是 [1, … 1]
  2. 元素 pi 是可递增的,前提是:
    • pin
    • 有一些 ji 使得 pi 等于; pj
  3. 可行前缀的最小后缀是 [1, … 1]

在观察 2 中陈述条件的另一种方式是,一个元素是可递增的,除非它的值是 n 或者它是序列中具有它的值的第一个元素。如果我们还保持序列 [m1, ... mn,我们可以在 O(1) 中做出决定] 其中 m1 为 0,mimi-1pi 的最大值em>-1m 维护起来很简单,它允许我们将增量条件重写为简单的 pimi.

很容易看出 Next Partition 可以在 O(n) 时间内实现,但碰巧它也是摊销时间 O (1)。粗略地说,这是因为在大多数情况下,序列的最后一个元素是可递增的。

【讨论】:

  • 这个算法会生成“同构”分区吗?例如,它会同时生成 {1, 1, 1, 2} 和​​ {2, 2, 2, 1} (定义相同的分区)吗?
  • @zack:不,不会。有效性规则(这是错误的,但现在已修复)保证每个同构分区组中只生成一个。
  • 这个答案被严重低估了。我希望我能投票更多。
  • 很棒的答案。谢谢!
【解决方案2】:

如果您的集合不大(或者使用堆栈),您可以尝试递归答案:

原理如下,你有一个回馈的功能:

rec_func(SET) = List of List of Set

并按以下方式工作:

rec_func(SET) =
  if SET = {empty}:
    // if no element, easy to give the answer
    return([[]])
  else:
    // 1. Remove one element from the set : 'a' to this set
    a = SET.pop()
    // 2. Call rec_func :
    list_of_list_of_set = rec_func(SET\'a')  
    response = []
    // 3. For every possibilities given by the function add the element 'a' :
    For every list_of_set in list_of_list_of_set  :
       // Case 1, you add 'a' to list_of_set
       response.push( [{'a'} + list_of_set] )
       // Case 2, for every set, you create a copy where you add 'a'
       for every set in list_of_set:
           response.push( [{set,'a'} + list_of_set\set] )

    // The function return the list of list of set created.        
    return(response)

【讨论】:

  • @cristid9 抱歉,操作错误,我正在写答案...
  • @cristid9 如果你需要更具体的实现,请给我一种你熟悉的语言来编写函数。
  • 我对 C 很熟悉,但我想要的只是一般概念。
  • @EmmanuelJay 你可以帮我stackoverflow.com/questions/49452604/…
  • 哪些情况会导致您闯入案件?你为什么要做案例 1 和案例 2?
【解决方案3】:

我正在研究一种有效的算法,该算法根据@rici 定义的关键字方法生成集合的所有分区,如前所述。以下用 python 编写的算法可以做到这一点,并且仍然可以进行优化。我去做。如您所知,这个问题是 NP 完全的!由于优化,可能会有一些奇怪的符号,比如 try/except。但是,n 和 k 变量可以通过 n 来定义集合有多少不同的元素,而 k 是允许集合具有的不同类的数量。信息:算法生成所有分区,最多不同类的数量,而不仅仅是那些类!!!

def partitions():
        global n
        global k
        codeword = [1 for digitIndex in range(0, n)]
        while True:
                print codeword
                startIndex = n - 1
                while startIndex >= 0:
                        maxValue = max(codeword[0 : startIndex])
                        if codeword[startIndex] > maxValue or maxValue > k or codeword[startIndex] >= k:
                                codeword[startIndex] = 1
                                startIndex -= 1
                        else:
                                codeword[startIndex] += 1
                                break

n = 12
k = 2
try:
        partitions()
except:
        pass

【讨论】:

  • 有趣,-1。
猜你喜欢
  • 2022-11-18
  • 2012-04-26
  • 1970-01-01
  • 1970-01-01
  • 2013-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-20
相关资源
最近更新 更多