What is the best data structure for this case? Given N resources with ID from 0 to N-1, you can get a resource or free a resource.
We also need to consider the time & space complexity for get
and free
operations.
interface ResourcePool {
int get(); // return an available ID
void free(int id); // mark ID as available
}
Follow up: what if N is a super large number, say 1 billion or 1 trillion.
CodePudding user response:
Generally, you need 2 things:
- A variable like
int nextUnused
that contains the smallest ID that's never been allocated - A list of free IDs less than
nextUnused
.
Allocating an ID will take it from the free list if it's non empty. Otherwise it will increment nextUnused
.
Freeing an ID will just add it to the free list.
There are lots of different representations for the free list, but if you need to reserve memory for allocated resources, then it's common to reuse the memory of the free ones as linked list nodes in the free list, so the free list itself doesn't consume any space. This kind of data structure is called... a "free list": https://en.wikipedia.org/wiki/Free_list
Alternatively, you can store the free list separately. Since IDs can be freed in any order, and you need to remember which ones are free, there is no choice but to store the whole list somehow.
If your ID space is really big, it's conceivable that you could adopt strategies for keeping this representation as small as possible, but I've never seen much effort put into that in practice. The other possibility is to move parts of the free list into disk storage when it gets too big.
CodePudding user response:
If N is very large, you can represent your resource pool using a balanced binary search tree. Each node in the tree is a range of free ids, represented by an upper and lower bound of int
s. get()
removes an arbitrary node from the tree, increments the lower bound, then re-inserts the node if the range it represents is still non-empty. free(i)
inserts a new node (i,i)
, then coalesces that nodes with its two neighbors, if possible. For instance, if the tree contains (7,9)
and (11,17)
, then free(10)
results in a tree with fewer nodes - (7,9)
, (10, 10)
, and (11,17)
are all removed, and (7,17)
is there in their place. On the other hand, if the two neighbors of (10,10)
are (7,9)
and (12,17)
, then the result is (7,10)
and (12,17)
, while if the two neighbors are (7,8)
and (12,17)
, then no coalescing is possible and all three nodes, (7,8)
, (10,10)
, and (12,17)
, remain in the tree.
Both operations, get()
and free()
, take O(log P) time, where P is the size of the number of reserved elements at the moment the operation begins. This is slower than a free list, but the advantage of this over a plain free list is that the size of the structure will be no more than P, so as long as P is much smaller than N, the space usage is low.