简体   繁体   English

如何找到一个集合的所有分区

[英]How to find all partitions of a set

I have a set of distinct values.我有一组不同的价值观。 I am looking for a way to generate all partitions of this set, ie all possible ways of dividing the set into subsets.我正在寻找一种方法来生成该集合的所有分区,即将集合划分为子集的所有可能方法。

For instance, the set {1, 2, 3} has the following partitions:例如,集合{1, 2, 3}具有以下分区:

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

As these are sets in the mathematical sense, order is irrelevant.由于这些是数学意义上的集合,因此顺序无关紧要。 For instance, {1, 2}, {3} is the same as {3}, {2, 1} and should not be a separate result.例如, {1, 2}, {3}{3}, {2, 1} ,不应是单独的结果。

A thorough definition of set partitions can be found on Wikipedia .可以在Wikipedia上找到集合分区的完整定义。

I've found a straightforward recursive solution.我找到了一个简单的递归解决方案。

First, let's solve a simpler problem: how to find all partitions consisting of exactly two parts.首先,让我们解决一个更简单的问题:如何找到恰好由两部分组成的所有分区。 For an n-element set, we can count an int from 0 to (2^n)-1.对于 n 元素集,我们可以从 0 到 (2^n)-1 计算一个 int。 This creates every n-bit pattern, with each bit corresponding to one input element.这将创建每个 n 位模式,每个位对应一个输入元素。 If the bit is 0, we place the element in the first part;如果位为 0,我们将元素放在第一部分; if it is 1, the element is placed in the second part.如果它是 1,元素被放置在第二部分。 This leaves one problem: For each partition, we'll get a duplicate result where the two parts are swapped.这留下了一个问题:对于每个分区,我们将得到一个重复的结果,其中两个部分被交换。 To remedy this, we'll always place the first element into the first part.为了解决这个问题,我们总是将第一个元素放入第一部分。 We then only distribute the remaining n-1 elements by counting from 0 to (2^(n-1))-1.然后我们只通过从 0 到 (2^(n-1))-1 计数来分配剩余的 n-1 个元素。

Now that we can partition a set into two parts, we can write a recursive function that solves the rest of the problem.现在我们可以将一个集合分成两部分,我们可以编写一个递归函数来解决剩下的问题。 The function starts off with the original set and finds all two-part-partitions.该函数从原始集合开始,并找到所有由两部分组成的分区。 For each of these partitions, it recursively finds all ways to partition the second part into two parts, yielding all three-part partitions.对于这些分区中的每一个,它递归地找到将第二部分分成两部分的所有方法,从而产生所有的三部分分区。 It then divides the last part of each of these partitions to generate all four-part partitions, and so on.然后它划分这些分区中的每一个的最后一部分以生成所有四部分分区,依此类推。

The following is an implementation in C#.以下是 C# 中的实现。 Calling打电话

Partitioning.GetAllPartitions(new[] { 1, 2, 3, 4 })

yields产量

{ {1, 2, 3, 4} },
{ {1, 3, 4}, {2} },
{ {1, 2, 4}, {3} },
{ {1, 4}, {2, 3} },
{ {1, 4}, {2}, {3} },
{ {1, 2, 3}, {4} },
{ {1, 3}, {2, 4} },
{ {1, 3}, {2}, {4} },
{ {1, 2}, {3, 4} },
{ {1, 2}, {3}, {4} },
{ {1}, {2, 3, 4} },
{ {1}, {2, 4}, {3} },
{ {1}, {2, 3}, {4} },
{ {1}, {2}, {3, 4} },
{ {1}, {2}, {3}, {4} }.
using System;
using System.Collections.Generic;
using System.Linq;

namespace PartitionTest {
    public static class Partitioning {
        public static IEnumerable<T[][]> GetAllPartitions<T>(T[] elements) {
            return GetAllPartitions(new T[][]{}, elements);
        }

        private static IEnumerable<T[][]> GetAllPartitions<T>(
            T[][] fixedParts, T[] suffixElements)
        {
            // A trivial partition consists of the fixed parts
            // followed by all suffix elements as one block
            yield return fixedParts.Concat(new[] { suffixElements }).ToArray();

            // Get all two-group-partitions of the suffix elements
            // and sub-divide them recursively
            var suffixPartitions = GetTuplePartitions(suffixElements);
            foreach (Tuple<T[], T[]> suffixPartition in suffixPartitions) {
                var subPartitions = GetAllPartitions(
                    fixedParts.Concat(new[] { suffixPartition.Item1 }).ToArray(),
                    suffixPartition.Item2);
                foreach (var subPartition in subPartitions) {
                    yield return subPartition;
                }
            }
        }

        private static IEnumerable<Tuple<T[], T[]>> GetTuplePartitions<T>(
            T[] elements)
        {
            // No result if less than 2 elements
            if (elements.Length < 2) yield break;

            // Generate all 2-part partitions
            for (int pattern = 1; pattern < 1 << (elements.Length - 1); pattern++) {
                // Create the two result sets and
                // assign the first element to the first set
                List<T>[] resultSets = {
                    new List<T> { elements[0] }, new List<T>() };
                // Distribute the remaining elements
                for (int index = 1; index < elements.Length; index++) {
                    resultSets[(pattern >> (index - 1)) & 1].Add(elements[index]);
                }

                yield return Tuple.Create(
                    resultSets[0].ToArray(), resultSets[1].ToArray());
            }
        }
    }
}

Please refer to the Bell number , here is a brief thought to this problem:请参考Bell number ,这里是对这个问题的简单思考:
consider f(n,m) as partition a set of n element into m non-empty sets.将 f(n,m) 视为将 n 个元素的集合划分为 m 个非空集合。

For example, the partition of a set of 3 elements can be:例如,一组 3 个元素的划分可以是:
1) set size 1: {{1,2,3}, } <-- f(3,1) 1) 设置大小 1: {{1,2,3}, } <-- f(3,1)
2) set size 2: {{1,2},{3}}, {{1,3},{2}}, {{2,3},{1}} <-- f(3,2) 2) 设置大小 2: {{1,2},{3}}, {{1,3},{2}}, {{2,3},{1}} <-- f(3,2)
3) set size 3: {{1}, {2}, {3}} <-- f(3,3) 3) 设置大小 3: {{1}, {2}, {3}} <-- f(3,3)

Now let's calculate f(4,2):现在让我们计算 f(4,2):
there are two ways to make f(4,2):有两种方法可以使 f(4,2):

A. add a set to f(3,1), which will convert from {{1,2,3}, } to {{1,2,3}, {4}} A. 给f(3,1)添加一个集合,它将从{{1,2,3},}转换为{{1,2,3},{4}}
B. add 4 to any of set of f(3,2), which will convert from B. 将 4 添加到任何一组 f(3,2) 中,这将从
{{1,2},{3}}, {{1,3},{2}}, {{2,3},{1}} {{1,2},{3}}, {{1,3},{2}}, {{2,3},{1}}
to
{{1,2,4},{3}}, {{1,2},{3,4}} {{1,2,4},{3}}, {{1,2},{3,4}}
{{1,3,4},{2}}, {{1,3},{2,4}} {{1,3,4},{2}}, {{1,3},{2,4}}
{{2,3,4},{1}}, {{2,3},{1,4}} {{2,3,4},{1}}, {{2,3},{1,4}}

So f(4,2) = f(3,1) + f(3,2)*2所以f(4,2) = f(3,1) + f(3,2)*2
which result in f(n,m) = f(n-1,m-1) + f(n-1,m)*m这导致f(n,m) = f(n-1,m-1) + f(n-1,m)*m

Here is Java code for get all partitions of set:这是用于获取集合的所有分区的 Java 代码:

import java.util.ArrayList;
import java.util.List;

public class SetPartition {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for(int i=1; i<=3; i++) {
            list.add(i);
        }

        int cnt = 0;
        for(int i=1; i<=list.size(); i++) {
            List<List<List<Integer>>> ret = helper(list, i);
            cnt += ret.size();
            System.out.println(ret);
        }
        System.out.println("Number of partitions: " + cnt);
    }

    // partition f(n, m)
    private static List<List<List<Integer>>> helper(List<Integer> ori, int m) {
        List<List<List<Integer>>> ret = new ArrayList<>();
        if(ori.size() < m || m < 1) return ret;

        if(m == 1) {
            List<List<Integer>> partition = new ArrayList<>();
            partition.add(new ArrayList<>(ori));
            ret.add(partition);
            return ret;
        }

        // f(n-1, m)
        List<List<List<Integer>>> prev1 = helper(ori.subList(0, ori.size() - 1), m);
        for(int i=0; i<prev1.size(); i++) {
            for(int j=0; j<prev1.get(i).size(); j++) {
                // Deep copy from prev1.get(i) to l
                List<List<Integer>> l = new ArrayList<>();
                for(List<Integer> inner : prev1.get(i)) {
                    l.add(new ArrayList<>(inner));
                }

                l.get(j).add(ori.get(ori.size()-1));
                ret.add(l);
            }
        }

        List<Integer> set = new ArrayList<>();
        set.add(ori.get(ori.size() - 1));
        // f(n-1, m-1)
        List<List<List<Integer>>> prev2 = helper(ori.subList(0, ori.size() - 1), m - 1);
        for(int i=0; i<prev2.size(); i++) {
            List<List<Integer>> l = new ArrayList<>(prev2.get(i));
            l.add(set);
            ret.add(l);
        }

        return ret;
    }

}

And result is:结果是:
[[[1, 2, 3]]] [[[1, 3], [2]], [[1], [2, 3]], [[1, 2], [3]]] [[[1], [2], [3]]] Number of partitions: 5

Just for fun, here's a shorter purely iterative version:只是为了好玩,这里有一个较短的纯迭代版本:

public static IEnumerable<List<List<T>>> GetAllPartitions<T>(T[] elements) {
    var lists = new List<List<T>>();
    var indexes = new int[elements.Length];
    lists.Add(new List<T>());
    lists[0].AddRange(elements);
    for (;;) {
        yield return lists;
        int i,index;
        for (i=indexes.Length-1;; --i) {
            if (i<=0)
                yield break;
            index = indexes[i];
            lists[index].RemoveAt(lists[index].Count-1);
            if (lists[index].Count>0)
                break;
            lists.RemoveAt(index);
        }
        ++index;
        if (index >= lists.Count)
            lists.Add(new List<T>());
        for (;i<indexes.Length;++i) {
            indexes[i]=index;
            lists[index].Add(elements[i]);
            index=0;
        }
    }

Test here: https://ideone.com/EccB5n在这里测试: https : //ideone.com/EccB5n

And a simpler recursive version:还有一个更简单的递归版本:

public static IEnumerable<List<List<T>>> GetAllPartitions<T>(T[] elements, int maxlen) {
    if (maxlen<=0) {
        yield return new List<List<T>>();
    }
    else {
        T elem = elements[maxlen-1];
        var shorter=GetAllPartitions(elements,maxlen-1);
        foreach (var part in shorter) {
            foreach (var list in part.ToArray()) {
                list.Add(elem);
                yield return part;
                list.RemoveAt(list.Count-1);
            }
            var newlist=new List<T>();
            newlist.Add(elem);
            part.Add(newlist);
            yield return part;
            part.RemoveAt(part.Count-1);
        }
    }

https://ideone.com/Kdir4e https://ideone.com/Kdir4e

Here is a non-recursive solution这是一个非递归解决方案

class Program
{
    static void Main(string[] args)
    {
        var items = new List<Char>() { 'A', 'B', 'C', 'D', 'E' };
        int i = 0;
        foreach (var partition in items.Partitions())
        {
            Console.WriteLine(++i);
            foreach (var group in partition)
            {
                Console.WriteLine(string.Join(",", group));
            }
            Console.WriteLine();
        }
        Console.ReadLine();
    }
}  

public static class Partition
{
    public static IEnumerable<IList<IList<T>>> Partitions<T>(this IList<T> items)
    {
        if (items.Count() == 0)
            yield break;
        var currentPartition = new int[items.Count()];
        do
        {
            var groups = new List<T>[currentPartition.Max() + 1];
            for (int i = 0; i < currentPartition.Length; ++i)
            {
                int groupIndex = currentPartition[i];
                if (groups[groupIndex] == null)
                    groups[groupIndex] = new List<T>();
                groups[groupIndex].Add(items[i]);
            }
            yield return groups;
        } while (NextPartition(currentPartition));
    }

    private static bool NextPartition(int[] currentPartition)
    {
        int index = currentPartition.Length - 1;
        while (index >= 0)
        {
            ++currentPartition[index];
            if (Valid(currentPartition))
                return true;
            currentPartition[index--] = 0;
        }
        return false;
    }

    private static bool Valid(int[] currentPartition)
    {
        var uniqueSymbolsSeen = new HashSet<int>();
        foreach (var item in currentPartition)
        {
            uniqueSymbolsSeen.Add(item);
            if (uniqueSymbolsSeen.Count <= item)
                return false;
        }
        return true;
    }
}

Here is a solution in Ruby that's about 20 lines long:这是一个大约 20 行长的 Ruby 解决方案:

def copy_2d_array(array)
  array.inject([]) {|array_copy, item| array_copy.push(item)}
end

#
# each_partition(n) { |partition| block}
#
# Call the given block for each partition of {1 ... n}
# Each partition is represented as an array of arrays.
# partition[i] is an array indicating the membership of that partition.
#
def each_partition(n)
  if n == 1
    # base case:  There is only one partition of {1}
    yield [[1]]
  else
    # recursively generate the partitions of {1 ... n-1}.
    each_partition(n-1) do |partition|
      # adding {n} to a subset of partition generates
      # a new partition of {1 ... n}
      partition.each_index do |i|
        partition_copy = copy_2d_array(partition)
        partition_copy[i].push(n)
        yield (partition_copy)    
      end # each_index

      # Also adding the set {n} to a partition of {1 ... n}
      # generates a new partition of {1 ... n}
      partition_copy = copy_2d_array(partition)
      partition_copy.push [n]
      yield(partition_copy)
    end # block for recursive call to each_partition
  end # else
end # each_partition

(I'm not trying to shill for Ruby, I just figured that this solution may easier for some readers to understand.) (我不是想为 Ruby 买单,我只是想这个解决方案可能更容易让一些读者理解。)

A trick I used for a set of N members.我用于一组 N 个成员的技巧。 1. Calculate 2^N 2. Write each number between 1 and N in binary. 1. 计算 2^N 2. 用二进制写出 1 和 N 之间的每个数字。 3. You will get 2^N binary numbers each of length N and each number tells you how to split the set into subset A and B. If the k'th digit is 0 then put the k'th element in set A otherwise put it in set B. 3. 你会得到 2^N 个长度为 N 的二进制数,每个数告诉你如何将集合分成子集 A 和 B。如果第 k 位是 0,则将第 k 个元素放入集合 A 中,否则放入它在集合 B 中。

I have implemented Donald Knuth's very nice Algorith H that lists all partitions in Matlab我已经实现了 Donald Knuth 非常好的算法 H,它列出了 Matlab 中的所有分区

https://uk.mathworks.com/matlabcentral/fileexchange/62775-allpartitions--s-- http://www-cs-faculty.stanford.edu/~knuth/fasc3b.ps.gz https://uk.mathworks.com/matlabcentral/fileexchange/62775-allpartitions--s-- http://www-cs-faculty.stanford.edu/~knuth/fasc3b.ps.gz

function [ PI, RGS ] = AllPartitions( S )
    %% check that we have at least two elements
    n = numel(S);
    if n < 2
        error('Set must have two or more elements');
    end    
    %% Donald Knuth's Algorith H
    % restricted growth strings
    RGS = [];
    % H1
    a = zeros(1,n);
    b = ones(1,n-1);
    m = 1;
    while true
        % H2
        RGS(end+1,:) = a;
        while a(n) ~= m            
            % H3
            a(n) = a(n) + 1;
            RGS(end+1,:) = a;
        end
        % H4
        j = n - 1;
        while a(j) == b(j)
           j = j - 1; 
        end
        % H5
        if j == 1
            break;
        else
            a(j) = a(j) + 1;
        end
        % H6
        m = b(j) + (a(j) == b (j));
        j = j + 1;
        while j < n 
            a(j) = 0;
            b(j) = m;
            j = j + 1;
        end
        a(n) = 0;
    elementsd
    %% get partitions from the restricted growth stirngs
    PI = PartitionsFromRGS(S, RGS);
end
def allPossiblePartitions(l): # l is the list whose possible partitions have to be found


    # to get all possible partitions, we consider the binary values from 0 to 2**len(l))//2-1
    """
    {123}       --> 000 (0)
    {12} {3}    --> 001 (1)
    {1} {2} {3} --> 010 (2)
    {1} {23}    --> 011 (3)  --> (0 to (2**3//2)-1)

    iterate over each possible partitions, 
    if there are partitions>=days and
    if that particular partition contains
    more than one element then take max of all elements under that partition
    ex: if the partition is {1} {23} then we take 1+3
    """
    for i in range(0,(2**len(l))//2):
            s = bin(i).replace('0b',"")
            s = '0'*(len(l)-len(s)) + s
            sublist = []
            prev = s[0]
            partitions = []
            k = 0
            for i in s:
                if (i == prev):
                    partitions.append(l[k])
                    k+=1
                else:
                    sublist.append(partitions)
                    partitions = [l[k]]
                    k+=1
                    prev = i
            sublist.append(partitions)
            print(sublist)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM