This question with an added constraint.
I'm willing to allow not-uniform selection as long as it's not to lop sided.
Given that "sets are typically implemented as binary search trees" and I expect they will contain some kind of depth or size information for balancing, I would expect you could do some sort of weighted random walk of the tree. However I don't know of any remotely portable way to do that.
Edit: The constraint is NOT for the amortized time.
Introduce array with size equal to set. Make array elements hold addresses of every element in set. Generate random integer R
bounded by array/set size, pick address in array's element indexed by R
and dereference it to obtain set's element.
I don't see how to do it with just std::set
, so you probably need a different data structure. Like Victor Sorokin said, you can combine a set with a vector. Instead of set<T>
, use map<T, size_t>
, plus vector< map<T, size_t>::iterator >
. The value for each key is an index into the vector, and each element of the vector points back to the map element. The vector elements have no particular order. When you add an element, put it at the end of the vector. When you remove an element and it's not the last one in the vector, move the last element to the deleted element's position.
IF you know the distribution of the elements in the set, you can randomly select key (with that same distribution) and use std::set::lower_bound
. That's a lot of if though.
int main() {
std::set<float> container;
for(float i=0; i<100; i += .01)
container.insert(i);
//evenish distribution of 10000 floats between 0 and 100.
float key = std::rand() *10000f / RAND_MAX; //not random, sue me
std::set<float>::iterator iter = container.lower_bound(key); //log(n)
std::cout << *iter;
return 0;
}
You may be able to make a randomly-ordered copy of the map by using this constructor
template <class InputIterator>
set(InputIterator f, InputIterator l,
const key_compare& comp)
..and passing a comparator that compares hashes of the keys (or some other deterministic spreading function.) Then take the "smallest" keys according to this new map.
You could construct the map once and amortize the cost across several requests for a "random" element.
For std::unordered_set<int> s
:
1) take random R
in min(s)..max(s)
2) if R
in s
: return R
3)
newIter = s.insert(R).first;
newIter++;
if (newIter == s.end()) {
newIter = s.begin();
}
auto result = *newIter;
s.erase(R);
return result;
For ordered set (std::set) probability would depend on distance between elements. unordered_set is randomized by hash.
I hope this can help.
PS converting std::set<V>
into std::set<std::pair<int, V>>
(where first element in pair is a hash of second) makes this method suitable for any hashable V.
来源:https://stackoverflow.com/questions/8302231/how-to-select-a-random-element-in-stdset-in-less-than-on-time