我希望解决以下问题。我需要从一个非常大的集合中进行抽样,数量级约为10^20,并提取一个大小约为10%-20%的无重复样本。考虑到集合的大小,我认为像Fisher-Yates这样的算法是不可行的。
我想到了类似随机路径树的算法,可以在O(n log n)内完成,但我想问一下是否已经有类似的实现。
谢谢您的时间!
我想到了类似随机路径树的算法,可以在O(n log n)内完成,但我想问一下是否已经有类似的实现。
谢谢您的时间!
我不知道下面描述的技术在正式的随机性测试中表现如何,但它确实产生了“看起来像随机”的结果。
你可以使用乘法逆元来实现这个过程。思路是使用数学函数将范围在1-N内的每个整数映射到相同范围内的唯一整数。这通常用于生成混淆密钥,但你可以通过更改种子值和提取项的范围来适应它以生成随机子集。
我之前写过一篇博客文章,介绍了如何生成混淆的连续键。以下是代码:
private void DoIt()
{
const long m = 101; // Number of keys + 1
const long x = 387420489; // must be coprime to m
// Compute the multiplicative inverse
var multInv = MultiplicativeInverse(x, m);
// HashSet is used to hold the obfuscated value so we can ensure that no duplicates occur.
var nums = new HashSet<long>();
// Obfuscate each number from 1 to 100.
// Show that the process can be reversed.
// Show that no duplicates are generated.
for (long i = 1; i <= 100; ++i)
{
var obfuscated = i * x % m;
var original = obfuscated * multInv % m;
Console.WriteLine("{0} => {1} => {2}", i, obfuscated, original);
if (!nums.Add(obfuscated))
{
Console.WriteLine("Duplicate");
}
}
}
private long MultiplicativeInverse(long x, long modulus)
{
return ExtendedEuclideanDivision(x, modulus).Item1 % modulus;
}
private static Tuple<long, long> ExtendedEuclideanDivision(long a, long b)
{
if (a < 0)
{
var result = ExtendedEuclideanDivision(-a, b);
return Tuple.Create(-result.Item1, result.Item2);
}
if (b < 0)
{
var result = ExtendedEuclideanDivision(a, -b);
return Tuple.Create(result.Item1, -result.Item2);
}
if (b == 0)
{
return Tuple.Create(1L, 0L);
}
var q = a / b;
var r = a % b;
var rslt = ExtendedEuclideanDivision(b, r);
var s = rslt.Item1;
var t = rslt.Item2;
return Tuple.Create(t, s - q * t);
}
1 => 43 => 1
2 => 86 => 2
3 => 28 => 3
4 => 71 => 4
5 => 13 => 5
6 => 56 => 6
7 => 99 => 7
8 => 41 => 8
9 => 84 => 9
10 => 26 => 10
m
和x
值来反映您的数字范围,这将对您有用。而不是总是从1开始并获取前10或20%,您可以从50%标记开始,然后从那里开始。或者使用一些技术来提取每个第五个数字,或者任何其他东西,只要您的方法不重复访问相同的数字。x
值。