Linked Lists
Linked Lists
Linked Lists
Topics
1. What is a Linked List?
2. Types of Linked Lists
3. Advantages & Disadvantages of a Linked List
4. Implementation of Doubly Linked List in C/C++
5. Problems & Solutions
6. Important References
What is a Linked List?
A Linked List [LL] allocates space for each element separately in its own block of memory
called a "linked list element" or "node". The list gets its overall structure by using pointers to
connect all its nodes together like the links in a chain. This is in contrast to an array, where
memory is allocated for all its elements lumped together as one block of memory.
Each node contains two fields: a "data" field to store whatever element type the list holds for
its client, and a "next" field which is a pointer used to link one node to the next node. Each
node is allocated in the heap with a call to malloc(), so the node memory continues to exist
until it is explicitly deallocated with a call to free(). The front of the list is a pointer to the
first node. Here is what a list containing the numbers 1, 2, and 3 might look like:
Disadvantages of arrays:
1. The size of the array is fixed. Most often this size is specified at compile time with a
simple declaration. With a little extra effort, the size of the array can be deferred until
the array is created at runtime, but after that it remains fixed. (additional information
for experts) We can go to the trouble of dynamically allocating an array in the heap
and then dynamically resizing it with realloc(), but that requires some real
programming effort.
2. Because of the aforementioned reason, the most convenient thing for programmers to
do is to allocate arrays which seem "large enough". Although convenient, this strategy
has two disadvantages:
a. Most of the time there are just 20 or 30 elements in the array and 70% of the
space in the array is not really utilised.
b. If the program ever needs to process more elements, the code breaks.
3. Inserting new elements at the front is potentially expensive because existing elements
need to be shifted over to make room, thereby, increasing the overall complexity.
Linked lists have their own strengths and weaknesses, but they happen to be strong where
arrays are weak. All arrays follow a simple strategy and that is allocating the memory for all
its elements in one block of memory. Linked lists use an entirely different strategy. As we
will see, linked lists allocate memory for each element separately and only when necessary.
Singly Linked List: Singly linked lists contain nodes which have a data part as well as an
address part i.e., next, which points to the next node in the sequence of nodes. The
operations we can perform on a singly linked list are insertion, deletion and traversal.
Doubly Linked List: In a doubly linked list, each node contains two links the first link points
to the previous node (prev) and the next link (next) points to the next node in the sequence.
Compared to SLL, the added advantage of a DLL is that, from a given node we can traverse
backward and forward using a DLL. But this costs us extra memory for using an additional
pointer for every node to also point to the previous node.
Circular Linked List: In the circular linked list the last node of the list contains the address
of the first node and forms a circular chain. A CLL can be implemented using a SLL or a
DLL. The last node of the CLL doesn’t point to NULL. A simple example where CLL’s are
used is when we try to keep track of whose turn it is in a multi-player board game.
// Structure of node
typedef struct node {
int data;
struct node *next, *prev;
} node;
// Structure of LL, where root is a dummy node of LL, and last is the tail of LL
typedef struct LinkedList {
node *root, *last;
int size;
} LinkedList;
// Function that deletes all nodes with the a particular data value in the LL
void deleteNode(LinkedList* linkedList, int num) {
node* current = linkedList->root;
while(current->next != NULL) {
if(current->next->data == num) {
node* tmp = current->next;
tmp->next->prev = current;
current->next = tmp->next;
free(tmp);
linkedList->size = linkedList->size - 1;
}
else
current=current->next;
}
}
// Function that returns the data present at the tail node of the LL
int back(LinkedList* linkedList) {
if(linkedList->size == 0)
return -1;
return linkedList->last->data;
}
// Function that returns the data present at the head node of the LL
int front(LinkedList* linkedList) {
if(linkedList->size == 0)
return -1;
return linkedList->root->next->data;
}
// Driver Program
int main() {
LinkedList* linkedList = createLinkedList();
cout<<"isEmpty:"<<(isEmpty(linkedList)?"True":"False")<<endl;
push_front(linkedList, 2);
push_back(linkedList, 3
);
printList(linkedList);
pop_front(linkedList);
printList(linkedList);
push_back(linkedList, 4
);
push_front(linkedList, 1);
push_back(linkedList, 5 );
push_back(linkedList, 4 );
printList(linkedList);
deleteNode(linkedList, 4);
printList(linkedList);
pop_back(linkedList);
printList(linkedList);
push_back(linkedList, 5
);
cout<<"isEmpty:"<<(isEmpty(linkedList)?"True":"False")<<endl;
cout<<"Length:"<<length(linkedList)<<endl;
printList(linkedList);
return 0;
}
Problems & Solutions
Iterative Recursive
6. Rearrange a given linked list in-place, i.e., “L0 -> L1 -> L2 -> … Ln-2 -> Ln-1 -> Ln” to
> L
“L0 - n -
> L1 -> Ln-1 -> L2 -> Ln-2 …”
Example: 1 ->2->3->4->5->NULL; Ans: 1 ->5->2->4->3->NULL
Solution: Find the midpoint, split the list into two parts, reverse the right part and
create a list by taking alternate elements from 1st part and 2nd part.
7. You are given a Doubly Linked List with one pointer of each node pointing to the
next node just like in a singly linked list. The second pointer however can point to any
node in the list and not just the previous node. WAP to clone such a linked list, i.e.,
create an exact clone of the given linked list with next and random pointers pointing
to the same valued elements (not the addresses) in the original linked list.
Solution:
a. Create a copy of each node and insert it between the current and the next node.
b. Update random pointer for the new nodes by:
original->next->random = original->random->next;
c. Restore the original and copy linked lists using:
original->next = original->next->next;
copy->next = copy->next->next;
8. Add two numbers represented by linked lists into a new Linked List.
Example: 5->6->3->NULL + 8->4->2->NULL ; Ans: 1->4->0->5->NULL, i.e.,
Solution: Reverse the two given linked lists, add node by node and maintain a carry
pointer into a new list and finally reverse the new list to obtain the final answer.
12. Write a function to get the intersection point of two Linked Lists, given their head
pointers.
Example:
Input1: 1->2->3->4->5->6->7->8->NULL and k=3
->2->1->6->5->4->8->7->NULL
Output1: 3
Solution:
Recursive Iterative
node* reverseK(node *root, int k) node* reverseK(node *root, i nt k)
{ {
if(root == NULL) node *h
ead = createNode(-1) ;
return NULL; node *p 2 = root, *p
1 = head;
node *p rev = NULL;
node* prev=NULL, *n
ext=NULL; int cnt = 0 ;
node* head = root;
int cnt = 0; while(r oot != NULL) {
while(root != NULL & & cnt<k
) { node* tmp = root->next;
next = root->next; root->next = prev;
root->next = prev; prev = root;
prev = root; root = tmp;
root = next; cnt++;
cnt++; if(cnt == k) {
} cnt = 0
;
p1->next = prev;
head->next = reverseK(r
oot, prev = NULL;
k); p1 = p2; p2 = root;
return prev; }
} }
p1->next = prev;
return head->next;
}
14. Given a pointer to the head of a circular linked list, split the list in two equal sized
circular linked lists and return the head of the 2nd circular list.
Solution:
a. Store the mid and last pointers of the circular linked list using tortoise and
hare algorithm.
b. Make the second half circular.
c. Make the first half circular.
d. Set head (or start) pointers of the two linked lists.
15. Insert an element in a sorted circular linked list, given a pointer to the head of the list.
Solution: Iterate and find the position for insertion, carefully handle edge cases. One
of the edge case is that, we can insert a smaller element than the first element.
Another edge case is, we can insert a greater element than the last element. Therefore,
these edge cases are to be handled efficiently.
Important References
1. Linked List vs Array
2. Singly Linked List & Doubly Linked List Implementations in Java
3. Singly Linket List & Doubly Linked List Implementations in Python
4. XOR Linked Lists - A Memory Efficient Linked List - Part-1 & Part-2