10

For a set of the form A = {1, 2, 3, ..., n}. It is called partition of the set A, a set of k<=n elements which respect the following theorems:

a) the union of all the partitions of A is A

b) the intersection of 2 partitions of A is the empty set (they can't share the same elements).

For example. A = {1, 2,... n} We have the partitions:

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

These theoretical concepts are presented in my algorithms textbook (by the way, this subchapter is part of the "Backtracking" chapter). I am supposed to find an algorithm to generate all the partitions of a given set. I was struggling with this problem all the day and I couldn't find a solution. Can you explain me how does this algorithm work? Also, can you give me a pseudo-code sketch of the algorithm?

rici
  • 234,347
  • 28
  • 237
  • 341
cristid9
  • 1,070
  • 1
  • 17
  • 37
  • 3
    Hint: Suppose you have all the set of all partitions of {1, ..., n-1}. (BTW, the individual sets within a partition are usually called "parts".) Is there a way to generate each partition of {1, ..., n} from these by injecting the element n into each partition somehow? Will this generate each partition of {1, ..., n} once and only once? – j_random_hacker Jun 17 '15 at 14:01
  • I think you are right, but I couldn't figure out the precise algorithm. For example, If I want to generate all the partitions of `A_3={1, 2, 3}` then I have to find I way to insert `3` in all the partitions of `A_2={1, 2}`. Then, the partitions of `A_2` are `{1, 2}` and `{1} {2}`. Am I right so far? And for bigger values, `A_4` I have to insert the `4` in all the partitions of `A_3` then insert the `3` in all the partitions of `A_2`? – cristid9 Jun 17 '15 at 14:15
  • 1
    Yes, you're right so far. Two things to remember: 1. When a partition contains more than one part (like `{1} {2}` in your example), you'll need to try adding the new element to each part -- so you will create a whole new *partition* for each *part*. 2. You also need to create partitions in which the new number is in a part by itself. – j_random_hacker Jun 17 '15 at 14:21
  • 1
    I responded to your comment before it was edited. You only ever need to think about how to generate the current partition from the previous one; to generate that previous one, you simply *call yourself* (recurse). (You also need to handle a base case -- when the set is empty.) If you unwind the execution, this will wind up inserting the 3 before the 4. – j_random_hacker Jun 17 '15 at 14:24
  • 1
    It occurs to me that you are trying to do the same thing as in this thread: http://stackoverflow.com/questions/20530128/how-to-find-all-partitions-of-a-set. –  Jun 17 '15 at 14:24
  • 1
    Relevant with respect to running time: [Bell numbers](https://en.wikipedia.org/wiki/Bell_number). You probably don't want to actually generate all of them for any n >= 20, since then you're already at roughly 51 * 10^12 possible partitions. Assuming a single partition fits into a byte, that's already way more than a Petabyte. – G. Bach Jun 17 '15 at 15:19
  • @j_random_hacker I have found I way to think at this problem. Let's say that I save in an array the partition that each element belongs to. For (for A_3 = {1, 2, 3}) example `[1, 1, 2]` means that the first element which is `1` belong to the partition `1`, then the second element, which is `2` belongs to the first partition. Thus the first partition is `{1, 2}` and the third element belongs to the second partition. So we end up with this solution `{1, 2} {3}.` So far everything is ok. to be continued... – cristid9 Jun 17 '15 at 15:34
  • @j_random_hacker Ok, this is how I see the solution in my head https://docs.google.com/drawings/d/1xprV28qJ0uRQGniG-_5gYKfAEq7hJwclZmo219iAUIY/edit?usp=sharing The solution `[1, 1, 3]` is not valid since there can't be partition `3` if there is no partition `2`. Is this the right way to view it? – cristid9 Jun 17 '15 at 15:43
  • 1
    Your choice of representation is fine, but the way you are filling it out makes things complicated. If you do it the way I suggest, then once you have the set of partitions of {1, ..., n-1} (each of which will be represented as an array of n-1 numbers in the range 1 .. n-1, with no "missing" numbers), for each such partition you can generate all the corresponding partitions of {1, ..., n} very easily: Suppose that the largest number that appears in the current array is k. Then make k+1 new arrays, just by appending each of the numbers 1 .. k+1 to the original array. – j_random_hacker Jun 17 '15 at 16:06
  • @j_random_hacker Can you write a short pseudo-code with the solution that you were describing in the previous comment? I don't think I understand it. – cristid9 Jun 17 '15 at 16:21
  • 1
    generate(n): { If n = 1 then return [[1]]. Otherwise let PS = generate(n-1). For each partition P in PS: { Append the list of partitions inject(P) to the list of partitions Q. } Return Q. } inject(X): For each 1 <= i <= max(X) + 1: { Create a new partition Y, equal to X but with i appended at the end. Append Y to the list of partitions Z. } Return Z. } – j_random_hacker Jun 17 '15 at 16:34
  • 2
    @cristid9: Your observation about rewriting the partition as an array is important; it leads to a simple iterative solution, which I submitted as an answer, along with a general strategy for such enumerations. – rici Jun 17 '15 at 17:14
  • I have made a small implementation in C https://ideone.com/yFkSYF. It looks right, what do you think guys? – cristid9 Jun 17 '15 at 19:24
  • 3
    @cristid9: As I said in my answer, making this O(1) requires maintaining the maxima instead of recomputing them. In your recursive solution, you can do that by passing the maximum as a fifth parameter. In the recursive calls to `printPartitions`, you pass `i > maximum ? i : maximum`. That could be made more efficient by observing that in all the recursive calls except the last, you just pass maximum, and in the last one you pass maximum+1. That simplifies the code a bit. See https://ideone.com/6vaXaN – rici Jun 17 '15 at 19:58
  • Could you help me on https://stackoverflow.com/questions/49452604/creating-all-possible-subset-of-k-and-m-combinations-of-n-items-in-c – famedoro Mar 27 '18 at 08:07
  • [Here's my JS ES6 solution](https://gist.github.com/lorenzos/87a4fa6031af47b978a20dce1cd016df) based on this [answer](https://stackoverflow.com/a/20530130/995958). I was searching for this exact problem, all the combinations of sets you can split a set into, I couldn't find anything until I understood was the technical term was: set partition. – lorenzo-s Oct 02 '21 at 23:05

3 Answers3

32

There is an important observation (in a comment) that a partition of a set of n elements can be represented as an integer sequence of the form [p1, … pn] where pi is the partition number of element i. For such a sequence to be valid, it must obey the rules that p1 is 1, and for every j where 1 < jn, there is some i < j such that pjpi&plus;1. Or in other words, in any prefix of the sequence, no integer is skipped.

Now, there is a standard algorithm for enumerating constrained sequences in lexicographical order, which consists of the following:

  • Start with the smallest sequence.
  • To find the next sequence in lexicographical order:
    • Scan backwards over the sequence and find the rightmost "incrementable" element. (An incrementable element is an element such that there is some larger element which could be substituted for that element, and the resulting subsequence up to that point is a prefix of at least one valid sequence.)
    • Change that element to the next larger element which is viable (i.e. which results in a valid prefix, as above), and then fill in the remaining elements to its right (if any) with the smallest possible values.
    • If there is no incrementable element, then the enumeration has terminated.

With several provisions about the search for an incrementable element, this algorithm is at worst O(n) and it is often O(1) when the element to be incremented is usually close to the end of the sequence. (For example, using this algorithm to enumerate permutation is O(1) to find the next permutation as long as you can find the "next element" in O(1).)

In order to apply this algorithm to the partition case, we observe the following:

  1. The smallest possible sequence is [1, … 1]
  2. An element pi is incrementable provided that:
    • pi<n
    • There is some j<i such that pi&equals;pj
  3. The smallest suffix of a viable prefix is [1, … 1]

Another way of stating the condition in observation 2 is that an element is incrementable unless its value is n or it is the first element in the sequence with its value. We can make that determination in O(1) if we also maintain the sequence [m1, … mn] where m1 is 0, and mi is the maximum of mi-1 and pi-1. m is trivial to maintain, and it allows us to rewrite the incrementability condition as simply pimi.

It is easy to see that Next Partition can be implemented in O(n) time, but as it happens it is also the case that it is amortized time O(1). Roughly speaking, that is because in most cases, the last element of the sequence is incrementable.

rici
  • 234,347
  • 28
  • 237
  • 341
  • Will this algorithm generate "isomorphic" partitions? For example, will it generate both {1, 1, 1, 2} and {2, 2, 2, 1} (which define the same partition)? – Zack Jan 12 '16 at 22:51
  • 1
    @zack: No, it won't. The validity rule (which was wrong, but is now fixed) guarantees that only one of each isomorphic group of partitions is generated. – rici Jan 13 '16 at 00:54
  • 7
    This answer is an woefully undervalued gem. I wish I could up-vote it more. – kjo May 01 '16 at 20:27
  • @rici could you help me on https://stackoverflow.com/questions/49452604/creating-all-possible-subset-of-k-and-m-combinations-of-n-items-in-c ? – famedoro Mar 27 '18 at 09:44
  • 1
    I agree this method is much better. Also, the number of set partitions for a set of 16 elements is around $10^10$ - so generating partitions of larger sets is very questionable... – Yauhen Yakimenka Mar 14 '20 at 17:12
3

You can try the recursive answer if your set is not to big (or else use stack) :

The principle is the following, you have a function that give back :

rec_func(SET) = List of List of Set

And work as follow :

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)
Emmanuel Jay
  • 484
  • 3
  • 10
-2

I was working on an efficient algorithm that produces all partitions of a set as described before based on the keyword approach defined by @rici. The following algorithm, written in python, does it and there is still optimization possible. I am on it. As you may know, this problem is NP-complete! Due to optimization there is maybe some strange notation like the try/except. However, the n and k variables are there to define via n, how many different elements the set has and the k is the amount of different classes the sets are allowed to have. INFO: the algorithm generates ALL partitions UP TO the amount of different classes not exclusively those classes!!!

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
Erhard Dinhobl
  • 1,646
  • 18
  • 34