我描述了两种方法,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 很大(甚至是“中等”)但使用更多内存时,这是很好的,但实际上在某些情况下它可能会因为这个原因而变慢。
方法一:
您需要做的第一件事是规范化条目。 (我不清楚您是否假设它们已标准化。)这意味着,将所有条目相加并除以总和。 (这部分可能很慢,所以最好假设或要求它已经发生。)
然后你像这样采样:
从0到n-1的整数范围内随机选择i,j每个矩阵的随机条目。
在[0, 1]范围内选择一个均匀随机实数p。
检查是否matrix[i][j] > p。如果是,则返回 [i][j] 对。如果没有,请返回第 1 步。
为什么会这样?我们以任何特定输出在步骤 3 结束的概率等于选择 [i][j] 的概率(每个条目都相同)乘以数字 p 足够小的概率。这与值matrix[i][j] 成正比,因此采样选择具有正确比例的每个条目。也有可能在第 3 步我们回到起点——这是否有偏见?基本上,没有。原因是,假设我们任意选择一个数字k,然后考虑算法的分布,条件是恰好在k 轮之后停止。条件是我们停止在第k'th 轮,无论我们选择什么值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__ 在 cmets 中指出的那样,在方法 (B) 中,如果矩阵非常大,即使使用类型 double,由于浮点舍入可能会出现一些错误,在这一行:
cumulative += Matrix[i][j];
问题在于,如果 cumulative 比 Matrix[i][j] 大得多,超出了浮点精度可以处理的范围,那么每次执行此语句时,您可能会观察到大量错误,这些错误累积起来会导致严重的不准确性。
正如他所建议的,如果发生这种情况,最直接的解决方法是首先对值 Matrix[i][j] 进行排序。为了安全起见,您甚至可以在一般实现中执行此操作 - 渐近地对这些人进行排序不会比您已经拥有的时间更多。