Unit-4 Notes Linked List
Unit-4 Notes Linked List
When the allocation of memory performs at the compile time, then it is known as static memory. In this,
the memory is allocated for variables by the compiler.
When the memory allocation is done at the execution or run time, then it is called dynamic memory
allocation.
1 When the allocation of memory When the memory allocation is done at the
performs at the compile time, then it is execution or run time, then it is called dynamic
known as static memory. memory allocation.
2 The memory is allocated at the compile The memory is allocated at the runtime.
time.
6 Static memory allocation allots memory Dynamic memory allocation allots memory
from the stack. from the heap.
7 Once the memory is allotted, it will Here, the memory can be alloted at any time in
remain from the beginning to end of the the program.
program.
8 Static memory allocation is less efficient as Dynamic memory allocation is more efficient as
compared to Dynamic memory allocation. compared to the Static memory allocation.
A linked list is a linear Data Structure, consisting of a group of nodes stored at random
addresses. In a linked list the elements are linked using pointers.
Every node stores the data and address of the next node. Every node consists of 2 parts:
Linked list can be represented as the connection of nodes in which each node points to the
next node of the list. Below is the representation of the linked list
A linked-list is a sequence of data structures which are connected together via links.
• Node − Each Node of a linked list can store a data called an element.
• Next − Each Link of a linked list contain a link to next node called Next.
However, if we have items in the list, head will point to a node as shown in the figure below.
This node has some data (in this case -2) and its own pointer that points to the next node in
the list. As we can see in our example, head points to a sequence of five nodes that makes up
our list. The node with the data 67 in it is the last item in the list since its pointer is null. We
often refer to this condition as having a null pointer.
While we will not show them explicitly in this module, each pointer is actually an address in
memory. If we have a pointer to node X in our node, that means that we actually store the
address of X in memory in our node.
To capture the necessary details for a singly linked list, we put everything into a class. The
singly linked list class has two attributes:
Linked Node
To solve the disadvantages of arrays, we need a data structure that allows us to insert
and remove items in an ordered collection in constant time, independently from the
number of items in the data structure.
The solution lies in creating our own specialized data structure where each node contains the
data of interest as well as a reference, or pointer to the next node in the list. Of course, we
would also need to have a pointer to the first node in the list, which we call the head.
The figure below shows how we can construct a linked list data structure. The head entity
shown in the figure is a variable that contains a pointer to the first node in the list, in this case
the node containing -2. Each node in the list is an object that has two main parts: the data that
it holds, and a pointer to the next item in the list.
The class representation of a singly linked list Node is shown below. As discussed above, we
have two attributes: data, which holds the data of the node, and next, which is a reference or
pointer to the next node. We also use a constructor and a standard to String operation to
appropriately create a string representation for the data stored in the node.
// 1. allocate node
Node* new_node = new Node();
A singly list is a linked list that is unidirectional, i.e. it can be traversed in only
one direction starting from the head of the linked list to the end node (tail). In a
Singly Linked List, every node contains:
A data field.
An address field points to the next node.
SYNTAX-
struct Node {
int data;
struct Node* next;
};
node
node.next
node.next.next
head
head.next
SINGLY LINKED LISTS - INSERTION
(4)end function
We show this process in the following example. The figure below shows the initial state as we enter
the prepend operation. Our list has three items in it, an “a”, “W”, and “Q” and we want to add the new
node “M” in front of item “a”.
The figure below shows the effect of the first step of the operation. This step creates a new node for
“M” and changes next to point at the same node as the pointer held by head, which is the address of
the first item in the list, “a”.
The result of performing line 3 in the operation is shown below. In line 3 we simply change head to
point to our new node, instead of node “a”. Notice now that the new node has been fully inserted into
the list.
Since there are no loops in the prepend operation, prepend runs in constant time.
Singly Linked List: It is the most basic linked list in which traversal is
unidirectional i.e. from the head node to the last node.
Example:
class Node {
public:
int data;
Node* next;
Node(int data) {
this->data = data;
this->next = nullptr;
}
};
class SinglyLinkedList {
public:
Node* head;
SinglyLinkedList() {
this->head = nullptr;
}
A doubly linked list is a type of linked list in which a pointer of the previous node as well as
the next node in the sequence. A doubly linked list consists of three parts: node data, pointer
to the previous node, pointer to the next node.
In a Doubly Linked List, a particular node contains:
A data field.
Two address fields next and prev, next points to the immediate next node in the linked list,
and prev points to the immediate previous node in the linked list.
struct Node {
int data;
struct DLLNode* next;
struct DLLNode* prev;
};
DOUBLY LINKED LISTS - INSERTION
Insertion in doubly linked lists is similar to what we saw in the singly linked list with two exceptions:
We must update both the previous and next pointers in all affected nodes.
We can use the tail pointer to make the insertion of data at the end of the list very efficient.
Inserting at the beginning of a doubly linked list is almost as straightforward as in a singly linked list.
We just need to make sure that we update the previous pointer in each affected node. After creating
the new node in line 1, we check to see if the list is empty in line 2. If it is empty, then we only have
to worry about updating the head and tail pointers to both point at node in lines 3 and 4. If the list is
not empty, we have the situation shown below.
To insert a node at the beginning of the list, we set head.previous (the previous pointer in the first
node in the list) to point to the new node in line 5.
Next, we set the next pointer in the new node to point to where head is currently pointing in line 6,
which is the first node in the list.
Finally, we update head to point to the new node and then increment the size in line 8.
With a little bit of reformatting, we can see that we’ve successfully inserted our new node in the list.
struct Node {
int data;
struct Node* next;
struct Node* prev;
};
struct DoublyLinkedList {
struct Node* head;
};
In a Circular Linked List, instead of pointing to NULL, the last node points to
the head of the linked list. Hence, the name circular linked list. The main
advantage of a circular linked list is that we can consider any node as the
starting node and traverse the list.
struct Node {
int data;
struct Node* next;
};
struct Node {
int data;
struct Node* next;
};
struct CircularLinkedList {
struct Node* head;
};
if (list->head == NULL) {
list->head = new_node;
new_node->next = new_node;
return;
}
current->next = new_node;
new_node->next = list->head;
}
Why Linked List?
Although using arrays, we can store the same types of data; we can access elements directly
using the index; however, they have the following drawbacks.
1. The arrays have a fixed size: As a result, we must know the maximum amount of
elements ahead of time. In addition, regardless of use, the allocated memory is always equal
to the maximum limit.
2. Inserting a new element into an array at some middle position is costly since space
must be made for the new elements, and old elements must be shifted to make room.
3. Also deleting an element from an array is costly as it too requires shifting of array
elements.
• Insertion of a node of a linked list can be on three positions i.e. Insertion at the beginning,
Insertion at the end, and Insertion in the middle of the list.
• Deletion: Deletion operations are used to remove an element from the beginning of the
linked list. You can also do delextion in the linked list in three ways either from the end,
beginning, or from a specific position.
• Search: A search operation is used to search an element using the given key. The search
operation is done to find a particular element in the linked list. If the element is found in any
location, then it returns. Else, it will return null.
Time Complexity
Insertion O(n)
Deletion O(n)
Search O(n)
• No Memory Wastage: As the size of a linked list can grow or shrink at runtime, there is no
memory wastage. Only the required memory is allocated.
• Implementation: Some very helpful data structures like queues and stacks can easily be
implemented using a Linked List.
• Insertion and Deletion Operation: In a Linked List, insertion and deletion operations are
quite easy, as there is no need to shift every element after insertion or deletion. Only the
address present in the pointers needs to be updated.
Disadvantages of Linked List
• Memory Usage: The memory required by a linked list is more than the memory required by an array,
as there is also a pointer filed along with the data field in the linked list. The pointer field requires
memory to store the address of the next node.
• Random Access: To access nodes at index x in a linked list, we have to traverse through all the nodes
before it. In the case of an array, we can directly access an element at index x using arr[x].
• Reverse Traversal: In a singly linked list, reverse traversal is not possible, as every node stores only
the address of the next node. But in the case of a doubly-linked list, reverse traversal is possible, but it
consumes more memory, as we have to allocate extra memory to store the previous pointer.
/* Initialize nodes */
struct node *head;
struct node *one = NULL;
struct node *two = NULL;
struct node *three = NULL;
/* Allocate memory */
one = malloc(sizeof(struct node));
two = malloc(sizeof(struct node));
three = malloc(sizeof(struct node));
/* Connect nodes */
one->next = two;
two->next = three;
three->next = NULL;
C malloc() method
The “malloc” or “memory allocation” method in C is used to dynamically allocate a
single large block of memory with the specified size. It returns a pointer of type void which
can be cast into a pointer of any form. It doesn’t Initialize memory at execution time so that
it has initialized each block with the default garbage value initially.
Syntax of malloc() in C
ptr = (cast-type*) malloc(byte-size)
For Example:
#include <stdio.h>
#include <stdlib.h>
// Creating a node
struct node {
int value;
struct node *next;
};
int main() {
// Initialize nodes
struct node *head;
struct node *one = NULL;
struct node *two = NULL;
struct node *three = NULL;
// Allocate memory
one = malloc(sizeof(struct node));
two = malloc(sizeof(struct node));
three = malloc(sizeof(struct node));
// Connect nodes
one->next = two;
two->next = three;
three->next = NULL;
// printing node-value
head = one;
printLinkedlist(head);
}
int main( )
{
list<int> intlist;
list<int>::iterator ptr;
int i;
ptr=intlist.begin( );
cout<<endl<<"First element has value: "<<*ptr;
return 0;
}