Java 如何在O(n)时间内,根据其在映射中的整数值相对于其他值随机选择一个键?
如果我们有一个Java 如何在O(n)时间内,根据其在映射中的整数值相对于其他值随机选择一个键?,java,random,map,uniform,Java,Random,Map,Uniform,如果我们有一个映射,假设整数值表示“有多少个”T。因此,我想根据整数值统一选择一个T。如果映射包含“a”=4和“b”=6的字符串,那么我希望它在40%的时间“a”被选中,在60%的时间“b”被选中 最重要的是,我希望在O(n)中这样,在我前面的示例中,n是两个(而不是十个)。我最初制作了一个ArrayList,其中包含键的数量(并简单地返回任意随机索引),但这个过程不仅非常缓慢,而且对于映射所表示的内容来说完全违反直觉。要做到这一点,您必须缓存每个值T的相对频率。这将为您提供O(n)O(n)插入
映射
,假设整数值表示“有多少个”T。因此,我想根据整数值统一选择一个T。如果映射包含“a”=4和“b”=6的字符串,那么我希望它在40%的时间“a”被选中,在60%的时间“b”被选中
最重要的是,我希望在O(n)中这样,在我前面的示例中,n是两个(而不是十个)。我最初制作了一个ArrayList,其中包含键的数量(并简单地返回任意随机索引),但这个过程不仅非常缓慢,而且对于
映射所表示的内容来说完全违反直觉。要做到这一点,您必须缓存每个值T的相对频率。这将为您提供O(n)O(n)插入成本价格的概率分布(每次插入时,必须更新每个T的相对频率)。如果可以存储总金额,这很容易做到:
只需将对(T,int)存储为类或普通数组中的任何内容,然后进行检查:
int val = Random.nextInt(total);
for (Pair p : pairs) {
val -= p.val;
if (val < 0) return p;
}
int val=Random.nextInt(总计);
用于(p对:对){
val-=p.val;
如果(val<0)返回p;
}
考虑到循环遍历ArrayList是遍历n个值的最有效的方法,并且显然不可能比O(n)做得更好,所以不能更快。唯一的开销是nextInt(),在每个解决方案中都需要它(或类似的东西)。
根据您组织ArrayList的方式(排序与否),其他操作会变得更便宜/更昂贵,但这对于特定操作并不重要
编辑:尽管考虑到这一点,“你显然需要O(n)”不是真的。如果您很少更改数组中的值,并且允许进行昂贵的准备,并且内存不是问题,那么您可以通过存储HashMap来做得更好。
例如,如果您有一个发行版:
T0:2
T1:3
T2:1
您可以在hashmap中插入(0,T0),(1,T0),(2,T1),(4,T1),(5,T2)
Edit2:或者看看phooji的方法,该方法对于更大的数据集应该是可行的。使用arraylist实际上比使用Map更快,因为您可以在O(1)中完成
class-RandVal{
列表=新的ArrayList();
Random rand=新的Random();
公共T值(){
int next=rand.nextInt(list.size());
返回列表。获取(下一步);
}
}
这是一件坏事的唯一方式是,如果顺序很重要(a a B B a B B B a B B B a B B a或其他),但显然不是这样,因为您使用的是一个没有顺序的映射…很抱歉延迟,但我认为我有一个相对优雅的解决方案,O(n lg n)
构造时间和O(lg n)
fetch-a-random-element时间。来吧
加权概率图:
此类实现了随机元素生成器。它是基于一个Iterable
构建的;请参见下面的Test.java
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;
class WeightedProbMap<EltType> {
private SortedMap<Integer, EltType> elts = new TreeMap<Integer, EltType>();
private Random rand = new Random();
private int sum = 0;
// assume: each weight is > 0; there is at least one element;
// elements should not be repeated
// ensure: this.elts maps cumulative weights to elements;
// this.sum is the total weight
public WeightedProbMap(Iterable<Pair<Integer, EltType>> weights) {
for (Pair<Integer, EltType> e : weights) {
this.elts.put(this.sum, e.second);
this.sum += e.first;
}
}
// assume: this was initialized properly (cf. constructor req)
// ensure: return an EltType with relative probability proportional
// to its associated weight
public EltType nextElt() {
int index = this.rand.nextInt(this.sum) + 1;
SortedMap<Integer, EltType> view = this.elts.headMap(index);
return view.get(view.lastKey());
}
}
对此进行测试:
取消对Test.java
中的一行或两行elt.add(…)
行的注释
编译时使用:
$javac Pair.java WeightedProbMap.java Test.java
使用运行(例如,在Unix中):
$java测试| grep“Hello”| wc-l
这将为您提供该特定执行的计数
说明:
建造商:
WeightedProbMap
(WPM)类使用一个函数将累积权重映射到元素。图形说明:
The constructor takes weights... ...and creates a mapping from the
3 +---+ number line:
| |
2 +---+ +---+ 2 0 2 5 7
| | | | +------+---------+------+
| | | | | X | Y | Z |
--+---+---+---+-- +------+---------+------+
X Y Z
nextElt()
:
SortedMap
按键顺序存储数据,这使得它能够廉价地提供地图子集的“视图”。特别是这条线
SortedMap<Integer, EltType> view = this.elts.headMap(index)
SortedMap视图=此.elts.headMap(索引)
返回原始映射的视图(this.elts
),其中仅包含严格小于索引的键。此操作()是固定时间:view
需要O(1)
时间来构造,如果以后要更改This.elts
,则更改也会反映在view
中
一旦我们创建了小于随机数的所有内容的视图
,我们现在只需要在该子集中找到最大的键。我们使用SortedMap.lastKey()
来实现这一点,对于TreeMap
,这应该需要\Theta(lg n)
时间 构建一个反向映射,映射
,使每个关键点都是迄今为止处理的所有权重的总和
例如,如果您有此地图:
T1 -> 10
T2 -> 8
T3 -> 3
此反向映射为:
10 -> T1
18 -> T2
21 -> T3
(为了获得更好的性能,您可以先按降序排列权重。)
然后在0和所有权重之和之间生成一个均匀分布的随机数,并在反向映射的键集中对该数字执行二进制搜索。OP here
我想出了一个优雅的解决方案!对于任何误解:我最初的想法是按一个ArrayList中的多少值来存储所有键,而完全不考虑使用映射来存储“使用整数的键实例”;任何类似的解决方案都会适得其反!假设地图是无序的,下面是我的解决方案:
public T randomPick(Random r) {
int randomValue = r.nextInt(size());
int currentSum = 0;
T lastElement = null;
for (T t : map.keySet()){
if (randomValue < currentSum + map.get(t)){
return t;
}
currentSum+= map.get(t);
lastElement = t;
}
return lastElement;
}
publictrandompick(随机r){
int randomValue=r.nextInt(size());
int currentSum=0;
T lastElement=null;
for(T:map.keySet()){
if(随机值
它将随机值
与当前和+当前元素的值
进行比较。如果小于该值,则返回当前键。否则,继续并将该值添加到总和中。如果是这样的情况,随机值永远不小于任何值,我们返回t
T1 -> 10
T2 -> 8
T3 -> 3
10 -> T1
18 -> T2
21 -> T3
public T randomPick(Random r) {
int randomValue = r.nextInt(size());
int currentSum = 0;
T lastElement = null;
for (T t : map.keySet()){
if (randomValue < currentSum + map.get(t)){
return t;
}
currentSum+= map.get(t);
lastElement = t;
}
return lastElement;
}