Data Structure UNIT-1
Data Structure UNIT-1
Data Structure:
Data Structure is a systematic way to organize data in order to use it efficiently. Following terms are the
foundation terms of a data structure.
Interface − each data structure has an interface. Interface represents the set of operations that
a data structure supports. An interface only provides the list of supported operations, type of
parameters they can accept and return type of these operations.
Implementation − Implementation provides the internal representation of a data structure.
Implementation also provides the definition of the algorithms used in the operations of the data
structure.
There are three cases which are usually used to compare various data structure's execution time in a
relative manner.
Worst Case − This is the scenario where a particular data structure operation takes maximum
time it can take. If an operation's worst case time is ƒ(n) then this operation will not take more
than ƒ(n) time where ƒ(n) represents function of n.
Average Case − This is the scenario depicting the average execution time of an operation of a
data structure. If an operation takes ƒ(n) time in execution, then m operations will take mƒ(n)
time.
Best Case − This is the scenario depicting the least possible execution time of an operation of a
data structure. If an operation takes ƒ(n) time in execution, then the actual operation may take
time as the random number which would be maximum as ƒ(n).
Data Types in C
Each variable in C has an associated data type. Each data type requires different amounts of memory
and has some specific operations which can be performed over it. It specifies the type of data that the
variable can store like integer, character, floating, double, etc. The data type is a collection of data with
values having fixed values, meaning as well as its characteristics.
Types Description
Primitive Data
Types Arithmetic types can be further classified into integer and floating data types.
The data type has no value or operator and it does not provide a result to its caller. But
Void Types void comes under Primitive data types.
User Defined It is mainly used to assign names to integral constants, which make a program easy to
DataTypes read and maintain
The data types that are derived from the primitive or built-in datatypes are referred to
Derived types as Derived Data Types.
Different data types also have different ranges up to which they can store numbers. These ranges may
vary from compiler to compiler. Below is a list of ranges along with the memory requirement and format
specifiers.
Algorithm:
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain
order to get the desired output. Algorithms are generally created independent of underlying languages,
i.e. an algorithm can be implemented in more than one programming language.
From the data structure point of view, following are some important categories of algorithms −
Characteristics of an Algorithm
Not all procedures can be called an algorithm. An algorithm should have the following characteristics −
Unambiguous − Algorithm should be clear and unambiguous. Each of its steps (or phases), and
their inputs/outputs should be clear and must lead to only one meaning.
Output − an algorithm should have 1 or more well-defined outputs, and should match the
desired output.
Efficiency of Algorithm:
Efficiency of an algorithm can be analyzed at two different stages, before implementation and after
implementation. They are the following −
Algorithm Complexity
Suppose X is an algorithm and n is the size of input data, the time and space used by the algorithm X are
the two main factors, which decide the efficiency of X.
Time Factor − Time is measured by counting the number of key operations such as comparisons
in the sorting algorithm.
Space Factor − Space is measured by counting the maximum memory space required by the
algorithm.
The complexity of an algorithm f(n) gives the running time and/or the storage space required by the
algorithm in terms of n as the size of input data.
Space Complexity
Space complexity of an algorithm represents the amount of memory space required by the algorithm in
its life cycle. The space required by an algorithm is equal to the sum of the following two components −
A fixed part that is a space required to store certain data and variables, that are independent of
the size of the problem. For example, simple variables and constants used, program size, etc.
A variable part is a space required by variables, whose size depends on the size of the problem.
For example, dynamic memory allocation, recursion stack space, etc.
Space complexity S(P) of any algorithm P is S(P) = C + SP(I), where C is the fixed part and S(I) is the
variable part of the algorithm, which depends on instance characteristic I. Following is a simple example
that tries to explain the concept −
Algorithm: SUM(A, B)
Step 1 - START
Step 2 - C ← A + B + 10
Step 3 - Stop
Here we have three variables A, B, and C and one constant. Hence S(P) = 1 + 3. Now, space depends on
data types of given variables and constant types and it will be multiplied accordingly.
Time Complexity
Time complexity of an algorithm represents the amount of time required by the algorithm to run to
completion. Time requirements can be defined as a numerical function T(n), where T(n) can be
measured as the number of steps, provided each step consumes constant time.
For example, addition of two n-bit integers takes n steps. Consequently, the total computational time is
T(n) = c ∗ n, where c is the time taken for the addition of two bits. Here, we observe that T(n) grows
linearly as the input size increases.
Asymptotic Analysis:
Asymptotic analysis of an algorithm refers to defining the mathematical boundation/framing of its run-
time performance. Using asymptotic analysis, we can very well conclude the best case, average case,
and worst case scenario of an algorithm.
Asymptotic analysis is input bound i.e., if there's no input to the algorithm, it is concluded to work in a
constant time. Other than the "input" all other factors are considered constant.
Asymptotic analysis refers to computing the running time of any operation in mathematical units of
computation. For example, the running time of one operation is computed as f(n) and may be for
another operation it is computed as g(n2). This means the first operation running time will increase
linearly with the increase in n and the running time of the second operation will increase exponentially
when n increases. Similarly, the running time of both operations will be nearly the same if n is
significantly small.
Usually, the time required by an algorithm falls under three types −
Asymptotic Notations
Following are the commonly used asymptotic notations to calculate the running time complexity of an
algorithm.
Ο Notation
Ω Notation
θ Notation
Big Oh Notation, Ο
The notation Ο(n) is the formal way to express the upper bound of an algorithm's running time. It
measures the worst case time complexity or the longest amount of time an algorithm can possibly take
to complete.
Ο(f(n)) = { g(n) : there exists c > 0 and n0 such that f(n) ≤ c.g(n) for all n > n0. }
Omega Notation, Ω
The notation Ω(n) is the formal way to express the lower bound of an algorithm's running time. It
measures the best case time complexity or the best amount of time an algorithm can possibly take to
complete.
For example, for a function f(n)
Ω(f(n)) ≥ { g(n) : there exists c > 0 and n0 such that g(n) ≤ c.f(n) for all n > n0. }
Theta Notation, θ
The notation θ(n) is the formal way to express both the lower bound and the upper bound of an
algorithm's running time. It is represented as follows −
θ(f(n)) = { g(n) if and only if g(n) = Ο(f(n)) and g(n) = Ω(f(n)) for all n > n0. }
constant − Ο(1)
logarithmic − Ο(log n)
linear − Ο(n)
quadratic − Ο(n2)
cubic − Ο(n3)
polynomial − nΟ(1)
exponential − 2Ο(n)
The best Algorithm is that which helps to solve a problem that requires less space in memory and also
takes less time to generate the output. But in general, it is not always possible to achieve both of these
conditions at the same time. The most common condition is an algorithm using a lookup table. This
means that the answers to some questions for every possible value can be written down. One way of
solving this problem is to write down the entire lookup table, which will let you find answers very
quickly but will use a lot of space. Another way is to calculate the answers without writing down
anything, which uses very little space, but might take a long time. Therefore, the more time-efficient
algorithms you have, that would be less space-efficient.
In C programming, they are the derived data types that can store the primitive type of data such as int,
char, double, float, etc. For example, if we want to store the marks of a student in 6 subjects, then we
don't need to define a different variable for the marks in different subjects. Instead, we can define an
array that can store the marks in each subject at the contiguous memory locations.
Properties of array
There are some of the properties of an array that are listed as follows -
o Each element in an array is of the same data type and carries the same size that is 4 bytes.
o Elements in the array are stored at contiguous memory locations from which the first element is
stored at the smallest memory location.
o Elements of the array can be randomly accessed since we can calculate the address of each
element of the array with the given base address and the size of the data element.
Representation of an array
We can represent an array in various ways in different programming languages. As an illustration, let's
see the declaration of array in C language -
As per the above illustration, there are some of the following important points -
o Arrays are good for storing multiple values in a single variable - In computer programming,
most cases require storing a large number of data of a similar type. To store such an amount of
data, we need to define a large number of variables. It would be very difficult to remember the
names of all the variables while writing the programs. Instead of naming all the variables with a
different name, it is better to define an array and store all the elements into it.
As stated above, all the data elements of an array are stored at contiguous locations in the main
memory. The name of the array represents the base address or the address of the first element in the
main memory. Each element of the array is represented by proper indexing.
3. n (n - based indexing): The first element of the array can reside at any random index number.
In the above image, we have shown the memory allocation of an array arr of size 5. The array follows a
0-based indexing approach. The base address of the array is 100 bytes. It is the address of arr[0]. Here,
the size of the data type used is 4 bytes; therefore, each element will take 4 bytes in the memory.
Basic operations:
Now, let's discuss the basic operations supported in the array -
o Search - It is used to search an element using the given index or by the value.
o Update - It updates an element at a particular index.
data_type array_name[rows][columns];
int arr[4][3];
The 2-dimensional array can be defined as an array of arrays. The 2-Dimensional arrays are organized as
matrices which can be represented as the collection of rows and columns as array[M][N] where M is the
number of rows and N is the number of columns.
Example:
To find the address of any element in a 2-Dimensional array there are the following two ways-
Example: Given an array, arr[1………10][1………15] with base value 100 and the size of each element is 1
Byte in memory. Find the address of arr[8][6] with the help of row-major order.
Solution:
Given:
Base address B = 100
Storage size of one element store in any array W = 1 Bytes
Row Subset of an element whose address to be found I = 8
Column Subset of an element whose address to be found J = 6
Lower Limit of row/start row index of matrix LR = 1
Lower Limit of column/start column index of matrix = 1
Number of column given in the matrix N = Upper Bound – Lower Bound + 1
= 15 – 1 + 1
= 15
Formula:
Address of A[I][J] = B + W * ((I – LR) * N + (J – LC))
Solution:
Address of A[8][6] = 100 + 1 * ((8 – 1) * 15 + (6 – 1))
= 100 + 1 * ((7) * 15 + (5))
= 100 + 1 * (110)
Address of A[I][J] = 210
If elements of an array are stored in a column-major fashion means moving across the column and then
to the next column then it’s in column-major order. To find the address of the element using column-
major order use the following formula:
Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))
Example: Given an array arr[1………10][1………15] with a base value of 100 and the size of each element
is 1 Byte in memory find the address of arr[8][6] with the help of column-major order.
Solution:
Given:
Base address B = 100
Storage size of one element store in any array W = 1 Bytes
Row Subset of an element whose address to be found I = 8
Column Subset of an element whose address to be found J = 6
Lower Limit of row/start row index of matrix LR = 1
Lower Limit of column/start column index of matrix = 1
Number of Rows given in the matrix M = Upper Bound – Lower Bound + 1
= 10 – 1 + 1
= 10
Formula: used
Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))
Address of A[8][6] = 100 + 1 * ((6 – 1) * 10 + (8 – 1))
= 100 + 1 * ((5) * 10 + (7))
= 100 + 1 * (57)
Address of A[I][J] = 157
From the above examples, it can be observed that for the same position two different address locations
are obtained that’s because in row-major order movement is done across the rows and then down to
the next row, and in column-major order, first move down to the first column and then next column. So
both the answers are right.
So it’s all based on the position of the element whose address is to be found for some cases the same
answers is also obtained with row-major order and column-major order and for some cases, different
answers are obtained.
Applications of Array Data Structure:
Storing and accessing data: Arrays are used to store and retrieve data in a specific order. For
example, an array can be used to store the scores of a group of students, or the temperatures
recorded by a weather station.
Sorting: Arrays can be used to sort data in ascending or descending order. Sorting algorithms
such as bubble sort, merge sort, and quicksort rely heavily on arrays.
Searching: Arrays can be searched for specific elements using algorithms such as linear search
and binary search.
Matrices: Arrays are used to represent matrices in mathematical computations such as matrix
multiplication, linear algebra, and image processing.
Stacks and queues: Arrays are used as the underlying data structure for implementing stacks
and queues, which are commonly used in algorithms and data structures.
Graphs: Arrays can be used to represent graphs in computer science. Each element in the array
represents a node in the graph, and the relationships between the nodes are represented by the
values stored in the array.
Signal Processing: Arrays are used in signal processing to represent a set of samples that are
collected over time. This can be used in applications such as speech recognition, image
processing, and radar systems.
Multimedia Applications: Arrays are used in multimedia applications such as video and audio
processing, where they are used to store the pixel or audio samples. For example, an array can
be used to store the RGB values of an image.
Data Mining: Arrays are used in data mining applications to represent large datasets. This allows
for efficient data access and processing, which is important in real-time applications.
Robotics: Arrays are used in robotics to represent the position and orientation of objects in 3D
space. This can be used in applications such as motion planning and object recognition.
Real-time Monitoring and Control Systems: Arrays are used in real-time monitoring and control
systems to store sensor data and control signals. This allows for real-time processing and
decision-making, which is important in applications such as industrial automation and aerospace
systems.
Financial Analysis: Arrays are used in financial analysis to store historical stock prices and other
financial data. This allows for efficient data access and analysis, which is important in real-time
trading systems.
Scientific Computing: Arrays are used in scientific computing to represent numerical data, such
as measurements from experiments and simulations. This allows for efficient data processing
and visualization, which is important in real-time scientific analysis and experimentation.
Efficient access to elements: Arrays provide direct and efficient access to any element in the
collection. Accessing an element in an array is an O(1) operation, meaning that the time
required to access an element is constant and does not depend on the size of the array.
Fast data retrieval: Arrays allow for fast data retrieval because the data is stored in contiguous
memory locations. This means that the data can be accessed quickly and efficiently without the
need for complex data structures or algorithms.
Memory efficiency: Arrays are a memory-efficient way of storing data. Because the elements of
an array are stored in contiguous memory locations, the size of the array is known at compile
time. This means that memory can be allocated for the entire array in one block, reducing
memory fragmentation.
Versatility: Arrays can be used to store a wide range of data types, including integers, floating-
point numbers, characters, and even complex data structures such as objects and pointers.
Easy to implement: Arrays are easy to implement and understand, making them an ideal choice
for beginners learning computer programming.
Compatibility with hardware: The array data structure is compatible with most hardware
architectures, making it a versatile tool for programming in a wide range of environments.
Fixed size: Arrays have a fixed size that is determined at the time of creation. This means that if
the size of the array needs to be increased, a new array must be created and the data must be
copied from the old array to the new array, which can be time-consuming and memory-
intensive.
Memory allocation issues: Allocating a large array can be problematic, particularly in systems
with limited memory. If the size of the array is too large, the system may run out of memory,
which can cause the program to crash.
Insertion and deletion issues: Inserting or deleting an element from an array can be inefficient
and time-consuming because all the elements after the insertion or deletion point must be
shifted to accommodate the change.
Wasted space: If an array is not fully populated, there can be wasted space in the memory
allocated for the array. This can be a concern if memory is limited.
Limited data type support: Arrays have limited support for complex data types such as objects
and structures, as the elements of an array must all be of the same data type.
Lack of flexibility: The fixed size and limited support for complex data types can make arrays
inflexible compared to other data structures such as linked lists and trees.
A matrix is a two-dimensional data object made of m rows and n columns, therefore having total m x n
values. If most of the elements of the matrix have 0 value, then it is called a sparse matrix.
Storage: There are lesser non-zero elements than zeros and thus lesser memory can be used to store
only those elements.
Computing time: Computing time can be saved by logically designing a data structure traversing only
non-zero elements.
Example:
00304
00570
00000
02600
Representing a sparse matrix by a 2D array leads to wastage of lots of memory as zeroes in the matrix
are of no use in most of the cases. So, instead of storing zeroes with non-zero elements, we only store
non-zero elements. This means storing non-zero elements with triples- (Row, Column, value).
2D array is used to represent a sparse matrix in which there are three rows named as
In simple words, a linked list consists of nodes where each node contains a data field and a
reference (link) to the next node in the list.
Till now, we were using array data structure to organize the group of elements that are to be stored
individually in the memory. However, Array has several advantages and disadvantages which must be
known in order to decide the data structure which will be used throughout the program.
1. The size of array must be known in advance before using it in the program.
2. Increasing size of the array is a time taking process. It is almost impossible to expand the size of
the array at run time.
3. All the elements in the array need to be contiguously stored in the memory. Inserting any
element in the array needs shifting of all its predecessors.
Linked list is the data structure which can overcome all the limitations of an array. Using linked list is
useful because,
1. It allocates the memory dynamically. All the nodes of linked list are non-contiguously stored in
the memory and linked together with the help of pointers.
2. Sizing is no longer a problem since we do not need to define its size at the time of declaration.
List grows as per the program's demand and limited to the available memory space.
Insertion
The insertion into a singly linked list can be performed at different positions. Based on the position of
the new node being inserted, the insertion is categorized into the following categories.
SN Operation Description
1 Insertion at It involves inserting any element at the front of the list. We just need to a few link
beginning adjustments to make the new node as the head of the list.
2 Insertion at end of It involves insertion at the last of the linked list. The new node can be inserted as
the list the only node in the list or it can be inserted as the last one. Different logics are
implemented in each scenario.
3 Insertion after It involves insertion after the specified node of the linked list. We need to skip the
specified node desired number of nodes in order to reach the node after which the new node will
be inserted. .
The Deletion of a node from a singly linked list can be performed at different positions. Based on the
position of the node being deleted, the operation is categorized into the following categories.
SN Operation Description
1 Deletion at It involves deletion of a node from the beginning of the list. This is the
beginning simplest operation among all. It just need a few adjustments in the node
pointers.
2 Deletion at the It involves deleting the last node of the list. The list can either be empty or
end of the list full. Different logic is implemented for the different scenarios.
3 Deletion after It involves deleting the node after the specified node in the list. we need to
specified node skip the desired number of nodes to reach the node after which the node will
be deleted. This requires traversing through the list.
4 Traversing In traversing, we simply visit each node of the list at least once in order to
perform some specific operation on it, for example, printing data part of each
node present in the list.
5 Searching In searching, we match each element of the list with the given element. If the
element is found on any of the location then location of that element is
returned otherwise null is returned. .
Linked list program
#include<stdio.h>
#include<stdlib.h>
struct node
{
int data;
struct node *next;
};
struct node *head;
void beginsert ();
void lastinsert ();
void randominsert();
void begin_delete();
void last_delete();
void random_delete();
void display();
void search();
void main ()
{
int choice =0;
while(choice != 9)
{
printf("\n\n*********Main Menu*********\n");
printf("\nChoose one option from the following list ...\n");
printf("\n===============================================\n");
printf("\n 1.Insert in begining\n 2.Insert at last\n 3.Insert at any random location\n4.Delete from
Beginning\n 5.Delete from last\n 6.Delete node after specified location\n 7.Search for an element\n
8.Show\n 9.Exit\n");
printf("\nEnter your choice?\n");
scanf("\n%d",&choice);
switch(choice)
{
case 1: beginsert();
break;
case 2: lastinsert();
break;
case 3: randominsert();
break;
case 4: begin_delete();
break;
case 5: last_delete();
break;
case 6: random_delete();
break;
case 7: search();
break;
case 8: display();
break;
case 9: exit(0);
break;
default: printf("Please enter valid choice..");
}
}
}
void beginsert()
{
struct node *ptr;
int item;
ptr = (struct node *) malloc(sizeof(struct node ));
if(ptr == NULL)
{
printf("\nOVERFLOW");
}
else
{
printf("\nEnter value\n");
scanf("%d",&item);
ptr->data = item;
ptr->next =head;
head = ptr;
printf("\nNode inserted");
}
}
void lastinsert()
{
struct node *ptr,*temp;
int item;
ptr = (struct node*)malloc(sizeof(struct node));
if(ptr == NULL)
{
printf("\nOVERFLOW");
}
else
{
printf("\nEnter value?\n");
scanf("%d",&item);
ptr->data = item;
if(head == NULL)
{
ptr -> next = NULL;
head = ptr;
printf("\nNode inserted");
}
else
{
temp = head;
while (temp -> next != NULL)
{
temp = temp -> next;
}
temp->next = ptr;
ptr->next = NULL;
printf("\nNode inserted");
}
}
}
void randominsert()
{
int i,loc,item;
struct node *ptr, *temp;
ptr = (struct node *) malloc (sizeof(struct node));
if(ptr == NULL)
{
printf("\nOVERFLOW");
}
else
{
printf("\nEnter element value");
scanf("%d",&item);
ptr->data = item;
printf("\nEnter the location after which you want to insert ");
scanf("\n%d",&loc);
temp=head;
for(i=0;i<loc;i++)
{
temp = temp->next;
if(temp == NULL)
{
printf("\n can't insert\n");
return;
}
}
ptr ->next = temp ->next;
temp ->next = ptr;
printf("\nNode inserted");
}
}
void begin_delete()
{
struct node *ptr;
if(head == NULL)
{
printf("\n List is empty\n");
}
else
{
ptr = head;
head = ptr->next;
free(ptr);
printf("\nNode deleted from the begining ...\n");
}
}
void last_delete()
{
struct node *ptr,*ptr1;
if(head == NULL)
{
printf("\nlist is empty");
}
else if(head -> next == NULL)
{
head = NULL;
free(head);
printf("\nOnly node of the list deleted ...\n");
}
else
{
ptr = head;
while(ptr->next != NULL)
{
ptr1 = ptr;
ptr = ptr ->next;
}
ptr1->next = NULL;
free(ptr);
printf("\nDeleted Node from the last ...\n");
}
}
void random_delete()
{
struct node *ptr,*ptr1;
int loc,i;
printf("\n Enter the location of the node after which you want to perform deletion \n");
scanf("%d",&loc);
ptr=head;
for(i=0;i<=loc;i++)
{
ptr1 = ptr;
ptr = ptr->next;
if(ptr == NULL)
{
printf("\nCan't delete");
return;
}
}
ptr1 ->next = ptr ->next;
free(ptr);
printf("\nDeleted node %d ",loc+1);
}
void search()
{
struct node *ptr;
int item,i=0,flag;
ptr = head;
if(ptr == NULL)
{
printf("\nEmpty List\n");
}
else
{
printf("\nEnter item which you want to search?\n");
scanf("%d",&item);
while (ptr!=NULL)
{
if(ptr->data == item)
{
printf("item found at location %d ",i+1);
flag=0;
}
else
{
flag=1;
}
i++;
ptr = ptr -> next;
}
if(flag==1)
{
printf("Item not found\n");
}
}
}
void display()
{
struct node *ptr;
ptr = head;
if(ptr == NULL)
{
printf("Nothing to print");
}
else
{
printf("\nprinting values . . . . .\n");
while (ptr!=NULL)
{
printf("\n%d",ptr->data);
ptr = ptr -> next;
}
}
}
Doubly linked list
Doubly linked list is a complex type of linked list in which a node contains a pointer to the previous as
well as the next node in the sequence. Therefore, in a doubly linked list, a node consists of three parts:
node data, pointer to the next node in sequence (next pointer) , pointer to the previous node (previous
pointer). A sample node in a doubly linked list is shown in the figure.
A doubly linked list containing three nodes having numbers from 1 to 3 in their data part, is shown in the
following image.
struct node
{
struct node *prev;
int data;
struct node *next;
}
The prev part of the first node and the next part of the last node will always contain null indicating end
in each direction.
In a singly linked list, we could traverse only in one direction, because each node contains address of the
next node and it doesn't have any record of its previous nodes. However, doubly linked list overcome
this limitation of singly linked list. Due to the fact that, each node of the list contains the address of its
previous node, we can find all the details about the previous node as well by using the previous address
stored inside the previous part of each node.
Memory Representation of a doubly linked list
Memory Representation of a doubly linked list is shown in the following image. Generally, doubly linked
list consumes more space for every node and therefore, causes more expansive basic operations such as
insertion and deletion. However, we can easily manipulate the elements of the list since the list
maintains pointers in both the directions (forward and backward).
Node Creation
struct node
{
struct node *prev;
int data;
struct node *next;
};
struct node *head;
All the remaining operations regarding doubly linked list are described in the following table.
SN Operation Description
1 Insertion at beginning Adding the node into the linked list at beginning.
2 Insertion at end Adding the node into the linked list to the end.
3 Insertion after specified Adding the node into the linked list after the specified node.
node
5 Deletion at the end Removing the node from end of the list.
6 Deletion of the node Removing the node which is present just after the node containing
having given data the given data.
7 Searching Comparing each node data with the item to be searched and return
the location of the item in the list if the item found else return null.
8 Traversing Visiting each node of the list at least once in order to perform some
specific operation like searching, sorting, display, etc.
Circular Linked List
The circular linked list is a linked list where all nodes are connected to form a circle. In a circular linked
list, the first node and the last node are connected to each other which forms a circle. There is no NULL
at the end.
Circular singly linked list: In a circular Singly linked list, the last node of the list contains a
pointer to the first node of the list. We traverse the circular singly linked list until we reach the
same node where we started. The circular singly linked list has no beginning or end. No null
value is present in the next part of any of the nodes.
Circular Doubly linked list: Circular Doubly Linked List has properties of both doubly linked list
and circular linked list in which two consecutive elements are linked or connected by the
previous and next pointer and the last node points to the first node by the next pointer and also
the first node points to the last node by the previous pointer.