我有一个正实数值的二维矩阵,存储如下:
vector<vector<double>> matrix;
每个单元格的值可以等于或大于0,该值表示选择单元格的可能性。特别是,例如,与值为1的单元格相比,值等于3的单元格的选择概率是其三倍。
我需要随机选择矩阵的N
个单元格(0 <= N
&lt; =细胞总数),但要根据它们被选择的概率。
我该怎么做?
算法应该尽可能快。
答案 0 :(得分:2)
我描述了两种方法,A和B.
A的时间约为N * number of cells
,并使用空格O(log number of cells)
。 N
很小的时候很好。
B的时间大约为(number of cells + N) * O(log number of cells)
,并使用空格O(number of cells)
。因此,当N
很大(甚至是'中等')但是使用更多内存时会很好,实际上在某些情况下它可能会因为这个原因而变慢。
方法A:
您需要做的第一件事是规范化条目。 (如果你认为它们是否正常化,我不清楚。)这意味着,将所有条目相加并除以总和。 (这部分可能很慢,所以如果你假设或要求已经发生这种情况会更好。)
然后你这样做:
选择矩阵的随机[i,j]
条目(从整数范围i,j
到0
均匀地随机选择n-1
。
在p
范围内选择一个随机实数[0, 1]
。
检查是否matrix[i][j] > p
。如果是,请返回[i][j]
对。如果没有,请返回步骤1.
为什么这样做?我们在步骤3中以任何特定输出结束的概率等于[i][j]
被选中的概率(每个条目都相同),乘以p
数小的概率足够。这与值matrix[i][j]
成比例,因此采样选择具有正确比例的每个条目。也有可能在第3步我们回到开始 - 这是否有偏见?基本上没有。原因是,假设我们任意选择一个数字k
,然后考虑算法的分布,条件是在k
轮后完全停止。假设我们在第k
轮停止,无论我们选择什么值k
,我们采样的分布都必须完全正确。因为如果我们消除p
太小的情况,其他可能性的比例都是正确的。由于分布对于我们可能会考虑的k
的每个值都是完美的,并且整体分布(不以k
为条件)是k
的每个值的分布的平均值,整体分布也很完美。
如果您想以严格的方式分析通常需要的回合数,可以通过分析我们在任何特定回合的第3步实际停止的概率来做到这一点。由于轮次是独立的,因此每轮都是相同的,并且在统计上,这意味着算法的运行时间是泊松分布的。这意味着它紧紧围绕其均值,我们可以通过了解概率来确定均值。
我们在步骤3停止的概率可以通过考虑我们在步骤3停止的条件概率来确定,因为我们选择了任何特定的条目[i][j]
。通过条件期望的公式,你得到了
Pr[ stop at step 3 ] = sum_{i,j} ( 1/(n^2) * Matrix[i,j] )
由于我们假设矩阵已归一化,因此该总和减少到1/n^2
。因此,无论矩阵中的条目是什么,预期轮次数约为n^2
(即,n^2
直到常数因子)。你不能希望做得比我想象的要好得多 - 那就是读取矩阵的所有条目花费的时间相同,并且很难从一个你甚至无法阅读的分布中抽样
注意:我所描述的是一种正确采样单个元素的方法 - 从一个矩阵中获取N
元素,您可以重复N
次。
方法B:
基本上你只想计算一个直方图并从中反向采样,这样你才能知道你得到了正确的分布。计算直方图是很昂贵的,但是一旦你拥有它,获得样本既便宜又容易。
在C ++中,它可能如下所示:
// Make histogram
typedef unsigned int uint;
typedef std::pair<uint, uint> upair;
typedef std::map<double, upair> histogram_type;
histogram_type histogram;
double cumulative = 0.0f;
for (uint i = 0; i < Matrix.size(); ++i) {
for (uint j = 0; j < Matrix[i].size(); ++j) {
cumulative += Matrix[i][j];
histogram[cumulative] = std::make_pair(i,j);
}
}
std::vector<upair> result;
for (uint k = 0; k < N; ++k) {
// Do a sample (this should never repeat... if it does not find a lower bound you could also assert false quite reasonably since it means something is wrong with rand() implementation)
while(1) {
double p = cumulative * rand(); // Or, for best results use std::mt19937 or boost::mt19937 and sample a real in the range [0,1] here.
histogram_type::iterator it = histogram::lower_bound(p);
if (it != histogram.end()) {
result.push_back(it->second);
break;
}
}
}
return result;
这里制作直方图的时间与number of cells * O(log number of cells)
类似,因为插入地图需要时间O(log n)
。您需要一个有序的数据结构,以便以后在重复采样时获得便宜的查找N * O(log number of cells)
。可能你可以选择更专业的数据结构来加快速度,但我认为改进的空间有限。
编辑:正如@Bob__在注释中指出的那样,在方法(B)中,如果矩阵非常大,即使使用类型double
,写入也可能会因浮点舍入而出现一些错误,在这一行:
cumulative += Matrix[i][j];
问题在于,如果cumulative
比浮点精度可以处理的Matrix[i][j]
大得多,那么每次执行此语句时,您可能会发现重大错误,这些错误会累积,从而导致严重的错误
正如他所说,如果发生这种情况,最直接的解决方法是先对值Matrix[i][j]
进行排序。你甚至可以在一般的实现中做到这一点是安全的 - 对这些人进行排序不会比你已经拥有的那样花费更多的时间。