Another solution that provides amortized constant time operations is to use a dynamic array with insertion and removal at the end, just as in Steven's answer, but swap the last element with a random one after insert, instead of before removal as in Steven's method.
That is, instead of doing what Steven suggests (in pseudocode):
public method insert(element):
array.push_to_end(element)
public method remove_random():
swap_last_with_random()
return array.pop_from_end()
private method swap_last_with_random():
i = array.length - 1
j = random(min: 0, max: i)
array.swap_elements_at(i, j)
you can equivalently do:
public method insert(element):
array.push_to_end(element)
swap_last_with_random()
public method remove_random():
return array.pop_from_end()
with swap_last_with_random()
defined as above.
Both of these implementations can be easily shown to work correctly by induction, and they have the same asymptotic time complexity, but their performance in practice may differ.
In particular, with my implementation, multiple consecutive removals require only reading elements sequentially from the end of the array and no writes to the array at all, which might yield a more cache-friendly access pattern. Also, the fact that my method doesn't need to modify the contents of the array during element removal could potentially make implementing efficient concurrent access easier (although it's still not trivial, since removal still changes the length of the array).
All that said, I make no claim that this method is actually any faster in practice than Steven's approach. Depending on your access patterns, it could even be slower. But if performance matters, it's at least worth comparing both of them.
add
andremove_random
do you intend to handle? Interleaved or separated (all adds first, then all removes, in which case one shuffle is enough)? Is this theoretical (want best O()) or practical (want good cache locality etc.)? $\endgroup$