通过谷歌搜索可以找到大量有关将整数n分成m部分的所有可能的分区生成的信息,但我没有找到有关随机生成一个等概率分布的随机分区的任何内容。
通过谷歌搜索可以找到大量有关将整数n分成m部分的所有可能的分区生成的信息,但我没有找到有关随机生成一个等概率分布的随机分区的任何内容。
这篇文章的标题有些误导人,随机整数分割默认是无限制的,意味着它可以具有任意大小的许多部分。提出的具体问题是关于将n分成m个部分的分区,这是一种受限制的整数分区类型。
对于生成无限制整数分区,一个非常快速简单的算法由Fristedt提出,见于他1993年发表的论文The Structure of Random Partitions of Large Integer中。算法如下:
一旦算法停止,那么Z(1)是1的数量,Z(2)是2的数量,以此类推,在随机选择的分区中。接受随机选择的Z集合的概率渐近为1/(94n^3)^(1/4),这意味着人们期望在接受单个样本前运行该算法O(n^(3/4))次。
我花时间解释这个算法的原因是因为它直接适用于将n分成正好m个部分的问题。首先观察到:
将n分成正好m个部分的分区数等于最大部分为m时n的分区数。
然后我们可以直接应用Fristedt算法,但是不必生成Z(1), Z(2), ..., Z(n),而只需要生成Z(1), Z(2), ..., Z(m-1), Z(m)+1 (这里的+1确保最大部分正好为m,并且在Z(m)>=1的条件下,1+Z(m)与Z(m)的分布相等),并将所有其他Z(m+1), Z(m+2), ...都设置为0。然后,在第3步获得目标和后,我们也保证有一个无偏样本。要获得n个部分中的确切m个部分的划分,只需取所生成划分的共轭。
相对于Nijenhuis和Wilf的递归方法,它的优点在于除了存储随机变量Z(1),Z(2)等之外,没有其他存储要求。此外,x的值可以是0到1之间的任何值,这个算法仍然是无偏的!选择一个好的x值,可以使算法更快,尽管第一步的选择几乎对于无限制的整数划分来说是最优的。
如果n非常大,Fristedt算法太慢(且表格方法不可行),则还有其他选项,但它们稍微复杂一些;请参见我的论文 https://sites.google.com/site/stephendesalvo/home/papers 了解概率分治及其应用的更多信息。
这里是一段完成此操作的代码。第一次调用它的时间复杂度为O(n2),但它会构建一个缓存,使得后续调用的时间复杂度为O(n)。
import random
cache = {}
def count_partitions(n, limit):
if n == 0:
return 1
if (n, limit) in cache:
return cache[n, limit]
x = cache[n, limit] = sum(count_partitions(n-k, k) for k in range(1, min(limit, n) + 1))
return x
def random_partition(n):
a = []
limit = n
total = count_partitions(n, limit)
which = random.randrange(total)
while n:
for k in range(1, min(limit, n) + 1):
count = count_partitions(n-k, k)
if which < count:
break
which -= count
a.append(k)
limit = k
n -= k
return a
工作原理: 我们可以在O(n2)的时间复杂度内计算整数n的分区数。作为一个副作用,这会产生一个大小为O(n2)的表格,我们可以使用它来以O(n)的时间生成任何整数k的第k个分区。来自组合算法第52页的另一个算法,"将n
随机分成k
部分"
{1,2,..,n+k-1}
中的一个大小为 k-1
的子集 a
1
, a
2
, .. , a
k-1
(详见下面 1.,2.)r
1
=
a
1
-1
;对于 j=2..k-1
,令 r
j
=
a
j
-
a
j-1
-1
;令 r
k
= n+k-1-
a
k-1
r
j
(j=1..k
) 是 n
的 k
个部分的随机划分[0,1]
之间的随机数来均匀生成随机分区(也称为组合),详见IVAN STOJMENOVIC, “ON RANDOM AND ADAPTIVE PARALLEL GENERATION OF COMBINATORIAL OBJECTS”(第5节、第10节)。
只需要再用C#写一个版本。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication6
{
class Program
{
static Random random = new Random();
static void Main(string[] args)
{
PrintPartition(GetUniformPartition(24, 5));
PrintPartition(GetUniformPartition(24, 5));
PrintPartition(GetUniformPartition(24, 5));
PrintPartition(GetUniformPartition(24, 5));
PrintPartition(GetUniformPartition(24, 5));
Console.ReadKey();
}
static int[] GetUniformPartition(int input, int parts)
{
if(input<= 0 || parts <= 0)
throw new ArgumentException("invalid input or parts");
if (input < MinUniformPartition(parts))
throw new ArgumentException("input is to small");
int[] partition = new int[parts];
int sum = 0;
for (int i = 0; i < parts-1; i++)
{
int max = input - MinUniformPartition(parts - i - 1) - sum;
partition[i] = random.Next(parts - i, max);
sum += partition[i];
}
partition[parts - 1] = input - sum; // last
return partition;
}
// sum of 1,2,3,4,..,n
static int MinUniformPartition(int n)
{
return n * n - 1;
}
static void PrintPartition(int[] p)
{
for (int i = 0; i < p.Length; i++)
{
Console.Write("{0},", p[i]);
}
Console.WriteLine();
}
}
}
这段代码将会产生以下输出:
5,8,7,2,2,
6,6,7,2,3,
5,7,6,2,4,
6,4,3,2,9,
7,8,4,4,1,
我有一个均匀分布的分区生成器。
其中n := 要分割的整数,r:= 切片数量: 该算法是简单地随机插入分割线的朴素方法的修补版本。当我查看其输出时,我发现这种方法的问题在于放置分割线在同一位置的情况不太可能发生。获取{1,1,1}只有一种方法,而获取{2,4,9}有3!种方法,任何{4,2,9}、{2,4,9}、{9,4,2}等都会导致相同的分区位置排序。通过提供额外的显式重复机会来解决了这个问题。对于每个分割线插入,有一定的概率分割线的位置不是随机的,而是被选为以前选择的值的重复。这样就可以平衡朴素方法的不均匀概率分布。
我已经通过穷举证明了r = 3,n = 2时每个分区的概率完全相等。我没有尝试更高的值进行证明,但半心半意的尝试只发现了有希望的迹象。我还对随机输入进行了测试,发现它对我尝试的每个值都至少大致均匀[但可能完全均匀]。
这是C++11的代码:[输出格式与您期望的不同,它是分隔符的位置而不是它们之间空格的大小。不过转换很容易]
#include <vector>
#include <algorithm>
#include <random>
#include <cassert>
template <typename Parting, typename Seed>
vector<Parting> partitionGen(unsigned nparts, unsigned bandw, Seed seed){//nparts is the number of parts, that is, one greater than the number of dividers listed in the output vector. Bandw is the integer being partitioned.
assert(nparts > 0);
vector<Parting> out(nparts-1);
srand(seed);
unsigned genRange = bandw;
for(auto i=out.begin(); i<out.end(); ++i, ++genRange){
unsigned gen = rand()%genRange;
*i = ((gen<bandw)?
gen:
*(i-(gen-bandw+1)));
}
sort(out.begin(), out.end(), less<Parting>());
return out;
}
我不喜欢必须自己排序的事实。如果Vlody的版本有均匀分布,那似乎会更好。
在一些谷歌搜索之后,我在“应用算法手册”中找到了一个针对此问题的算法,其已被谷歌图书索引。该算法出现在第1.12.2节第31页。
我已经实现了上面的解决方案,并发现如果想要计算n的整数分区,它非常有效,但不考虑m。如果处理较大的n,可能需要大幅增加递归限制和调用堆栈。
然而,你不需要第一个函数,因为count_partitions(n, limit)事实上等于' n+limit '中' limit '部分分区的数量。一些数学软件有非常快速的函数来查找将n分成m份的分区数。
最近我衍生出了一种绝对不偏见,非常简单且非常快速(使用记忆化)的方法来解决您的确切问题:在Python中随机生成特定长度的整数分割的算法?
它基于了解具有m个部分的n的字典顺序分区的某些内容,并使用类似于广泛接受的算法(例如Nijenhuis和Wilf 1978)查找n的随机分区的方法,并且在概念上类似于上面的方法。
简而言之,如果有x个由m个部分组成的n的划分,则我们选择一个1到x之间的随机数。该随机数将编码为仅满足n和m的一个划分。希望这可以帮到你。