Time Complexity of HashMap methods - java

Since i'm working around time complexity, i've been searching through the oracle Java class library for the time complexity of some standard methods used on Lists, Maps and Classes. (more specifically, ArrayList, HashSet and HashMap)
Now, when looking at the HashMap javadoc page, they only really speak about the get() and put() methods.
The methods i still need to know are:
remove(Object o)
size()
values()
I think that remove() will be the same complexity as get(), O(1), assuming we don't have a giant HashMap with equal hashCodes, etc etc...
For size() i'd also assume O(1), since a HashSet, which also has no order, has a size() method with complexity O(1).
The one i have no idea of is values() - I'm not sure whether this method will just somehow "copy" the HashMap, giving a time complexity of O(1), or if it will have to iterate over the HashMap, making the complexity equal to the amount of elements stored in the HashMap.
Thanks.

The source is often helpful: http://kickjava.com/src/java/util/HashMap.java.htm
remove: O(1)
size: O(1)
values: O(n) (on traversal through iterator)

The code for remove(as in rt.jar for HashMap) is:
/**
* Removes and returns the entry associated with the specified key
* in the HashMap. Returns null if the HashMap contains no mapping
* for this key.
*/
final Entry<K,V> removeEntryForKey(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
Clearly, the worst case is O(n).

Search: O(1+k/n)
Insert: O(1)
Delete: O(1+k/n)
where k is the no. of collision elements added to the same LinkedList (k elements had same hashCode)
Insertion is O(1) because you add the element right at the head of LinkedList.
Amortized Time complexities are close to O(1) given a good hashFunction. If you are too concerned about lookup time then try resolving the collisions using a BinarySearchTree instead of Default implementation of java i.e LinkedList

Just want to add a comment regarding to the above comment claimed worst case scenario that HashMap may go to O(n) in deletion & search, that will never happen as we are talking about Java HashMap implementation.
for a limited number (below 64 of entries), the hashMap is backed up by array, so with a unfortunate enough case, but still very unlikely, it is linear, but asymptotically speaking, we should say in worse case, HahsMap O(logN)

You can always take a look on the source code and check it yourself.
Anyway... I once checked the source code and what I remember is that there is a variable named size that always hold the number of items in the HashMap so size() is O(1).

On an average the time complexity of a HashMap insertion, deletion, the search takes O(1) constant time.
That said, in the worst case, java takes O(n) time for searching, insertion, and deletion.
Mind you, the time complexity of HashMap apparently depends on the loadfactor n/b (the number of entries present in the hash table BY the total number of buckets in the hashtable) and how efficiently the hash function maps each insert. By efficient I mean, a hash function might map two very different objects to the same bucket (this is called a collision) in case. There are various methods of solving collisions known as collision resolution technique such as
Using a better hashing function
Open addressing
Chaining e.t.c
Java uses chaining and rehashing to handle collisions.
Chaining Drawbacks In the worst case, deletion and searching would take operation O(n). As it might happen all objects are mapped to a particular bucket, which eventually grows to the O(n) chain.
Rehashing Drawbacks Java uses an efficient load factor(n/b) of 0.75 as a rehashing limit (to my knowledge chaining apparently requires lookup operations on average O(1+(n/b)). If n/b < 0.99 with rehashing is used, it is constant time). Rehashing goes off-hand when the table is massive, and in this case, if we use it for real-time applications, response time could be problematic.
In the worst case, then, Java HashMap takes O(n) time to search, insert, and delete.

Related

HashMap Java 8 implementation

As per the following link document: Java HashMap Implementation
I'm confused with the implementation of HashMap (or rather, an enhancement in HashMap). My queries are:
Firstly
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
Why and how are these constants used? I want some clear examples for this.
How they are achieving a performance gain with this?
Secondly
If you see the source code of HashMap in JDK, you will find the following static inner class:
static final class TreeNode<K, V> extends java.util.LinkedHashMap.Entry<K, V> {
HashMap.TreeNode<K, V> parent;
HashMap.TreeNode<K, V> left;
HashMap.TreeNode<K, V> right;
HashMap.TreeNode<K, V> prev;
boolean red;
TreeNode(int arg0, K arg1, V arg2, HashMap.Node<K, V> arg3) {
super(arg0, arg1, arg2, arg3);
}
final HashMap.TreeNode<K, V> root() {
HashMap.TreeNode arg0 = this;
while (true) {
HashMap.TreeNode arg1 = arg0.parent;
if (arg0.parent == null) {
return arg0;
}
arg0 = arg1;
}
}
//...
}
How is it used? I just want an explanation of the algorithm.
HashMap contains a certain number of buckets. It uses hashCode to determine which bucket to put these into. For simplicity's sake imagine it as a modulus.
If our hashcode is 123456 and we have 4 buckets, 123456 % 4 = 0 so the item goes in the first bucket, Bucket 1.
If our hashCode function is good, it should provide an even distribution so that all the buckets will be used somewhat equally. In this case, the bucket uses a linked list to store the values.
But you can't rely on people to implement good hash functions. People will often write poor hash functions which will result in a non-even distribution. It's also possible that we could just get unlucky with our inputs.
The less even this distribution is, the further we're moving from O(1) operations and the closer we're moving towards O(n) operations.
The implementation of HashMap tries to mitigate this by organising some buckets into trees rather than linked lists if the buckets become too large. This is what TREEIFY_THRESHOLD = 8 is for. If a bucket contains more than eight items, it should become a tree.
This tree is a Red-Black tree, presumably chosen because it offers some worst-case guarantees. It is first sorted by hash code. If the hash codes are the same, it uses the compareTo method of Comparable if the objects implement that interface, else the identity hash code.
If entries are removed from the map, the number of entries in the bucket might reduce such that this tree structure is no longer necessary. That's what the UNTREEIFY_THRESHOLD = 6 is for. If the number of elements in a bucket drops below six, we might as well go back to using a linked list.
Finally, there is the MIN_TREEIFY_CAPACITY = 64.
When a hash map grows in size, it automatically resizes itself to have more buckets. If we have a small HashMap, the likelihood of us getting very full buckets is quite high, because we don't have that many different buckets to put stuff into. It's much better to have a bigger HashMap, with more buckets that are less full. This constant basically says not to start making buckets into trees if our HashMap is very small - it should resize to be larger first instead.
To answer your question about the performance gain, these optimisations were added to improve the worst case. You would probably only see a noticeable performance improvement because of these optimisations if your hashCode function was not very good.
It is designed to protect against bad hashCode implementations and also provides basic protection against collision attacks, where a bad actor may attempt to slow down a system by deliberately selecting inputs which occupy the same buckets.
To put it simpler (as much as I could simpler) + some more details.
These properties depend on a lot of internal things that would be very cool to understand - before moving to them directly.
TREEIFY_THRESHOLD -> when a single bucket reaches this (and the total number exceeds MIN_TREEIFY_CAPACITY), it is transformed into a perfectly balanced red/black tree node. Why? Because of search speed. Think about it in a different way:
it would take at most 32 steps to search for an Entry within a bucket/bin with Integer.MAX_VALUE entries.
Some intro for the next topic. Why is the number of bins/buckets always a power of two? At least two reasons: faster than modulo operation and modulo on negative numbers will be negative. And you can't put an Entry into a "negative" bucket:
int arrayIndex = hashCode % buckets; // will be negative
buckets[arrayIndex] = Entry; // obviously will fail
Instead there is a nice trick used instead of modulo:
(n - 1) & hash // n is the number of bins, hash - is the hash function of the key
That is semantically the same as modulo operation. It will keep the lower bits. This has an interesting consequence when you do:
Map<String, String> map = new HashMap<>();
In the case above, the decision of where an entry goes is taken based on the last 4 bits only of you hashcode.
This is where multiplying the buckets comes into play. Under certain conditions (would take a lot of time to explain in exact details), buckets are doubled in size. Why? When buckets are doubled in size, there is one more bit coming into play.
So you have 16 buckets - last 4 bits of the hashcode decide where an entry goes. You double the buckets: 32 buckets - 5 last bits decide where entry will go.
As such this process is called re-hashing. This might get slow. That is (for people who care) as HashMap is "joked" as: fast, fast, fast, slooow. There are other implementations - search pauseless hashmap...
Now UNTREEIFY_THRESHOLD comes into play after re-hashing. At that point, some entries might move from this bins to others (they add one more bit to the (n-1)&hash computation - and as such might move to other buckets) and it might reach this UNTREEIFY_THRESHOLD. At this point it does not pay off to keep the bin as red-black tree node, but as a LinkedList instead, like
entry.next.next....
MIN_TREEIFY_CAPACITY is the minimum number of buckets before a certain bucket is transformed into a Tree.
TreeNode is an alternative way to store the entries that belong to a single bin of the HashMap. In older implementations the entries of a bin were stored in a linked list. In Java 8, if the number of entries in a bin passed a threshold (TREEIFY_THRESHOLD), they are stored in a tree structure instead of the original linked list. This is an optimization.
From the implementation:
/*
* Implementation notes.
*
* This map usually acts as a binned (bucketed) hash table, but
* when bins get too large, they are transformed into bins of
* TreeNodes, each structured similarly to those in
* java.util.TreeMap. Most methods try to use normal bins, but
* relay to TreeNode methods when applicable (simply by checking
* instanceof a node). Bins of TreeNodes may be traversed and
* used like any others, but additionally support faster lookup
* when overpopulated. However, since the vast majority of bins in
* normal use are not overpopulated, checking for existence of
* tree bins may be delayed in the course of table methods.
You would need to visualize it: say there is a Class Key with only hashCode() function overridden to always return same value
public class Key implements Comparable<Key>{
private String name;
public Key (String name){
this.name = name;
}
#Override
public int hashCode(){
return 1;
}
public String keyName(){
return this.name;
}
public int compareTo(Key key){
//returns a +ve or -ve integer
}
}
and then somewhere else, I am inserting 9 entries into a HashMap with all keys being instances of this class. e.g.
Map<Key, String> map = new HashMap<>();
Key key1 = new Key("key1");
map.put(key1, "one");
Key key2 = new Key("key2");
map.put(key2, "two");
Key key3 = new Key("key3");
map.put(key3, "three");
Key key4 = new Key("key4");
map.put(key4, "four");
Key key5 = new Key("key5");
map.put(key5, "five");
Key key6 = new Key("key6");
map.put(key6, "six");
Key key7 = new Key("key7");
map.put(key7, "seven");
Key key8 = new Key("key8");
map.put(key8, "eight");
//Since hascode is same, all entries will land into same bucket, lets call it bucket 1. upto here all entries in bucket 1 will be arranged in LinkedList structure e.g. key1 -> key2-> key3 -> ...so on. but when I insert one more entry
Key key9 = new Key("key9");
map.put(key9, "nine");
threshold value of 8 will be reached and it will rearrange bucket1 entires into Tree (red-black) structure, replacing old linked list. e.g.
key1
/ \
key2 key3
/ \ / \
Tree traversal is faster {O(log n)} than LinkedList {O(n)} and as n grows, the difference becomes more significant.
The change in HashMap implementation was was added with JEP-180. The purpose was to:
Improve the performance of java.util.HashMap under high hash-collision conditions by using balanced trees rather than linked lists to store map entries. Implement the same improvement in the LinkedHashMap class
However pure performance is not the only gain. It will also prevent HashDoS attack, in case a hash map is used to store user input, because the red-black tree that is used to store data in the bucket has worst case insertion complexity in O(log n). The tree is used after a certain criteria is met - see Eugene's answer.
To understand the internal implementation of hashmap, you need to understand the hashing.
Hashing in its simplest form, is a way to assigning a unique code for any variable/object after applying any formula/algorithm on its properties.
A true hash function must follow this rule –
“Hash function should return the same hash code each and every time when the function is applied on same or equal objects. In other words, two equal objects must produce the same hash code consistently.”

Time complexity of TreeMap<> operations: get() and subMap()

Based on this post,
Time complexity of TreeMap operations- subMap, headMap, tailMap
subMap() itself is O(1), and O(n) comes from iterating the sub map.
So, why use get(key) then?
We can use subMap(key, true, key, true) instead,
which is O(1) and iterating this sub map is also O(1).
Faster than get(key), which is O(log(n)). Something wrong here...
We can use subMap(key, true, key, true) instead, which is O(1)
This is correct
and iterating this sub map is also O(1).
O(n) comes from the question. The answer says nothing to imply this, which is good, because it's not true.
Time complexity of iterating a subtree is O(log n + k), where n is the number of elements in the whole map, and k is the number of elements in the sub-map. In other words, it still takes O(log n) to get to the first position when you start iterating. Look up getFirstEntry() implementation to see how it is done.
This brings the overall complexity of your approach to O(log n), but it is bound to be slower than a simple get, because an intermediate object is created and discarded in the process.
The answer is a bit confusing. Technically it's true that creating the submap is constant operation. But that's just because it actually does nothing apart from setting the low and high keys and still shares the tree structure with the original tree.
As a result any operation on the tree is actually postponed until the specific method is invoked. So then get() still goes through the whole original map and only checks whether it didn't cross the low and high boundaries. Simply saying the get() is still O(n) where the n comes from the original map, not from the submap.
The construction of subMap takes O(1) time, however all retrieval operations take the same O(log n) time as in the original map because SubMap just wraps this object and eventually complete a range check and delegate the invocation of get() method to the original source map object.

hashing in Java -- structure & access time

I am looking for verification on two different but related arguments-- those above (A) and below (B) the first line line-comment here in the Q.
(A) The way HashMap is structured is:
a HashMap is a plain table. thats direct memory access (DMA).
The whole idea behind HashMap (or hashing in general) at the first place
is to put into use this constant-time memory access for
a.) accessing records by their own data content (< K,V >),
not by their locations in DMA (the table index)
b.) managing variable number of records-- a number of
records not of a given size, and may/not remain constant
in size throughout the use of this structure.
So, the overall structure in a Java Hash is:
a table: table // i`m using the identifier used in HashMap
each cell of this table is a bucket.
Each bucket is a linked list of type Entry--
i.e., each node of this linked list (not the linked list of Java/API, but the data structure) is of type Entry which in turn is a < K,V > pair.
When a new pair comes in to be added to the hash,
a unique hashCode is calculated for this < K,V > pair.
This hashCode is the key to the index of this < K,V > in table-- it tells
which bucket this < K,V > will go in in the hash.
Note: hashCode is "normalized" thru the function hash() (in HashMap for one)
to better-fit the current length of the table. indexFor() is also at use
to determine which bucket, i.e., cell of table the < K,V > will go in.
When the bucket is determined, the < K,V > is added to the beginning of the linked list in this bucket-- as a result, it is the first < K,V > entry in this bucket and the first entry of the linked-list-that-already-existed is now
the "next" entry that is pointed by this newly added one.
//===============================================================
(B)
From what I see in HashMap, the resizing of the table-- the hash is only done upon a decision based on
hash size and capacity, which are the current and max. # entries in the entire hash.
There is no re-structuring or resizing upon individual bucket sizes-- like "resize() when the max.#entries in a bucket exceeds such&such".
It is not probable, but is possible that a significant number of entries may be bulked up in a bucket while the rest of the hash is pretty much empty.
If this is the case, i.e., no upper limit on the size of each bucket, hash is not of constant but linear access-- theoretically for one thing. It takes $O(n)$ time to get hold of an entry in hash where $n$ is the total number of entries. But then it shouldn't be.
//===============================================================
I don't think I'm missing anything in Part (A) above.
I'm not entirely sure of Part (B). It is a significant issue and I'm looking to find out how accurate this argument is.
I'm looking for verification on both parts.
Thanks in advance.
//===============================================================
EDIT:
Maximum bucket size being fixed, i.e., hash being restructured whenever
the #entries in a bucket hits a maximum would resolve it-- the access time is plain
constant in theory and in use.
This isn't a well structured but quick fix, and would work just fine for sake of constant access.
The hashCodes are likely to be evenly distributed throughout the buckets and it isn`t so likely
that anyone of the buckets will hit the bucket-max before the threshold on the overall size of the hash is hit.
This is the assumption the current setup of HashMap is using as well.
Also based on Peter Lawrey`s discussion below.
Collisions in HashMap are only a problem in pathological cases such as denial of service attacks.
In Java 7, you can change the hashing strategy such that an external party cannot predict your hashing algo.
AFAIK, In Java 8 HashMap for a String key will use a tree map instead of a linked list for collisions. This means O(ln N) worst case instead of O(n) access times.
I'm looking to increase the table size when everything is in the same hash. The hash-to-bucket mapping changes when the size of the table does.
Your idea sounds good. And it is completely true and basically what HashMap does when the table size is smaller than desired / the average amount of elements per bucket gets too large.
It's not doing that by looking at each bucket and checking if there is too much in there because it's easy to calculate that.
The implementation of HashMap.get() in OpenJDK according to this is
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
That shows how HashMap finds elements pretty good but it's written in very confusing ways. After a bit of renaming, commenting and rewriting it could look roughly like this:
public V get(Object key) {
if (key == null)
return getForNullKey();
// get key's hash & try to fix the distribution.
// -> this can modify every 42 that goes in into a 9
// but can't change it once to a 9 once to 8
int hash = hash(key.hashCode());
// calculate bucket index, same hash must result in same index as well
// since table length is fixed at this point.
int bucketIndex = indexFor(hash, table.length);
// we have just found the right bucket. O(1) so far.
// and this is the whole point of hash based lookup:
// instantly knowing the nearly exact position where to find the element.
// next see if key is found in the bucket > get the list in the bucket
LinkedList<Entry> bucketContentList = table[bucketIndex];
// check each element, in worst case O(n) time if everything is in this bucket.
for (Entry entry : bucketContentList) {
if (entry.key.equals(key))
return entry.value;
}
return null;
}
What we see here is that the bucket indeed depends on both the .hashCode() returned from each key object and the current table size. And it will usually change. But only in cases where .hashCode() is different.
If you had an enormous table with 2^32 elements you could simply say bucketIndex = key.hashCode() and it would be as perfect as it can get. There is unfortunately not enough memory to do that so you have to use less buckets and map 2^32 hashes into just a few buckets. That's what indexFor essentially does. Mapping large number space into small one.
That is perfectly fine in the typical case where (almost) no object has the same .hashCode() of any other. But the one thing that you must not do with HashMaps is to add only elements with exactly the same hash.
If every hash is the same, your hash based lookup results in the same bucket and all your HashMap has become is a LinkedList (or whatever data structure holds the elements of a bucket). And now you have the worst case scenario of O(N) access time because you have to iterate over all the N elements.

How does the HashSet maintain the buckets? What data structure is used for this? [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
When an element with a different hashCode is added to a HashSet, a new got to be added right? To what data structure would this new bucket be added? Does it again resort to some sort of array and resizes that each time a new element is added thus making the addition and deletion into the HashSet O(n) complex?
After reading a few posts, I got to know that some implementations of JDKs use HashMap as the backup collection for HashSet but then what that HashMap use for this?
You can always look at the source code.
And there you will see that HashMap has an array of buckets:
transient Entry[] table;
Every bucket is essentially a linked list:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
The array gives you constant-time access to the bucket for a given hash code, and then you have to loop through that list (which hopefully does not have more than one or two entries):
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
When an element with a different hashCode is added to a HashSet, a new got to be added right?
When an element with the same hashCode as an existing one is added, it will go into the same bucket (at the end of a linked list).
When an element with a new hashCode is added, it may or may not go to a different bucket (because you have way more hashCodes than buckets).
All buckets are created in advance when the Map is sized. If the capacity limit is reached, it is resized with more buckets and everything gets put into new buckets.
To what data structure would this new bucket be added?
Buckets are not added. There is a fixed array of buckets. When you need more capacity, the whole structure is rebuilt with a bigger array.
Does it again resort to some sort of array and resizes that each time a new element is added thus making the addition and deletion into the HashSet O(n) complex?
Not each time. Ideally never. Only when you miscalculated the capacity and end up needing more. Then it gets expensive, as all is copied to a new array. This process is essentially the same as with ArrayList.
A lot can be gleaned by even just reading the Javadoc for HashSet and HashMap. A HashSet is backed by a HashMap.
According to the HashMap Javadoc, it's defined by an initial capacity and load factor. The backing hash table won't be resized until the load factor is exceeded, so to answer one of your questions, no, a resize won't happen on every new addition/deletion from the map.
HashMap uses an array of Map.Entry: an element in the array is a pair key,value.
When an element is inserted, the position of the bucket is calculated from the hash code.
If the inserted key is different from the key that is already stored in a bucket (a hash-code collision), then the next empty bucket is chosen. This algorithm has the consequence that operations on a hash maps where the array is "almost full" will be rather expensive: indeed, they will be O(n) if there is only one free bucket.
In order to avoid this problem, HashMap automagically resizes when its current count is greater than some percentage of the internal array capacity (the "load factor", which by default is 75%). This means that a 75-element HashMap will be baked by a 100-element array. Decreasing the load factor will increase the memory overhead, but will bias the average execution order to nearly constant.
Note that worst-case insertion may still be O(n) if every element has the same hashCode.

Worse case time complexity put/get HashMap

What is the worst case time complexity of an Hashmap when the hashcode of it's keys are always equal.
In my understanding: As every key has the same hashcode it will always go to the same bucket and loop through it to check for equals method so for both get and put the time complexity should be O(n), Am I right?
I was looking at this HashMap get/put complexity but it doesn't answer my question.
Also here Wiki Hash Table they state the worse case time complexity for insert is O(1) and for get O(n) why is it so?
Yes, in the worst case your hash map will degenerate into a linked list and you will suffer an O(N) penalty for lookups, as well as inserts and deletions, both of which require a lookup operation (thanks to the comments for pointing out the mistake in my earlier answer).
There are some ways of mitigating the worst-case behavior, such as by using a self-balancing tree instead of a linked list for the bucket overflow - this reduces the worst-case behavior to O(logn) instead of O(n).
In Java 8's HashMap implementation (for when the key type implements Comparable):
Handle Frequent HashMap Collisions with Balanced Trees: In the case of high hash collisions, this will improve worst-case performance from O(n) to O(log n).
From here.
in open hashing, you will have a linked list to store objects which have the same hashcode. so:
for example, you have a hashed table with size 4.
1) assume you want to store an object with hashcode = 0. the object then will be mapped into index (0 mod 4 = ) 0.
2) then you again want to put another object with hashcode = 8. this object will be mapped into index (8 mod 4 = ) 0, as we remember that the index 0 has already filled with our first object, so we have to put the second next to the first.
[0]=>linkedList{object1, object2}
[1]=>null
[2]=>null
[3]=>null
3) what are the steps for searching? 1st, you have to hash the key object and assume that it hashcode is 8, so you will be redirected to index (8 mod 4 = ) 0, then because there is more than one object stored in the same index, we have to search one-by-one all stored objects in the list until you find the matched one or until the end of the list. as the example has 2 objects which stored in the same hashtable index 0, and the searched object lies right in the end of the linkedlist, so you need to walk through all the stored objects. that's why it is O(n) as the worst case.
worst case occured when all the stored object are in the same index in the hashtable. so they will be stored in a linkedlist in which we (may) need to walk through all of them to find our searched object.
hope this help,.
HasMap Complexity
Best. Avg. Worst
Search O(1) O(1) O(n)
Insert O(1) O(1) O(n)
Delete O(1) O(1) O(n)
Hope that will help in short
When inserting, it doesn't matter where in the bucket you put it, so you can just insert it anywhere, thus insertion is O(1).
Lookup is O(n) because you will have to loop through each object and verify that it is the one you were looking for (as you've stated).

Categories