Singly Linked List - Chapter 3 - Linked Lists - Data Structures and Algorithms - Narasimha Karumanchi

Download as pdf or txt
Download as pdf or txt
You are on page 1of 15

3.1 What is a Linked List?

A linked list is a data structure used for storing collections of data. A linked list has the following
properties.
• Successive elements are connected by pointers
• The last element points to NULL
• Can grow or shrink in size during execution of a program
• Can be made just as long as required (until systems memory exhausts)
• Does not waste memory space (but takes some extra memory for pointers). It
allocates memory as list grows.
3.2 Linked Lists ADT

The following operations make linked lists an ADT:

Main Linked Lists Operations

• Insert: inserts an element into the list


• Delete: removes and returns the specified position element from the list

Auxiliary Linked Lists Operations

• Delete List: removes all elements of the list (disposes the list)
• Count: returns the number of elements in the list
• Find nth node from the end of the list

3.3 Why Linked Lists?

There are many other data structures that do the same thing as linked lists. Before discussing
linked lists it is important to understand the difference between linked lists and arrays. Both
linked lists and arrays are used to store collections of data, and since both are used for the same
purpose, we need to differentiate their usage. That means in which cases arrays are suitable and
in which cases linked lists are suitable.

3.4 Arrays Overview

One memory block is allocated for the entire array to hold the elements of the array. The array
elements can be accessed in constant time by using the index of the particular element as the
subscript.
Why Constant Time for Accessing Array Elements?

To access an array element, the address of an element is computed as an offset from the base
address of the array and one multiplication is needed to compute what is supposed to be added to
the base address to get the memory address of the element. First the size of an element of that data
type is calculated and then it is multiplied with the index of the element to get the value to be
added to the base address.

This process takes one multiplication and one addition. Since these two operations take constant
time, we can say the array access can be performed in constant time.

Advantages of Arrays

• Simple and easy to use


• Faster access to the elements (constant access)

Disadvantages of Arrays

• Preallocates all needed memory up front and wastes memory space for indices in the
array that are empty.
• Fixed size: The size of the array is static (specify the array size before using it).
• One block allocation: To allocate the array itself at the beginning, sometimes it may
not be possible to get the memory for the complete array (if the array size is big).
• Complex position-based insertion: To insert an element at a given position, we may
need to shift the existing elements. This will create a position for us to insert the
new element at the desired position. If the position at which we want to add an
element is at the beginning, then the shifting operation is more expensive.

Dynamic Arrays

Dynamic array (also called as growable array, resizable array, dynamic table, or array list) is a
random access, variable-size list data structure that allows elements to be added or removed.

One simple way of implementing dynamic arrays is to initially start with some fixed size array.
As soon as that array becomes full, create the new array double the size of the original array.
Similarly, reduce the array size to half if the elements in the array are less than half.

Note: We will see the implementation for dynamic arrays in the Stacks, Queues and Hashing
chapters.

Advantages of Linked Lists

Linked lists have both advantages and disadvantages. The advantage of linked lists is that they can
be expanded in constant time. To create an array, we must allocate memory for a certain number
of elements. To add more elements to the array when full, we must create a new array and copy
the old array into the new array. This can take a lot of time.

We can prevent this by allocating lots of space initially but then we might allocate more than we
need and waste memory. With a linked list, we can start with space for just one allocated element
and add on new elements easily without the need to do any copying and reallocating.

Issues with Linked Lists (Disadvantages)

There are a number of issues with linked lists. The main disadvantage of linked lists is access
time to individual elements. Array is random-access, which means it takes O(1) to access any
element in the array. Linked lists take O(n) for access to an element in the list in the worst case.
Another advantage of arrays in access time is spacial locality in memory. Arrays are defined as
contiguous blocks of memory, and so any array element will be physically near its neighbors. This
greatly benefits from modern CPU caching methods.

Although the dynamic allocation of storage is a great advantage, the overhead with storing and
retrieving data can make a big difference. Sometimes linked lists are hard to manipulate. If the
last item is deleted, the last but one must then have its pointer changed to hold a NULL reference.
This requires that the list is traversed to find the last but one link, and its pointer set to a NULL
reference.

Finally, linked lists waste memory in terms of extra reference points.

3.5 Comparison of Linked Lists with Arrays & Dynamic Arrays


3.6 Singly Linked Lists

Generally “linked list” means a singly linked list. This list consists of a number of nodes in which
each node has a next pointer to the following element. The link of the last node in the list is
NULL, which indicates the end of the list.

Following is a type declaration for a linked list of integers:

Basic Operations on a List


• Traversing the list
• Inserting an item in the list
• Deleting an item from the list

Traversing the Linked List

Let us assume that the head points to the first node of the list. To traverse the list we do the
following
• Follow the pointers.
• Display the contents of the nodes (or count) as they are traversed.
• Stop when the next pointer points to NULL.

The ListLength() function takes a linked list as input and counts the number of nodes in the list.
The function given below can be used for printing the list data with extra print function.

Time Complexity: O(n), for scanning the list of size n.


Space Complexity: O(1), for creating a temporary variable.

Singly Linked List Insertion

Insertion into a singly-linked list has three cases:


• Inserting a new node before the head (at the beginning)
• Inserting a new node after the tail (at the end of the list)
• Inserting a new node at the middle of the list (random location)

Note: To insert an element in the linked list at some position p, assume that after inserting the
element the position of this new node is p.

Inserting a Node in Singly Linked List at the Beginning

In this case, a new node is inserted before the current head node. Only one next pointer needs to
be modified (new node’s next pointer) and it can be done in two steps:
• Update the next pointer of new node, to point to the current head.

• Update head pointer to point to the new node.

Inserting a Node in Singly Linked List at the Ending

In this case, we need to modify two next pointers (last nodes next pointer and new nodes next
pointer).
• New nodes next pointer points to NULL.
• Last nodes next pointer points to the new node.

Inserting a Node in Singly Linked List at the Middle

Let us assume that we are given a position where we want to insert the new node. In this case
also, we need to modify two next pointers.
• If we want to add an element at position 3 then we stop at position 2. That means we
traverse 2 nodes and insert the new node. For simplicity let us assume that the
second node is called position node. The new node points to the next node of the
position where we want to add this node.
• Position node’s next pointer now points to the new node.

Let us write the code for all three cases. We must update the first element pointer in the calling
function, not just in the called function. For this reason we need to send a double pointer. The
following code inserts a node in the singly linked list.
Note: We can implement the three variations of the insert operation separately.

Time Complexity: O(n), since, in the worst case, we may need to insert the node at the end of the
list.
Space Complexity: O(1), for creating one temporary variable.

Singly Linked List Deletion


Similar to insertion, here we also have three cases.
• Deleting the first node
• Deleting the last node
• Deleting an intermediate node.

Deleting the First Node in Singly Linked List

First node (current head node) is removed from the list. It can be done in two steps:
• Create a temporary node which will point to the same node as that of head.

• Now, move the head nodes pointer to the next node and dispose of the temporary
node.

Deleting the Last Node in Singly Linked List

In this case, the last node is removed from the list. This operation is a bit trickier than removing
the first node, because the algorithm should find a node, which is previous to the tail. It can be
done in three steps:
• Traverse the list and while traversing maintain the previous node address also. By
the time we reach the end of the list, we will have two pointers, one pointing to the
tail node and the other pointing to the node before the tail node.
• Update previous node’s next pointer with NULL.

• Dispose of the tail node.

Deleting an Intermediate Node in Singly Linked List

In this case, the node to be removed is always located between two nodes. Head and tail links
are not updated in this case. Such a removal can be done in two steps:
• Similar to the previous case, maintain the previous node while traversing the list.
Once we find the node to be deleted, change the previous node’s next pointer to the
next pointer of the node to be deleted.
• Dispose of the current node to be deleted.
Time Complexity: O(n). In the worst case, we may need to delete the node at the end of the list.
Space Complexity: O(1), for one temporary variable.

Deleting Singly Linked List


This works by storing the current node in some temporary variable and freeing the current node.
After freeing the current node, go to the next node with a temporary variable and repeat this
process for all nodes.

Time Complexity: O(n), for scanning the complete list of size n.


Space Complexity: O(1), for creating one temporary variable.

3.7 Doubly Linked Lists

The advantage of a doubly linked list (also called two – way linked list) is that given a node in
the list, we can navigate in both directions. A node in a singly linked list cannot be removed
unless we have the pointer to its predecessor. But in a doubly linked list, we can delete a node
even if we don’t have the previous node’s address (since each node has a left pointer pointing to
the previous node and can move backward).

The primary disadvantages of doubly linked lists are:


• Each node requires an extra pointer, requiring more space.
• The insertion or deletion of a node takes a bit longer (more pointer operations).

Similar to a singly linked list, let us implement the operations of a doubly linked list. If you
understand the singly linked list operations, then doubly linked list operations are obvious.
Following is a type declaration for a doubly linked list of integers:

You might also like