Traversing linked list is easy but what would happen if there is a cycle in the linked list? It will just fall into an infinity loop if traversing is implemented without a cycle prevention code. Thankfully, there is already a known algorithm for this kind of problem.
How to detect if a cycle exists?
Suppose there is a linked list that has a cycle like below. Now the question is how to detect the cycle.
Let’s first think about what would happen if there is a cycle.
Iterating linked list typically happens in a while loop like these steps.
Check if the node is valid (not null)
Process node
Move on to next node and do #1,2
Normally iteration will be done once it reaches the end of the list of which next node is null. However, it will fall into an infinite loop if there is a cycle since there won’t be any null node.
Then, the key is that we shouldn’t visit a node if it was already visited. How do we detect visited nodes? The easy answer is to have a cache (map, array or whatever) that keeps track of visited nodes.
So there will be an additional step for cycle detection
Check if the node is valid (not null)
Process node
Move on to next node
Check if this node is already visited. If yes then there is a cycle.
What is the time complexity of this problem? O(n). We only need to iterate once. What is the space complexity of this problem? O(n). Potentially we need to keep track of all visited nodes.
Can we do better?
We cannot do better on time complexity because you need to iterate the list at least once. However, there is definitely a way to improve space complexity. In fact, we can solve this problem with O(1) space complexity.
How can we do that? Think about two people running on a circular track. And let’s say one person is able to run twice faster than the other. Then what would happen in that case? The faster runner will catch the slow runner at the start point after 1 lap of slow person since the faster runner is able to run track twice while the slow runner finishes 1 lap. We can apply exact same principle to detect a cycle in a linked list. If we have two pointers that one pointer iterates twice faster than the other faster pointer will catch up slower pointer once it completes 1 lap.
How to find out where the cycle starts?
I think some visualization will do a much better job than a verbal explanation. So let’s take a look.
The cycle may start after a few nodes. But as you can see in the picture, the number of nodes before cycle essentially means the number of nodes before cycle start node where two pointers begin iteration.
The above picture shows you that node 15 will be the location where two pointers will meet. Then, all we need to do is have another pointer at the start of the linked list and iterate both pointer 1 (slow pointer) and 3rd pointer until they meet which is the cycle node!
Today, we will go over a popular data structure – linked list. Linked list may not make much sense for some languages such as python and javascript since there is already a good one such as list. However, since linked list is quite useful for languages like C++ it is still worthwhile to go over. In this post I will go over some important characteristics about linked list and strengths and weaknesses. Please note that all the explanations will be based on C++.
What is Linked List?
Linked list is a data structure that each node is connected like a chain via pointer. It is one of fundamental data structure in C++ and widely used in C++ standard library (STL).
I believe a picture is worth a thousand words and let’s take a look at how a linked list looks like before any explanations.
As you see above there are three nodes in the linked list. Each node is based on Node struct as below which contains a value and a pointer to the next Node.
struct Node
{
// value that this Node contains.
// It could be any data type you want to contain
int val;
// pointer to next node
Node *next;
// pointer to previous node.
// this is optional
// Node *prev;
};
The example code has only one pointer ‘next’ but you can also have another Node * to point to the previous Node. If you have only one pointer (likely it’s a pointer to the next Node) it’s called a singly linked list. If you have two pointers one pointing to the next and the other to the previous then it’s called a doubly linked list.
Linked List Operations – insert, search, delete
As you can imagine there are three basic operations for a linked list. You need to be able to search a node in the list, insert and delete. Let’s go over each operation.
Search
How should the search be? Given you have a value to search you need to start from the head of the list and check if each node in a chain is the same as the search value. Return a pointer to the node if you find it or null pointer otherwise. Let’s take a look at code example for search.
/**
* @brief search a node based on search value
*
* @param head head of linked list
* @param searchVal value to search in the list
*
* @return pointer to found node. null otherwise
*/
Node *searchNode(Node *head, int searchVal)
{
// end of the list. value not found
if (!head)
{
return 0;
}
if (head->val == serachVal)
{
return head;
}
else
{
return searchNode(head->next, searchVal);
}
}
I implemented the operation recursively so I don’t need to write a loop. (I thought the recursive solution was easier than loop but it’s your choice) I actually also used loop based implementation for insertion operation for your reference.
Insertion
Inserting a node could be slightly trickier depending on how you want to maintain the list. There are three possible cases for insertion.
1. Insert the node at the head of the list This one is the easiest as you just need to create a node and insert the node. Here is the code example for head insertion.
/**
* @brief node insertion at the beginning of the list
*
* @param head double pointer to head since you need to update 'head' after insertion
* @param newVal new value to insertNode
*/
void insertNode(Node **head, int newVal)
{
Node *newNode = new Node;
newNode->val = newVal;
newNode->next = *head;
// update head
*head = newNode;
}
2. Insert the node at the tail of the list This one is slightly harder than head insertion but still pretty simple. What you need to do is to traverse to the end of the list and insert the node there.
/**
* @brief node insertion at the end of the list
*
* @param head double pointer to head since you still might need to update 'head'
* if the list is empty
* @param newVal new value to insert
*/
void insertNode(Node **head, int newVal)
{
Node *newNode = new Node;
newNode->val = newVal;
newNode->next = 0;
Node *tailNode = *head;
while (tailNode && tailNode->next)
{
tailNode = tailNode->next;
}
if (!tailNode)
{
*head = newNode;
}
else
{
tailNode->next = newNode;
}
}
3. Insert the node based on the sorted order of the list. Imagine you want to maintain the list in sorted order (decreasing or increasing). Then you will need to find a proper location to insert.
For example, let’s say you have a list like below and you would like to insert value 4. 1-> 3-> 5
Then you need to traverse the list and find a node that is not less than 4 which is 5, the last one, and create a node and insert. However, there is a couple of other edge cases such as the inserting position happens to be at the head or tail of the list. Although this one is not terribly difficult it does require little more thoughts than the others.
void insertNode(Node **head, int newVal)
{
// create new Node
Node *newNode = new Node;
newNode->val = newVal;
newNode->next = 0;
// if empty list then just insert it here
if (!*head)
{
*head = newNode;
return;
}
// check if it needs to be inserted at head.
else if (newNode->val < (*head)->val)
{
newNode->next = *head;
*head = newNode;
return;
}
Node *iter = *head;
// loop until you find first Node that is not less than new value
while (iter && iter->next)
{
if (iter->next->val >= newNode->val)
{
break;
}
iter = iter->next;
}
if (iter->next)
{
newNode->next = iter->next;
}
iter->next = newNode;
}
The tricky part of insertion is that you have to make sure you update the next pointers properly. 1. If insertion is happening at the head, make sure head pointer is updated and the next pointer of new node point to last head 2. If insertion is happening at the tail, make sure the last node of the list is pointing to the new node properly 3. If insertion is happening in the middle of the list, make sure the previous node’s next points to new node and new node points to the proper next one.
deletion
The deletion of a node is a very similar process as insertion. First, you need to find which node to delete if there is any and properly updates the next pointers. Just like insertion deleting node could be the head, the tail or in the middle of the list. Let’s take a look at the code example.
/**
* @brief delete a node from the list if there is one
* for simplicity I don't delete all the duplicate values here
*
* @param head double pointer to head in case you have to delete head node
* @param deleteVal value to delete
*/
void deleteNode(Node **head, int deleteVal)
{
Node *iter = *head;
// nothing to delete
if (!*head)
{
return;
}
// deleting head. need to update head
else if ((*head)->val == deleteVal)
{
*head = (*head)->next;
delete iter;
return;
}
while (iter && iter->next)
{
if (iter->next->val == deleteVal)
{
break;
}
iter = iter->next;
}
if (iter->next)
{
Node *delNode = iter->next;
iter->next = iter->next->next;
delete delNode;
}
}
Pros
Unlike an array size of a linked list is very flexible as long as the memory permits
Insertion and deletion are much simpler than an array. For an array insertion/deletion you have to move many other elements after the operation
It is a perfect data structure to implement more complex data structures like Stack and Queue since you only need to maintain head(or tail) of the list for insertion, search, deletion
Cons
It requires extra memory (next, prev pointers)
It does not allow random access like an array and therefore search could be slower than an array
Performance
Search – O(n) Insertion – O(1) if always insert at the head (for cases like Stack) O(n) otherwise Deletion – O(1) if always delete head (for cases like Stack) O(n) otherwise
Conclusion
We have taken a look at some basics of linked list. Linked list is a great data structure for some special purposes due to its flexibility of insertion and deletion. However, there are some weaknesses about it so it requires some discretion to wisely use it.
Thank you for reading my post and please let me know if you have any questions or suggestions and good luck!
This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Cookie settingsOK
Privacy & Cookies Policy
Privacy Overview
This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.