【问题标题】:Generating the partitions of a number生成数字的分区
【发布时间】:2021-04-07 00:03:27
【问题描述】:

我需要一种算法来生成正数的所有可能分区,我想出了一个(作为答案发布),但它是指数时间。

算法应该返回一个数字可以表示为小于或等于自身的正数之和的所有可能方式。因此,例如对于数字 5,结果将是:

  • 5
  • 4+1
  • 3+2
  • 3+1+1
  • 2+2+1
  • 2+1+1+1
  • 1+1+1+1+1

所以我的问题是:有没有更有效的算法呢?

编辑:问题的标题是“数字的总和分解”,因为我真的不知道这叫什么。 ShreevatsaR pointed out 他们被称为“分区”,所以我相应地编辑了问题标题。

【问题讨论】:

  • 只是好奇:这是一个理论问题(可以)还是有实际用途?
  • 它对我来说确实有实际用途。我需要生成一个数字为 N 的所有分区。每个分区对应一个不同的分布,因此有一个不同的“覆盖”值,我试图将其最大化。
  • 如果您只是在寻找分区的数量而不是特定的公式,那么有一个封闭形式的解决方案。
  • 什么是封闭式解决方案?
  • 我不想添加新答案或编辑我的答案,但请注意,Knuth 在第 7.2.1.4 节(计算机编程艺术的第 4A 卷)。本节的早期草稿可在线获取。 (PDF, PS)

标签: algorithm numbers decomposition


【解决方案1】:

它叫做Partitions。 [另见维基百科:Partition (number theory)。]

分区数量 p(n) 呈指数增长,因此您为生成所有个分区所做的任何事情都必须花费指数级时间。

也就是说,您可以比您的代码做得更好。请参阅this,或其在Python Algorithms and Data StructuresDavid Eppstein 的更新版本。

【讨论】:

  • 哦,谢谢。希望我知道它以前叫什么。 =) 有趣的是他们没有在数论中教授这个。
  • 感谢 David Eppstein 网站的链接,刚刚完成了对他网站的有趣浏览。
  • 他移动了他的博客;最后一个链接现在是here
【解决方案2】:

这是我在 Python 中的解决方案(指数时间):

q = { 1: [[1]] }

def decompose(n):
    try:
        return q[n]
    except:
        pass

    result = [[n]]

    for i in range(1, n):
        a = n-i
        R = decompose(i)
        for r in R:
            if r[0] <= a:
                result.append([a] + r)

    q[n] = result
    return result

 

>>> decompose(5)
[[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1], [2, 1, 1, 1], [1, 1, 1, 1, 1]]

【讨论】:

  • 没有比指数更好的了,因为分区的数量是指数的!
【解决方案3】:

当您要求更高效的算法时,我不知道该比较哪个。但这里有一种以直接方式(Erlang)编写的算法:

-module(partitions).

-export([partitions/1]).

partitions(N) -> partitions(N, N).

partitions(N, Max) when N > 0 ->
    [[X | P]
     || X <- lists:seq(min(N, Max), 1, -1),
        P <- partitions(N - X, X)];
partitions(0, _) -> [[]];
partitions(_, _) -> [].

它在时间上是指数的(与Can Berk Güder's solution in Python 相同)并且在堆栈空间中是线性的。但是使用相同的技巧,memoization,你可以通过节省一些内存和更少的指数来实现很大的改进。 (N=50 时快十倍)

mp(N) ->
    lists:foreach(fun (X) -> put(X, undefined) end,
          lists:seq(1, N)), % clean up process dictionary for sure
    mp(N, N).

mp(N, Max) when N > 0 ->
    case get(N) of
      undefined -> R = mp(N, 1, Max, []), put(N, R), R;
      [[Max | _] | _] = L -> L;
      [[X | _] | _] = L ->
          R = mp(N, X + 1, Max, L), put(N, R), R
    end;
mp(0, _) -> [[]];
mp(_, _) -> [].

mp(_, X, Max, R) when X > Max -> R;
mp(N, X, Max, R) ->
    mp(N, X + 1, Max, prepend(X, mp(N - X, X), R)).

prepend(_, [], R) -> R;
prepend(X, [H | T], R) -> prepend(X, T, [[X | H] | R]).

无论如何,您应该针对您的语言和目的进行基准测试。

【讨论】:

    【解决方案4】:

    这是一种更冗长的方法(这是我在知道“分区”一词之前所做的,这使我能够进行谷歌搜索):

    def magic_chunker (remainder, chunkSet, prevChunkSet, chunkSets):
        if remainder > 0:
            if prevChunkSet and (len(prevChunkSet) > len(chunkSet)): # counting down from previous
                # make a chunk that is one less than relevant one in the prevChunkSet
                position = len(chunkSet)
                chunk = prevChunkSet[position] - 1
                prevChunkSet = [] # clear prevChunkSet, no longer need to reference it
            else: # begins a new countdown; 
                if chunkSet and (remainder > chunkSet[-1]): # no need to do iterations any greater than last chunk in this set
                    chunk = chunkSet[-1]
                else: # i.e. remainder is less than or equal to last chunk in this set
                    chunk = remainder #else use the whole remainder for this chunk
            chunkSet.append(chunk)
            remainder -= chunk
            magic_chunker(remainder, chunkSet, prevChunkSet, chunkSets)
        else: #i.e. remainder==0
            chunkSets.append(list(chunkSet)) #save completed partition
            prevChunkSet = list(chunkSet)
            if chunkSet[-1] > 1: # if the finalchunk was > 1, do further recursion
                remainder = chunkSet.pop() #remove last member, and use it as remainder
                magic_chunker(remainder, chunkSet, prevChunkSet, chunkSets)
            else: # last chunk is 1
                if chunkSet[0]==1: #the partition started with 1, we know we're finished
                    return chunkSets
                else: #i.e. still more chunking to go 
                    # clear back to last chunk greater than 1
                    while chunkSet[-1]==1:
                        remainder += chunkSet.pop()
                    remainder += chunkSet.pop()
                    magic_chunker(remainder, chunkSet, prevChunkSet, chunkSets)
    
    partitions = []
    magic_chunker(10, [], [], partitions)
    print partitions
    
    >> [[10], [9, 1], [8, 2], [8, 1, 1], [7, 3], [7, 2, 1], [7, 1, 1, 1], [6, 4], [6, 3, 1], [6, 2, 2], [6, 2, 1, 1], [6, 1, 1, 1, 1], [5, 5], [5, 4, 1], [5, 3, 2], [5, 3, 1, 1], [5, 2, 2, 1], [5, 2, 1, 1, 1], [5, 1, 1, 1, 1, 1], [4, 4, 2], [4, 4, 1, 1], [4, 3, 3], [4, 3, 2, 1], [4, 3, 1, 1, 1], [4, 2, 2, 2], [4, 2, 2, 1, 1], [4, 2, 1, 1, 1, 1], [4, 1, 1, 1, 1, 1, 1], [3, 3, 3, 1], [3, 3, 2, 2], [3, 3, 2, 1, 1], [3, 3, 1, 1, 1, 1], [3, 2, 2, 2, 1], [3, 2, 2, 1, 1, 1], [3, 2, 1, 1, 1, 1, 1], [3, 1, 1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2], [2, 2, 2, 2, 1, 1], [2, 2, 2, 1, 1, 1, 1], [2, 2, 1, 1, 1, 1, 1, 1], [2, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
    

    【讨论】:

    • 没必要这么自嘲!您是否测试过它是否有效?你怎么称呼这个函数?有例子吗?
    • 感谢@ShreevatsaR,是的,它确实有效,我现在已经把它变成了一个完整的例子
    【解决方案5】:

    Java 实现。可以从记忆中受益。

    public class Partition {
    
        /**
         * partition returns a list of int[] that represent all distinct partitions of n.
         */
        public static List<int[]> partition(int n) {
            List<Integer> partial = new ArrayList<Integer>();
            List<int[]> partitions = new ArrayList<int[]>();
            partition(n, partial, partitions);
            return partitions;
        }
    
        /**
         * If n=0, it copies the partial solution into the list of complete solutions.
         * Else, for all values i less than or equal to n, put i in the partial solution and partition the remainder n-i.
         */
        private static void partition(int n, List<Integer> partial, List<int[]> partitions) {
            //System.out.println("partition " + n + ", partial solution: " + partial);
            if (n == 0) {
                // Complete solution is held in 'partial' --> add it to list of solutions
                partitions.add(toArray(partial));
            } else {
                // Iterate through all numbers i less than n.
                // Avoid duplicate solutions by ensuring that the partial array is always non-increasing
                for (int i=n; i>0; i--) {
                    if (partial.isEmpty() || partial.get(partial.size()-1) >= i) {
                        partial.add(i);
                        partition(n-i, partial, partitions);
                        partial.remove(partial.size()-1);
                    }
                }
            }
        }
    
        /**
         * Helper method: creates a new integer array and copies the contents of the list into the array.
         */
        private static int[] toArray(List<Integer> list) {
            int i = 0;
            int[] arr = new int[list.size()];
            for (int val : list) {
                arr[i++] = val;
            }
            return arr;
        }
    }
    

    【讨论】:

      【解决方案6】:

      这是我在 Haskell 中编写的使用超态的解决方案。

      import Numeric.Natural       (Natural)
      import Control.Monad         (join)
      import Data.List             (nub)
      import Data.Functor.Foldable (ListF (..), para)
      
      partitions :: Natural -> [[Natural]]
      partitions = para algebra
          where algebra Nothing          = []
                algebra (Just (0,_))     = [[1]]
                algebra (Just (_, past)) = (nub . (getAll =<<)) (fmap (1:) past)
      
      getAll :: [Natural] -> [[Natural]]
      getAll = fmap (dropWhile (==0) . sort) . subsets
          where subsets xs = flip sumIndicesAt xs <$> indices xs
      
      indices :: [Natural] -> [[Natural]]
      indices = join . para algebra
          where algebra Nil                 = []
                algebra (Cons x (xs, []))   = [[x:xs]]
                algebra (Cons x (xs, past)) = (:) <$> [x:xs,[]] <*> past
      

      它绝对不是最有效的,但我认为它非常优雅,而且很有启发性。

      【讨论】:

        【解决方案7】:

        这里是这个问题的java代码

        static void printArray(int p[], int n){
                for (int i = 0; i < n; i++)
                    System.out.print(p[i]+" ");
                System.out.println();
        }
        
        // Function to generate all unique partitions of an integer
        static void printAllUniqueParts(int n) {
            int[] p = new int[n]; // An array to store a partition
            int k = 0; // Index of last element in a partition
            p[k] = n; // Initialize first partition as number itself
        
            // This loop first prints current partition, then generates next
            // partition. The loop stops when the current partition has all 1s
            while (true) {
                // print current partition
                printArray(p, k + 1);
        
                // Generate next partition
        
                // Find the rightmost non-one value in p[]. Also, update the
                // rem_val so that we know how much value can be accommodated
                int rem_val = 0;
                while (k >= 0 && p[k] == 1) {
                    rem_val += p[k];
                    k--;
                }
        
                // if k < 0, all the values are 1 so there are no more partitions
                if (k < 0){
                    break;
                }
                // Decrease the p[k] found above and adjust the rem_val
                p[k]--;
                rem_val++;
        
                while (rem_val > p[k]) {
                    p[k + 1] = p[k];
                    rem_val = rem_val - p[k];
                    k++;
                }
                p[k + 1] = rem_val;
                k++;
            }
        }
        
        public static void main(String[] args) {
            System.out.println("All Unique Partitions of 5");
            printAllUniqueParts(5);
        
            System.out.println("All Unique Partitions of 7");
            printAllUniqueParts(7);
        
            System.out.println("All Unique Partitions of 9");
            printAllUniqueParts(8);
        }
        

        【讨论】:

        • 请解释你的答案。
        【解决方案8】:

        另一个 Java 解决方案。它首先创建第一个分区,该分区仅是给定的数字。然后它进入 while 循环,在最后创建的分区中找到大于 1 的最后一个数字。从该数字它将 1 移动到数组中的下一个数字。如果下一个数字最终与找到的数字相同,它会移动到下一个。当最后创建的分区的第一个数字为 1 时循环停止。这是因为所有分区中的数字始终按降序排序。

        以数字 5 为例。首先它创建第一个分区,即数字 5。然后它在最后一个分区中找到大于 1 的最后一个数字。因为我们的最后一个分区是数组 [5, 0, 0, 0, 0]它在索引 0 处找到数字 5。然后从 5 中取一个并将其移动到下一个位置。这就是我们获得分区 [4, 1, 0, 0, 0] 的方式。它再次进入循环。现在它从 4 中取一个并将其向上移动,因此我们得到 [3, 2, 0, 0, 0]。然后同样的事情,我们得到 [3, 1, 1, 0, 0]。在下一次迭代中,我们得到 [2, 2, 1, 0, 0]。现在它从第二个 2 中取出一个,并尝试将其移动到索引 2,我们有 1。它会跳到下一个索引,因为我们也会得到 2,我们会有分区 [2, 1, 2, 0, 0]只是最后一个的重复。相反,我们得到 [2, 1, 1, 1, 0]。在最后一步我们到达 [1, 1, 1, 1, 1] 并且循环存在,因为新分区的第一个数字是 1。

        private static List<int[]> getNumberPartitions(int n) {
            ArrayList<int[]> result = new ArrayList<>();
            int[] initial = new int[n];
            initial[0] = n;
            result.add(initial);
            while (result.get(result.size() - 1)[0] > 1) {
                int[] lastPartition = result.get(result.size() - 1);
                int posOfLastNotOne = 0;
                for(int k = lastPartition.length - 1; k >= 0; k--) {
                    if (lastPartition[k] > 1) {
                        posOfLastNotOne = k;
                        break;
                    }
                }
                int[] newPartition = new int[n];
                for (int j = posOfLastNotOne + 1; j < lastPartition.length; j++) {
                    if (lastPartition[posOfLastNotOne] - 1 > lastPartition[j]) {
                        System.arraycopy(lastPartition, 0, newPartition, 0, lastPartition.length);
                        newPartition[posOfLastNotOne]--;
                        newPartition[j]++;
                        result.add(newPartition);
                        break;
                    }
                }
            }
            return result;
        }
        

        【讨论】:

        • lastPartition.length 是 n,对吧?所有分区数组的大小相同。
        • 在我看来你的逻辑是错误的。对于 n=6,它没有 [2,2,2], [3,3]。
        【解决方案9】:

        这是我的 Rust 实现(灵感来自 Python Algorithms and Data Structures):

        #[derive(Clone)]
        struct PartitionIter {
            pub n: u32,
        
            partition: Vec<u32>,
            last_not_one_index: usize,
            started: bool,
            finished: bool
        }
        
        impl PartitionIter {
            pub fn new(n: u32) -> PartitionIter {
                PartitionIter {
                    n,
                    partition: Vec::with_capacity(n as usize),
                    last_not_one_index: 0,
                    started: false,
                    finished: false,
                }
            }
        }
        
        impl Iterator for PartitionIter {
            type Item = Vec<u32>;
        
            fn next(&mut self) -> Option<Self::Item> {
                if self.finished {
                    return None
                }
        
                if !self.started {
                    self.partition.push(self.n);
                    self.started = true;
                    return Some(self.partition.clone());
                } else if self.n == 1 {
                    return None;
                }
        
                if self.partition[self.last_not_one_index] == 2 {
                    self.partition[self.last_not_one_index] = 1;
                    self.partition.push(1);
                    if self.last_not_one_index == 0 {
                        self.finished = true;
                    } else {
                        self.last_not_one_index -= 1;
                    }
                    return Some(self.partition.clone())
                }
                let replacement = self.partition[self.last_not_one_index] - 1;
                let total_replaced = replacement + (self.partition.len() - self.last_not_one_index) as u32;
                let reps = total_replaced / replacement;
                let rest = total_replaced % replacement;
                self.partition.drain(self.last_not_one_index..);
                self.partition.extend_from_slice(&vec![replacement; reps as usize]);
                if rest > 0 {
                    self.partition.push(rest);
                }
                self.last_not_one_index = self.partition.len() - (self.partition.last().cloned().unwrap() == 1) as usize - 1;
                Some(self.partition.clone())
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2013-09-10
          • 1970-01-01
          • 2012-05-01
          • 2016-06-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多