Time Complexity in singly link list - c++

I am studying data-structure: singly link list.
The website says singly linked list has a insertion and deletion time complexity of O(1). Am I missing something?
website link
I do this in C++, and I only have a root pointer. If I want to insert at the end, then I have to travel all the way to the back, which means O(n).

The explanation for this is, that the big O notation in the linked table refers to the function implementation itself, not including the list traversal to find the previous reference node in the list.
If you follow the link to the wikipedia article of the Singly-LinkedList implementation it becomes more clear:
function insertAfter(Node node, Node newNode)
function removeAfter(Node node)
The above function signatures already take the predecessor node as argument (same for the other variants implicitly).
Finding the predecessor is a different operation and may be O(n) or other time complexity.

You missed the interface at two places:
std::list::insert()/std:list::erase() need an iterator to the element where to insert or erase. This means you have no search but only alter two pointers in elements in the list, which is constant complexity.
Inserting at the end of a list can be done via push_back. The standard requires this to be also O(1). Which means, if you have a std::list, it will store first and last element.
EDIT: Sorry, you meet std::forward_list. Point 1 holds also for this even if the names are insert_after and erase_after. Points 2 not, you have to iterate to the end of the list.

I do this in C++, and I only have a root pointer. If I want to insert at the end, then I have to travel all the way to the back, which means O(n).
That's two operations, you first search O(n) the list for given position, then insert O(1) element into the list.
In a single linked list, the operation of insertion consists of:
alternating pointer of previous element
wrapping object into data structure and setting its pointer to next element
Both are invariant to list size.
On the other hand, take for example a heap structure. Insertion of each element requires O(log(n)) operations for it to retain its structure. Tree structures have similar mechanisms that will be run upon insertion and depend on current tree size.

Here it is considered that you already have the node after which you need to add a new element.
In that case for a singly-linked-list insertion time complexity becomes O(1).

The fact is that, unlike an array, we don’t need to shift the elements of a singly-linked list while doing an insertion. Therefore, the insertion time complexity of a singly-linked list is O(1).
Imagine that you have a Python list filled with integer numbers...
my_list = [9, 8, 4, 5, 6]
... and you want to insert the number 3 right after the element 8.
my_list.insert(2, 3)
The printed result will be:
[9, 8, 3, 4, 5, 6]
When you do an insertion to my_list, the other elements after the element 3 are all shifted towards the right, so their indexes are changed. As a result, the time complexity to insert an element at a given index is O(n).
However, in singly-linked lists, there are no array elements, but chained nodes and node values.
Image source: LeetCode
As the above image shows, the prev node holds the reference of the next node. As #πάντα ῥεῖ stated, "function signatures already take the predecessor node as argument". You can find the previous node in O(n) time, but while inserting a new node, you just need to change the addresses of connected nodes and that is O(1) time complexity.

Related

Do not understand how c++ set works

I am using the std::set class for a leetcode question. From Googling I learned that std::set keeps the elements in an ordered manner and I heard that set.begin() returns the smallest element. But I also heard set uses red-black trees and has O(log n) time complexity. I don't understand how these two can go together, as in how does set.begin() return smallest element when a red-black tree doesn't guarantee the smallest element will be the head.
Also set.begin() function makes it seem like this container uses an array instead of a linked list to build the redblack tree, which again I don't understand. How can an array be used instead of a tree?
In the underlying tree, the leftmost node is the smallest, and begin() is the leftmost node.
Iterating traverses the tree's nodes in the appropriate order.
For instance, if the tree is (this is a simpler "regular" binary search tree, but the principle is the same with red-black trees)
4
/ \
2 6
/\ /
1 3 5
then iterating will start at 1, then move up to 2, down again to 3, up two steps to 4, down two steps to 5, and finally up to 6.
(This means that the "steps" when iterating over a tree are not constant-time operations.)
The standard does not impose a particular implementation for any of the containers. A red-black tree is a possible implementation of set. It's also free to choose how to make begin constant complexity.
Assuming the implementation chooses a tree, the most obvious way is by keeping a pointer to the least element, as well as to the root of the tree.
You can implement an ordered set with an array, e.g. boost::flat_set. That doesn't meet all the same complexity requirements as std::set. It does this by only inserting into the sorted position, and only if there isn't an equivalent element already present.

why Run-time for add-before for doubly-linked lists is O(1)?

In data structures, we say pushing an element before a node in singly-linked lists are O(n) operation! since there is no backward pointers, we have to walk all the way through the elements to get to the key we are going to add before the new element. Therefore, it has a linear run time.
Then, when we introduce doubly-linked lists, we say the problem is resolved and now since we have pointers in both directions pushing before becomes a constant time operation O(1).
I understand the logic but still, something is confusing to me! Since we DO NOT have constant time access to the elements of the list, for finding the element we want to add before, we have to walk through the previous element to get there! that is true that in the doubly-linked list it is now faster to implement the add-before command, but still, the action of finding the interested key is O(n)! then why we say with the doubly-linked list the operation of add before becomes O(1)?
Thanks,
In C++, the std::list::insert() function takes an iterator to indicate where the insert should occur. That means the caller already has this iterator, and the insert operation is not doing a search and therefore runs in constant time.
The find() algorithm, however, is linear, and is the normal way to search for a list element. If you need to find+insert, the combination is O(n).
However, there is no requirement to do a search before an insert. For example, if you have a cached (valid) iterator, you can insert in front of (or delete) the element it corresponds with in constant time.

Are these time complexities correct

I wanted to know if my time complexities regarding the following points along with the reasoning are correct or not.
Insertion at the end of a dynamic array is O(1) and anywhere else in
O(n) (since elements might need to copied and moved) (resembling a std::vector)
Searching through a single link list is O(n) since its linear.
Insertion and deletion in a single link list could either be O(1) or
O(n). It is O(1) if the address to the node is available otherwise
its O(n) since a linear search would be required.
I would appreciate feedback on this
Insertion at the end of a dynamic array is O(1) and anywhere else in O(n) (since elements might >need to copied and moved) (resembling a std::vector)
Amortized time complexity of a dynamic array is O(1).
https://stackoverflow.com/a/249695/1866301
Searching through a single link list is O(n) since its linear.
Yes
Insertion and deletion in a single link list could either be O(1) or O(n). It is O(1) if the
address to the node is available otherwise its O(n) since a linear search would be required.
Yes, if the nodes of the linked list are indexed by their addresses, you could get O(1), else you will have to search through the list which is O(n). There are other variants like skip list for which search is logarithmic, O(log n).
http://en.wikipedia.org/wiki/Skip_list
1) Insertion at the end of a dynamic array is amortized O(1). The reason is that, if the insertion forces a reallocation, the existing elements need to be moved to a new block of memory which is O(n). If you make sure the array is grown by a constant factor every time an allocation occurs, eventually the moves become infrequent enough to become insignificant. Inserting into the middle of a dynamic array will be O(n) to shift the elements after the insertion point over.
2) Correct; searching through a linked list, sorted or not, is O(n) because each element of the linked list will be visited during the search.
3) This is also correct. If you don't need a sorted list, insertion into a singly linked list is usually implemented as adding to the head of the list, so you can just update the list head to the new node and the next pointer of the new node to the be the old head of the list. This means insertion into an unsorted singly linked list will often be indicated as O(1) without much discussion.
Take a look at this cheatsheet for algorithm complexity, I use it as a reference

What is the best data structure for removing an element from the middle?

The data stucture must be like a stack. Only with one difference. I want to pop from any index not only last. When I have popped element n, the elements with indexes N > n must swap to N-1. Any ideas?
P.S.
Pushing element n into the last index of the stack.
Then popping it out.
Then deleting stack[n]
is a bad idea.
I think you're looking for a linked list.
A linked list (std::list) will allow you to remove an element from the middle with O(1) complexity and automatically "pull" up the elements after it. You can use a linked list like a stack by using push_front. However you need to be aware that accessing an element in a linked list is O(n) as you would need to start at the head of the list and then walk along the links from one element to the next until you have arrived at element n (so there is no O(1) indexing)
Basically you would need to
Create an iterator
advance it to position n
Get the element from the iterator
erase the element the iterator is currently pointing to
Some example code can be found here.
You need to implement a linked list, but unlike an array, the order in a linked list is determined by a pointer in each object. So, you cannot use indices to access the elements.

Is time complexity for insertion/deletion in a doubly linked list of order O(n)?

To insert/delete a node with a particular value in DLL (doubly linked list) entire list need to be traversed to find the location hence these operations should be O(n).
If that's the case then how come STL list (most likely implemented using DLL) is able to provide these operations in constant time?
Thanks everyone for making it clear to me.
Insertion and deletion at a known position is O(1). However, finding that position is O(n), unless it is the head or tail of the list.
When we talk about insertion and deletion complexity, we generally assume we already know where that's going to occur.
It's not. The STL methods take an iterator to the position where insertion is to happen, so strictly speaking, they ARE O(1), because you're giving them the position. You still have to find the position yourself in O(n) however.
Deleting an arbitrary value (rather than a node) will indeed be O(n) as it will need to find the value. Deleting a node (i.e. when you start off knowing the node) is O(1).
Inserting based on the value - e.g. inserting in a sorted list - will be O(n). If you're inserting after or before an existing known node is O(1).
Inserting to the head or tail of the list will always be O(1) - because those are just special cases of the above.