tags:

views:

444

answers:

7

Hi,

I couldn't find an answer but I am pretty sure I am not the first one looking for this. Did anyone know / use / see an STL like container with bidirectional access iterator that has O(1) complexity for Insert/Erase/Lookup ?
Thank you.

+11  A: 

There is no abstract data type with O(1) complexity for Insert, Erase AND Lookup which also provides a bi-directional access iterator.

Edit:

This is true for an arbitrarily large domain. Given a sufficiently small domain you can implement a set with O(1) complexity for Insert, Erase and Lookup and a bidirectional access iterator using an array and a doubly linked list:

std::list::iterator array[MAX_VALUE];
std::list list;

Initialise:

for (int i=0;i<MAX_VALUE;i++)
    array[i] = list.end();

Insert:

if (array[value] != list.end())
    array[value] = list.insert(value);

Erase:

if (array[value] != list.end()) {
    array[value].erase();
    array[value] = list.end();
}

Lookup:

array[value] != list.end()
atomice
Do you mean that you believe is impossible to create one, or that none exist that you know of?
cdiggins
I believe it's impossible to create one that works with a 'large' domain (let's say values up to 2^1000000000, for example).
atomice
for (int i=0;i<MAX_VALUE;i++)is this valid expression ?
grayasm
'<' is HTML for '<' - somehow it crept into the example
Stuart
Fixed that now - thanks.
atomice
The STL std::list used in the example does not invalidate iterators when you insert/erase (except the element you're erasing).
atomice
+1  A: 

Associative arrays (hashtable) have O(1) lookup complexity, while doubly linked lists have O(1) bidi iteration.

Justice
A: 

You won't be able to fit all of your requirements into one container... something's gotta give ;) However, maybe this is interesting for you: http://www.cplusplus.com/reference/stl/

Christian
Do you have some reason to believe this to be true? Is it based on fact, or an opinion?
cdiggins
I believe that it's not possible to fit all requirements into one container because the requirements in total don't really work for each other but (at some point, as atomice pointed out) against each other. Still it's an opinion :)
Christian
+4  A: 

tr1's unordered_set (also available in boost) is probably what you are looking for. You don't specify whether or not you want a sequence container or not, and you don't specify what you are using to give O(1) lookup (ie. vectors have O(1) lookup on index, unordered_set mentioned above has O(1) average case lookup based on the element itself).

Greg Rogers
Yep, cool! http://msdn.microsoft.com/en-us/library/bb982739.aspx
cdiggins
Oh, darn: http://msdn.microsoft.com/en-us/library/bb982483.aspx says that the iterator is "forward only". Strange. Could probably be cheated.
cdiggins
In theory, with open addressing hash table we can achieve O(1) query/insert/delete and it is fairly easy to implement bidirectional iterator. To implement bidirectional iterator on chaining hash table, we need double linked list in each bucket, which is quite wasteful. Maybe one could check google sparse hashtable.
+1  A: 

One trick I've done when messing about storage optimization is to implement a linked list with an add of O(1)[1], then have a caching operation which provides a structure with a faster O(n) lookup[2]. The actual cache takes some O(n) time to build, and I didn't focus on erase. So I 'cheated' a bit and pushed the work into another operation. But if you don't have to do a ton of adds/deletes, it's not a bad way to do it.

[1] Store end pointer and only add onto the end. No traversal required.
[2] I created a dynamic array[3] and searched against it. Since the data wasn't sorted, I couldn't binsearch against it for O(lg n) time. Although I suppose I could have sorted it.
[3]Arrays have better cache performance than lists.

Paul Nathan
If the cache takes O(N) time to build, won't it have to be built for each insertion, thus making Insert O(N)?
cdiggins
You only have to build the cache before you use it not after every insert.
stonemetal
@cdiggins: No. You'll have to rebuild on accessing the new element, but not on insert. Consider how an L1/L2 cache structure: if you make a hit, you don't have to refresh the cache. If you don't hit, maybe it's lower down(triggering a rebuild). If it's not lower down, then fault somehow. My implementation wasn't that smart, but... :-)
Paul Nathan
+1  A: 

Full list of all the complexity gurantees for the STL can be found here:
http://stackoverflow.com/questions/181693/what-are-the-complexity-guarantees-of-the-standard-containers

Summary:


  • Insert: No container gurantees O(1) for generic insert.
    • The only container that has a genric insert gurtantee is: the 'Associative Container'. And this is O(ln(n))
  • There are containers the provide limited insert gurantees

    • Forward sequece gurantee an insert at head of O(1)
    • Back sequence gurantee an insert at tail of O(1)
  • Erase

    • The Associative containers gurantee O(1) for erase (If you have an iterator).
  • Lookup:

    • If you mean element access by lookup (as no container has O(1) find capabilities).
    • Then Random Access container is the only container with O(1) accesses

So the answer is based on container types.
This is what the standard gurantees are defiend for how does this translate to real containers:

std::vector:    Sequence,   Back        Sequence,                   Forward/Reverse/Random Container
std::deque:     Sequence,   Front/Back  Sequence,                   Forward/Reverse/Random Container
std::list:      Sequence,   Front/Back  Seuqence,                   Forward/Reverse Container
std::set:       Sorted/Simple/Unique    Associative Container,      Forward Container
std::map:       Sorted/Pair/Unique      Associative Container,      Forward Container
std::multiset:  Sorted/Simple/Multiple  Associative Container,      Forward Container
std::multimap:  Sorted/Pair/Multiple    Associative Container,      Forward Container
Martin York
But this doesn't answer the question.
cdiggins
@cdiggins: I find thar a curious statement. It breaks up the question and answers each part to show that there is no such container.
Martin York
+1  A: 

In practice, it may be sufficient to use array (vector) and defer costs of inserts and deletes.

Delete element by marking it as deleted, insert element into bin at desired position and remember offset for larger indices.

Inserts and deletes will O(1) plus O(N) cleanup at convenient time; lookup will be O(1) average, O(number of changes since last cleanup) worst case.

ima
@ima This does make sense to me. Have you tried / seen such an attempt ?
grayasm
Used it. Keeping track of changes is somewhat clumsy, but provides required performance - and I don't think one can do much better in general case (atomice answer is one example of a very specific case which allows using array as a map).
ima