It is said that the complexity of the LinkedList remove and the add operation is of O(1)
. and in case of ArrayList
it is of O(n)
.
In array if we see the implementation from C language point of view then it will give us a clear understanding. While we can add and remove elements at constant time in array i.e we don't need to traverse through the entire array Eg: If the array is [1,2,3,4] the 5 can be added directly at Arr[arr.length] postion , similarly we can remove element at constant time ,position to be deleted is Arr[arr.length-1], but let us see a scenario when the array size you have declared is 4 and we want add one more elements then we can clearly see that there is no space for the elements to be added, therefore what we need to do is to make a new array of size lets say double of previous array i.e size 8 , then all the elements of previous array has to be copied here and the new elements is added at 5th position i.e Arr[arr.length] , therefore the insertion time complexity is O(n) as it is directly proportional to number of elements that previous array has.
Now coming to linked list it doesn't have fixed size(it is dynamically allocated at heap memory) declared we can track the first and last position by head and tails pointer therefore irrespective of linkedlist size we need to just change the pointer of head and tail, making time complexity to O(1) ,for adding at last make a new node, change the link part of current tail to this new node address and make this new node as tail.For inserting at first we need to make new node , set the link part of this node as current head address and atlast make this new node as head thus adding a element at 1st position just by altering the one node therefore time complexity will be O(1).
Remove and the add operations are said to be of O(1) in case of
LinkedList
as, inLinkedList
the shift is not involved, but there is traverse operation involved right?
Adding to either end of a linked list does not require a traversal, as long as you keep a reference to both ends of the list. This is what Java does for its add and addFirst/addLast methods.
Same goes for parameterless remove and removeFirst/removeLast methods - they operate on list ends.
remove(int) and remove(Object) operations, on the other hand, are not O(1). They requires traversal, so you correctly identified their costs as O(n).
The complexity of removing is considered that you already have the pointer to the right position of the element you want to remove...
Is not considered the cost you took for finding it
Information on this topic is now available on Wikipedia at: Search data structure
+----------------------+----------+------------+----------+--------------+
| | Insert | Delete | Search | Space Usage |
+----------------------+----------+------------+----------+--------------+
| Unsorted array | O(1) | O(1) | O(n) | O(n) |
| Value-indexed array | O(1) | O(1) | O(1) | O(n) |
| Sorted array | O(n) | O(n) | O(log n) | O(n) |
| Unsorted linked list | O(1)* | O(1)* | O(n) | O(n) |
| Sorted linked list | O(n)* | O(1)* | O(n) | O(n) |
| Balanced binary tree | O(log n) | O(log n) | O(log n) | O(n) |
| Heap | O(log n) | O(log n)** | O(n) | O(n) |
| Hash table | O(1) | O(1) | O(1) | O(n) |
+----------------------+----------+------------+----------+--------------+
* The cost to add or delete an element into a known location in the list (i.e. if you have an iterator to the location) is O(1). If you don't know the location, then you need to traverse the list to the location of deletion/insertion, which takes O(n) time.
** The deletion cost is O(log n) for the minimum or maximum, O(n) for an arbitrary element.
Yes, you are corrrect if you consider two operations (indexing and inserting) in one go. It is not true in this case because when you are inserting a node in the middle of a linked list, the assumption taken is that you are already at the address where you have to insert the node.
The time complexity of accessing the node is O(n) whereas only inserting a node is O(1).
Insertion at the head requires you to add the element and update the head pointer.
newnode->next = head;
head = newnode;
Insertion at the tail requires you to keep a pointer to the tail element, add the element at the tail and update the tail pointer.
tail->next = newnode;
tail = newnode;
Deleting the head element requires updating the head and deleting the previously head element.
temp = head;
head = head->next;
delete temp; /* or free(temp); */
All the above are trivial operations and don’t depend upon the number of elements in linked list. Hence, they are O(1)
Deleting the tail element would, however, be a O(n) operation because even though you might have a tail pointer, you would still need the penultimate node that would be setup as the new tail node ( by updating the tail pointer and setting the node’s next member to NULL). For this, you need to traverse through the whole linked list.
penultimate_el = find_penultimate_el(head); /* this is O(n) operation */
delete tail; /* or free(tail) */
tail = penultimate_el;
tail->next = NULL;
ArrayList provides resizable-array and stores "references" or "pointers" to actual storage. This array of references has to be recreated if the array is expanded beyond its allocated size. In other words, inserting a node at the beginning would require either all the existing elements to be moved up one, or to re-allocate the whole list if it's beyond it's allocated size. That's why insertion is O(n)
.
A LinkedList consists of a chain of nodes; each node is separated allocated. And so while inserting, it's not necessary to traverse all the nodes. And that's why it has the complexity O(1)
.
However, if you're inserting at end and you have reference of only first node, then you may have to traverse the entire list and thus complexity in this case will be O(n).
EDIT
If you look at the source code of java.util.LinkedList
you can find that LinkedList
always keeps the track of last element
Following are some code snippets from actual java.util.LinkedList
class.
package java.util;
...
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
/**
* Pointer to first node.
*/
transient Node<E> first;
/**
* Pointer to last node.
*/
transient Node<E> last;
...
...
/**
* Appends the specified element to the end of this list.
*/
public boolean add(E e) {
linkLast(e);
return true;
}
...
...
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
...
...
}
See particularly the linkLast()
method. It doesn't traverse the entire list. It just inserts the element at the end of the list and that's why time complexity is O(1)
.