Dsa Unit 1 & 2
Dsa Unit 1 & 2
Dsa Unit 1 & 2
LECTURE NOTES
MCA I YEAR – I
PATTERN -2024
• Have steps that are simple and definite enough to be done by a computer, and
Abstract data type (ADT): A set of values (the carrier set), and operations on
those values.
Built-in Data Type
Those data types for which a language has built-in support are known as
Built-in Data types. For example, most of the languages provide the following
Integers
Boolean (true, false)
Floating (Decimal numbers)
Character and Strings
Those data types which are implementation independent as they can be implemented in one or the
other way are known as derived data types. These data types are normally built by the combination of
primary or built-in data types and associated operations on them. For example −
List
Array
Stack
Queue
Data Definition
Data Object
Data Type
Data type is a way to classify various types of data such as integer, string, etc.
which determines the values that can be used with the corresponding type of
data, the type of operations that can be performed on the corresponding type
Basic Operations
The data in the data structures are processed by certain operations. The particular data structure
chosen largely depends on the frequency of the operation that needs to be performed on the data
structure.
Traversing
Searching
Insertion
Deletion
Sorting
Merging
Primitive Data Structures
Primitive Data Structures are the basic data structures that directly operate upon the machine
instructions. They have different representations on different computers. For example, integer,
character, and string are all primitive data types. Programmers can use these data types when creating
variables in their programs.
Non-primitive Data Structures
Non-primitive data structures are more complicated data structures and are derived from
primitive data structures.
Linear Data Structures
A data structure is called linear if all of its elements are arranged in the linear order. In
linear data structures, the elements are stored in non-hierarchical way where each
element has the successors and predecessors except the first and last element.
Types of Linear Data Structures are given below:
Arrays: An array is a collection of similar type of data items and each data item is
called an element of the array. The data type of the element may be any valid data
type like char, int, float or double. The elements of array share the same variable
name but each one carries a different index number known as subscript. The array
can be one dimensional, two dimensional or multidimensional.
Linked List: Linked list is a linear data structure which is used to maintain a list in
the memory. It can be seen as the collection of nodes stored at non-contiguous
memory locations. Each node of the list contains a pointer to its adjacent node.
Stack: Stack is a linear list in which insertion and deletions are allowed only at
one end, called top. A stack is an abstract data type (ADT), can be implemented in
most of the programming languages. It is named as stack because it behaves like
a real world stack, for example: - piles of plates or deck of cards etc.
Queue: Queue is a linear list in which elements can be inserted only at one end
called rear and deleted only at the other end called front. It is an abstract data
structure, similar to stack. Queue is opened at both end therefore it follows First-
In-First-Out (FIFO) methodology for storing the data items
This data structure does not form a sequence i.e. each item or element is connected with
two or more other items in a non-linear arrangement.
Trees: Trees are multilevel data structures with a hierarchical relationship among
its elements known as nodes. The bottommost nodes in the hierarchy are called
leaf node while the topmost node is called root node. Tree data structure is based
on the parent-child relationship among the nodes. Each ode contains pointers that
points to the child node. Each node in the tree can have more than one children
except the leaf nodes whereas each node can have at most one parent except the
root node. Trees can be classified into many categories which will be discussed
later.
Graphs: Graphs can be defined as the pictorial representation of the set of
elements (represented by vertices) connected by the links known as edges. A
graph is different from tree in the sense that a graph can have cycle while the tree
cannot have the one. Basically, primitive data types, as the name is self-
explanatory, are the data types that already come with the programming language
completely ready for the programmer’s use.
An algorithm is a procedure having well defined steps for solving a particular problem.
Algorithm is finite set of logic or instructions, written in order for accomplish the certain
predefined task. It is not the complete program or code, it is just a solution (logic) of a
problem, which can be represented either as an informal description using a Flowchart or
Pseudo code.
Characteristics of an Algorithm
From the data structure point of view, following are some important categories
of algorithms −
Introduction
A structured type of fundamental importance in almost every procedural programming
language is the array.
Array: A fixed length, ordered collection of values of the same type stored in contiguous
memory locations; the collection may be ordered in several dimensions. The values stored
in an array are called elements. (DEF1)
Array : Array is a container which can hold a fix number of items and these items should
be of the same type. Most of the data structures make use of arrays to implement their
algorithms. a segment of memory for element storage. Thereafter the array may shrink
or grow. If the array shrinks during execution, then only an initial portion of allocated
memory is used. But if the array grows beyond the space allocated for it, a more complex
reallocation procedure must occur, as follows:
A new segment of memory large enough to store the elements of the expanded
array is allocated.
All elements of the original (unexpanded) array are copied into the new
memory segment.
The memory used initially to store array values is freed and the newly allocated
memory is associated with the array variable or reference.
This reallocation procedure is computationally expensive, so systems are usually
designed to minimize its frequency of use. For example, when an array expands
beyond its memory allocation, its memory allocation might be doubled even if
space for only a single additional element is needed. The hope is that providing a
lot of extra space will avoid many expensive reallocation procedures if the array
expands slowly over time.
Dynamic arrays are convenient for programmers because they can never be too
small—
whenever more space is needed in a dynamic array, it can simply be expanded.
One drawback of dynamic arrays is that implementing language support for them
is more work for the compiler or interpreter writer. A potentially more serious
drawback is that the expansion procedure is expensive, so there are circumstances
when using a dynamic array can be dangerous. For example, if an application must
respond in real time to events in its environment, and a dynamic array must be
expanded when the application is in the midst of a response, then the response
may be delayed too long, causing problems
Basic Operations
Following are the basic operations supported by an array.
In some languages, the size of an array must be established once and for all at program
design time and cannot change during execution. Such arrays are called static arrays. A
chunk of memory big enough to hold all the values in the array is allocated when the array
is created, and thereafter elements are accessed using the fixed base location of the array.
Static arrays are the fundamental array type in older procedural languages, such as
Fortran, Basic, and C, and in many newer object-oriented languages as well, such as
Java. Some languages provide arrays whose sizes are established at run-time and can
change during execution. These dynamic arrays have an initial size used as the basis for
allocating
An array is a data structure that contains a group of elements. Typically these elements
are all of the same data type, such as an integer, float, character or string. Arrays are
commonly used in computer programs to organize data so that a related set of values can
be easily sorted or searched.
int no [3]={10,20,30};
float no [3]={1.5,2.7,8.9};
Definition
a unique identifier.
Initialization
Characteristics
Types of ARRAY :
Multi-Dimensional Arrays
A two-dimensional array is one in which two subscript values are used to access an array
element. They are useful when the elements being processed are to be arranged in rows
and columns (matrix form).
For example, in the statement int a[3][3], an integer array of three rows and three columns
is declared. Once a compiler reads a two-dimensional array declaration, it allocates a
specific amount of memory for this array.
Array is a linear data structure that is a collection of similar data types. Arrays are stored
in contiguous memory locations. It is a static data structure with a fixed size. It combines
data of similar types.
ARRAY
Applications of Array Data Structure:
Data structures like trees also sometimes use the array implementation since
arrays are easier to handle than pointers. For example, a segment tree uses array
implementation.
Binary search trees and balanced binary trees are used in data structures such as
a heap, map, and set, which can be built using arrays.
Storing collections of data: Arrays are often used to store collections of data of
the same type. For example, an array of integers can be used to store a set of
numerical values.
Implementing matrices and tables: Arrays can be used to implement matrices
and tables. For example, a two-dimensional array can be used to store a matrix of
numerical values.
Sorting and searching: Arrays are often used for sorting and searching data. For
example, the Arrays class in Java provides methods like sort() and binarySearch()
to sort and search elements in an array.
Implementing data structures: Arrays are used as the underlying data structure
for several other data structures like stacks, queues, and heaps. For example, an
array-based implementation of a stack can be used to store elements in the stack.
Image processing: Arrays are commonly used to store the pixel values of an
image. For example, a two-dimensional array can be used to store the RGB values
of an image.
Applications of Array in C#:
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.
Disadvantages of array data structure:
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.
Advantages of Structure over Array:
The structure can store different types of data whereas an array can only store
similar data types.
Structure does not have limited size like an array.
Structure elements may or may not be stored in contiguous locations but array
elements are stored in contiguous locations.
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:
0 0 3 0 4
0 0 5 7 0
0 0 0 0 0
02600
Sparse Matrix Representations can be done in many ways following are two common
representations:
1. Array representation
2D array is used to represent a sparse matrix in which there are three rows named as
Row: Index of row, where non-zero element is located
# using arrays
sparseMatrix = [[0,0,3,0,4],[0,0,5,7,0],[0,0,0,0,0],[0,2,6,0,0]]
# initialize size as 0
size = 0
for i in range(4):
for j in range(5):
if (sparseMatrix[i][j] != 0):
size += 1
k=0
for i in range(4):
for j in range(5):
if (sparseMatrix[i][j] != 0):
compactMatrix[0][k] = i
compactMatrix[1][k] = j
compactMatrix[2][k] = sparseMatrix[i][j]
k += 1
for i in compactMatrix:
print(i)
Output
001133
242312
345726
Time Complexity: O(NM), where N is the number of rows in the sparse matrix, and M is
the number of columns in the sparse matrix.
Auxiliary Space: O(NM), where N is the number of rows in the sparse matrix, and M is
the number of columns in the sparse matrix.
Method 2: Using Linked Lists
In linked list, each node has four fields. These four fields are defined as:
Row: Index of row, where non-zero element is located
#include<iostream>
using namespace std;
{
public:
int row;
int col;
int data;
Node *next;
};
{
Node *temp = *p;
Node *r;
{
temp = new Node();
temp->row = row_index;
temp->col = col_index;
temp->data = x;
temp->next = NULL;
*p = temp;
{
while (temp->next != NULL)
temp = temp->next;
r = new Node();
r->row = row_index;
r->col = col_index;
r->data = x;
r->next = NULL;
temp->next = r;
ptr = ptr->next;
}
ptr = ptr->next;
ptr = start;
ptr = ptr->next;
// Driver Code
int main()
{ 0 , 0 , 5 , 7 , 0 },
{ 0 , 0 , 0 , 0 , 0 },
{ 0 , 2 , 6 , 0 , 0 } };
// Creating head/first node of list as NULL
{
for(int j = 0; j < 5; j++)
create_new_node(&first, i, j,
sparseMatrix[i][j]);
printList(first);
return 0;
Output
row_position:0 0 1 1 3 3
column_position:2 4 2 3 1 2
Value:3 4 5 7 2 6
Time Complexity: O(N*M), where N is the number of rows in the sparse matrix, and M
is the number of columns in the sparse matrix.
Auxiliary Space: O(K), where K is the number of non-zero elements in the array.
Other representations:
As a Dictionary where row and column numbers are used as keys and values are matrix
entries. This method saves space but sequential access of items is costly.
As a list of list. The idea is to make a list of rows and every item of list contains values.
We can keep list items sorted by column numbers.
Strings are a collection of Unicode characters that are used specifically in Python. A string
is the language or text we use to communicate. However, machines can only read binary,
they cannot comprehend text. As a result, ASCII numbers are initially generated from
characters before being transformed to binary representation. However, ASCII is
exclusively used for the English language, and when programming languages are used in
other nations, they are translated to 16-bit characters known as Unicode.
Creating Strings
Python offers several methods for creating strings, including single, double, and triple
quotes. When constructing a string, if the string contains any kind of quote, you should
use a separate kind of quote in the declaration to prevent an error. Try the example below
in any Python IDE or code editor for a better understanding.
4
5
10
s = 'Good Morning'
#single quotes
s = 'It'
s been a great day ' #syntax error (It confuses where string starts and ends)
#correct way
s = ''
'Good Morning'
''
When declaring or referencing a lengthy paragraph use triple quotes. Any other data type
can be declared as a string or converted using an internal function.
a = 99 #int
Indexing
Indexing a string with zeros is the same as indexing an array. Simply put, the zero indexes
in a string represent the first character, and as the index rises, the string advances.
Positive and negative indexing are the two categories of indexing in Python.
Positive Indexing
Positive indexing is moving from zero to the end length of a string. It is also known as
zero-based indexing.
4
5
s = 'GoodMorning'
print(s[0])
print(s[2])
print(s[4])
print(s[len(s) - 1])
print(len(s))
output:
1
2
11
Note: If access s(11), program returns a Index error
Negative Indexing
Accessing string elements using a negative index means going backward in time. It
usually takes a long time to access the last character in a string if we use positive indexing
since we have to calculate the length of the string and then take one off of it. In this
situation, we can just use the negative indexing.
1
s = 'GoodMorning'
print(s[-1])
print(s[-2])
output:
n
Slicing
The concept of slicing, which is accomplished using the colon operator, comes into play
when we have long strings, such as sentences or paragraphs, and we want to access
specific pieces, like whole words, but we cannot do so merely using indexing. To ensure
that the output is a portion of the string from a start index to an end index less one, we
must specify the starting index and end index in square brackets that are separated by
colons.
1
2
s = 'Good Morning'
print(s[1: 4])
output:
1
ood
Start with the first index in the example above and retrieve the character all the way to the
third index. Now that there are various slicing variations, let’s examine them practically
using the code sample below.
6
s = 'GoodMorning'
print(s[1: ])
print(s[: 4])
print(s[: ])
print(s[0: 8: 3])
print(s[::-1])
output:
3
4
oodMorning #It will print the entire string starting from index 1
Good #It will print the entire string starting from index 0 to 3
Gdr #It will print zero index and skip two index and take third index till eighth index
Since strings in Python are immutable data types, attempting to change one will result in
a type error. The existing string cannot be changed or added to, but it can be assigned to
a separate variable to produce a new string. Let’s examine the practical effects of deleting
and altering strings to see whether we experience any errors.
Since strings are immutable, we cannot directly reassign the value of the particular string
in the below snippet.
s = 'GoodMorning'
s[0] = "M"
s = 'GoodMorning'
a = "M" + s[1: ]
print(a)
output:
MoodMorning
Deleting the strings won’t work well and the below statements will generate errors saying
we can’t delete the string.
del s[0]
del s[: 3: 2]
Arithmetic Operations
Addition(+)
Concatenation, as it is also known, allows us to link any amount of strings by using the
addition operator.
Good - Morning
Multiplication(*)
To repeat a certain string before the multiplication operator any number of times is known
as string repetition. When we want a pattern, we use it.
s = "Good"
print(s * 3)
output:
GoodGoodGood
Relational Operations
Relational operators display the relationship between two terms and determine whether a
particular condition is true or false, producing a boolean result.
2
print("Hello" == "World")
print("Hello" != "World")
output:
False
True
When we apply the larger than and less than operators the situation becomes more
intriguing.
1
2
False
True
Logical Operations
When you use the AND, OR, and NOT logical operators on strings, Python returns false
for empty strings and true for non-empty strings.
5
6
print("Hello"
print(""
print(""
Slicing allows us to loop over strings starting at any index. To access distinct characters
or create a pattern in a string, we can use both a where and a for loop.
1
s = 'GoodMorning'
for i in s:
print(i)
output:
8
9
10
11
M
o
Membership Operators
Python’s in and NOT in membership operators are used to determine whether any
element is present in any sequence data structure.
s = 'GoodMorning'
if 'M' in s:
print('true')
output:
true
The most important parts of a string are string functions which you use everywhere
whenever you build any project. Now we will study all-important string functions which are
frequently used.
Common Functions:
These are the same functions that are available for all other iterator data types, including
list, tuple, set, and string.
s = 'GoodMorning'
print(len(s))
print(min(s))
print(max(s))
print(sorted(s))
3
4
11
['G', 'M', 'd', 'g', 'i', 'n', 'n', 'o', 'o', 'o', 'r']
['r', 'o', 'o', 'o', 'n', 'n', 'i', 'g', 'd', 'M', 'G']
The title function capitalizes the first letter of each word, whereas the capitalize function
capitalizes the first letter of a string.
2
3
In string each character is converted in the upper function to lowercase characters. Swap
case changes an upper to a lower case character, and vice versa.
3
4
It provides the number of any substrings that are present in a string. It outputs 0 if the
substring is missing from the string. It is used to determine how often a given substring
occurs in a string.
print(s.count("i")) #3
print(s.count("ing")) # 1
Find / Index
Both functions operate in exactly the same manner. Both locate the position of a substring
within a string. The sole distinction between the two functions is that while index gives an
error when the substring is absent, string find produces a negative value when this occurs.
print(s.find("ing")) #8
print(s.index("ing")) # 8
print(s.find("down")) # - 1
print(s.index("down")) #error
Unit 2 : Linked List
2.1 Introduction :
A list or sequence is an abstract data type that represents a countable number of ordered
values, where the same value may occur more than once. A linked list is a sequence of data
structures, which are held together by links. A Linked List is a sequence of links which
contains items. Each link contains a connection to another link. A Linked list is the second
most-used data structure after an array.
Linked List
Link − Each link of a linked list can store a data called an element.
Next − Each link of a linked list contains a link to the next link called Next.
LinkedList − A Linked List contains the connection link to the first link called First.
Node Creation
When creating a new node in a linked list, memory is allocated dynamically using heap memory. This
allocation process involves reserving a continuous block of memory in heap that is large enough to store
two key components:
1. The data element of the node
2. A pointer to the next node
For example, if we’re creating a linked list of alphabets/characters, each node would typically require
memory for:
An ASCII character value (usually 4 bytes)
A pointer to the next node (usually 4 or 8 bytes, depending on the system architecture. Let’s assume that
this is a 32 bit system and 4 bits are required for storing pointer/reference address to the next node).
When a new node is created, the system allocates this continuous block of memory (in this case, 8 bytes or
two slots of memory) on the heap. Initially, the address of the next node is set to null. In memory
terms, null is represented by an invalid memory address (often 0x0 or 0), indicating that the current node is
not pointing to any other node yet. Below is how the newly created node looks like in memory:
# Node class to store data and link to the next node
class Node:
def __init__(self, data):
# Initialize the node with given data
self.data = data
# Set the next pointer to None initially
self.next = None
Linking Nodes
In the previous step, we looked at how we can allocate and fill memory required for each node. Now we
can start connecting them to form a linked list. This process involves setting the “next” pointer of each node
to the memory address of the subsequent node in the list. Let’s first connect node As next pointer to node Bs
address by using the expression node1.next = address of node2:
Now, address of A (103) is stored as the head node of the linked list. Using this address we can access all
the linked list nodes and perform all linked list operations. In the memory layout, although the sequence of
linked list nodes is D, A, B, C, since we start from head node address (103), the sequence depends on how
each node is linked, irrespective of how those nodes are arranged in the memory slots.
Below is how we programmatically link linked list nodes:
node1.next = node2 # A->B
node2.next = node3 # B->C
node3.next = node4 # C->A
class Node:
if self.head is None:
self.head = new_node
return
else:
new_node.next = self.head
self.head = new_node
if position == index:
self.insertAtBegin(data)
else:
while(current_node != None and position+1 != index):
position = position+1
current_node = current_node.next
if current_node != None:
new_node.next = current_node.next
current_node.next = new_node
else:
print("Index not present")
self.head = new_node
return
current_node = self.head
while(current_node.next):
current_node = current_node.next
current_node.next = new_node
if position == index:
current_node.data = val
else:
while(current_node != None and position != index):
position = position+1
current_node = current_node.next
if current_node != None:
current_node.data = val
else:
print("Index not present")
Delete Node in a Linked List
Remove First Node from Linked List
This method removes the first node of the linked list simply by making thesecond node head
of the linked list.
def remove_first_node(self):
if(self.head == None):
return
self.head = self.head.next
def remove_last_node(self):
if self.head is None:
return
current_node = self.head
while(current_node.next.next):
current_node = current_node.next
Deletecurrent_node.next = None
a Linked List Node at a given Position
In this method, we will remove the node at the given index, this method is similar to the
insert_at_inded() method. In this method, if the head is None we simply return else
We initializea current_node with self.head and position with 0. If the position is equal to the
index we called the remove_first_node() method else we traverse to the one node before that
we want to remove using the while loop. After that whenwe out of the while loop we check
that current_node is equal to None if not then we make the next of current_node equal to the
next of node that we wantto remove else we print the message “Index not
present” because current_node is equal to None. if not
then we make the next of current_node equal to the next of node that we wantto remove
else we print the message “Index not present” because current_node is equal
to None.
if self.head == None:
return
current_node = self.head
position = 0
if position == index:
self.remove_first_node()
else:
while(current_node != None and position+1 != index):
position = position+1
current_node = current_node.next
if current_node != None:
Delete a Linkedcurrent_node.next
List Node of a given Data
= current_node.next.next
else:
This method removes the node with the given data from the linked list. In this method, firstly we
made a current_node equal to the head
print("Index and run a while loop to
not present") traverse the linked
list. This while loop breaks when current_node becomes None or the data next to the
current node is equal to the data given in the argument. Now, After coming out of the loop if the
current_node is equal to None it means that the node is not present in the data and we just return,
and if the data next to the current_node is equal to the data given then we remove that node by
making next of that removed_node to the next of current_node. And this is implemented using the
if else condition.
def remove_node(self, data):
current_node = self.head
if current_node == None:
return
else:
current_node.next = current_node.next.next
Linked List Traversal in Python
This method traverses the linked list and prints the data of each node. In this
method, we made a current_node equal to the head and iterate through the
linked list using a while loop until the current_node become None and print
the data of current_node in each iteration and make the current_node next
toit.
def printLL(self):
current_node = self.head
while(current_node):
print(current_node.data)
current_node = current_node.next
def sizeOfLL(self):
size = 0
if(self.head):
current_node = self.head
while(current_node):
size = size+1
current_node = current_node.next
return size
else:
return 0
Example of the Linked list in Python
In this example, After defining the Node and LinkedList class we have created a linked list named
“llist” using the linked list class and then insert four nodeswith character data ‘a’, ‘b’, ‘c’, ‘d’ and
‘g’ in the linked list then we print the linked list using printLL() method linked list class after that
we have removed some nodes using remove methods and then print the linked list again and wecan
see in the output that node is deleted successfully. After that, we also print the size of the linked list.
Python3
class LinkedList:
if self.head is None:
self.head = new_node
return
else:
new_node.next = self.head
self.head = new_node
else:
while(current_node != None and position+1 !=
index):position = position+1
current_node = current_node.next
else:
print("Index not present") # Method to add a
current_node = self.head
while(current_node.next): current_node =
current_node.next
linked list
# at given position
def updateNode(self, val, index):current_node =
self.head position = 0
else:
while(current_node != None and position !=
index):position = position+1
current_node = current_node.next
else:
print("Index not present")
def remove_first_node(self):
if(self.head == None):
return
self.head = self.head.next
if self.head is None:
return
current_node = self.head
while(current_node.next.next): current_node =
current_node.next
current_node.next = None
current_node = self.headposition = 0
else:
while(current_node != None and position+1 !=
index):position = position+1
current_node = current_node.next
if current_node != None:
current_node.next = current_node.next.next
else:
print("Index not present")
if current_node == None:
returnelse:
current_node.next = current_node.next.next
current_node = self.head
while(current_node):size = size+1
current_node = current_node.next
return size
else:
return 0
print(current_node.data) current_node =
current_node.next
a
g
b
d
b
Size of linked list : 2
What is Circular Linked List?
A circular linked list is a type of data structure where each node points to the next node, and the last
node points back to the first, forming a circle.
This setup allows for an endless cycle of node traversal, which can be particularly useful in
applications where the end of the list naturally reconnects to the beginning.
For example, in computer applications that manage resources in a loop or in creating playlists where
the last song leads back to the first song, a circular linked list offers a seamless way to cycle through
items without the need for complex logic to manage the end of the list.
Use Cases: Suitable for applications where the list needs to be traversed in only one direction, such as
implementing a round-robin scheduler or a cyclic queue where entries are processed in a continuous
loop.
Advantages: Allows both forward and backward traversal, making it easier to navigate and modify the
list. Useful for more complex data structures where nodes might need to be removed or added
frequently and from different positions.
Use Cases: Ideal for applications requiring bidirectional traversal, such as a playlist that allows
playing the next or previous media file, or implementing advanced navigation systems in software
applications.
Insertion at a node
def __repr__(self):
string = ""
if(self.head == None):
string += "Circular Linked List Empty"
return string
if self.head == None:
self.head = Node(data)
self.count += 1
return
temp = self.head
for _ in range(self.count - 1 if index - 1 == -1 else index - 1):
temp = temp.next
if self.count == 1:
self.head = None
self.count = 0
return
before = self.head
for _ in range(self.count - 1 if index - 1 == -1 else index - 1):
before = before.next
after = before.next.next
before.next = after
if(index == 0):
self.head = after
self.count -= 1
return
def size(self):
return self.count
def display(self):
print(self)
Applications:
Circular linked lists find applications in various scenarios, including:
1. Implementing a playlist in media players.
2. Managing processes in operating systems.
3. Simulation of round-robin scheduling algorithms.
4. Computer Networking: Circular linked lists are used in networking, particularly in
implementing algorithms for token ring networks where each computer on a network is given
a chance to transmit data in a pre-defined, circular order.
5. Operating Systems: Many operating systems use circular linked lists for various
management tasks, including scheduling algorithms like round-robin scheduling. This method
cycles through processes, allocating CPU time one by one in a fair and cyclic manner.
6. Multiplayer Games: In games that involve multiple players, circular linked lists can manage
player turns in a loop. After the last player takes a turn, the system automatically goes back to
the first player.
7. Music Players: Media players often use circular linked lists to manage playlists, especially in
'repeat' mode. When the playlist reaches the end, it loops back to the beginning seamlessly.
8. User Interface: Circular linked lists are used in applications with a circular carousel of items,
such as image sliders or navigation menus that loop back to the beginning after reaching the
end.
9. Resource Allocation: They are also used in managing resources in an environment where the
resources need to be used cyclically, such as allocating CPU or memory resources in a
circular manner to various processes.
Doubly Linked List
A doubly linked list is a type of data structure where each element, known as a node, holds data and
two references: one pointing to the next node and one pointing to the previous node. This two-way
linking allows for movement in both directions through the list, making it more versatile than a singly
linked list which only allows movement in one direction.
The importance of doubly linked lists in data structures and algorithms (DSA) lies in their ability to
facilitate quicker and more flexible modifications—such as insertions and deletions—not just at the
ends of the list, but also in the middle.
Example of Doubly Linked List
The doubly linked list in data structure is particularly beneficial in applications where the ability to
navigate backwards is as necessary as moving forwards.
For example, in navigation systems like web browsers, a doubly linked list can manage the user's
history, allowing easy forward (redo) and backward (undo) movements through previously visited
pages.
This example illustrates how doubly linked lists support complex sequence data management in
practical software solutions, enhancing user interface responsiveness and functionality.
Structure of a Doubly Linked List
Node:
Each node in a doubly linked list includes:
Data: The actual information held within the node, which could be numbers, strings, or any
other data type.
Next Pointer: A reference to the next node in the list, which helps in traversing the list forward.
Prev Pointer: A reference to the previous node in the list, which facilitates backward traversal.
How It Works?
Head and Tail: The list has two special nodes, the head and the tail. The head points to the first
element of the list, while the tail points to the last element. These pointers enhance operations
such as adding or removing elements at the ends of the list.
Traversal: You can start from the head to move forward through the list or from the tail to move
backward, making it versatile for various operations that require scanning the list in either
direction.
Insertions and Deletions: Adding or removing nodes can be done efficiently at any point in the
list. When inserting or deleting a node, pointers from adjacent nodes are updated to maintain
the list’s integrity without needing to shift elements as in contiguous data structures
like array
class Node:
def __init__(self, data):
self.item = data
self.next = None
self.prev = None
The item variable will store the actual element of the node. The next stores the address to the
next node, while prev stores the address to the previous node in the doubly linked list.
In the next step, we will create a doublyLinkedList class, that contains different functions to
insert, delete and display elements of doubly linked list.
class doublyLinkedList:
def __init__(self):
self.start_node = None
Only forwards. You can Both forwards and backwards. You can
Direction of Traversal traverse from the head to the traverse from the head to the tail and vice
end of the list. versa.
Less memory per node (one More memory per node (two pointers per
Memory Usage
pointer per node). node).
Simpler and requires less code More complex due to additional pointers,
Complexity to manage compared to doubly requiring more management and care in
linked lists. code.
2. Deletion
Removing nodes requires adjustment of pointers from both the preceding and succeeding nodes:
From the Beginning: Remove the head node by updating the head to the second node and
setting the prev pointer of the new head to null.
From the End: Remove the tail node by setting the next pointer of the second last node to null
and updating the tail to this second last node.
A Specific Node: Disconnect the node by adjusting the next pointer of the preceding node to
point to the node after the target node, and adjust the prev pointer of the succeeding node
likewise.
5. Update
Updating the value of a node can be performed directly once the node is accessed, without any
specific need for traversal if the node reference is already known.
Applications
A music player that has next and previous buttons, like the concept of doubly linked list you’ll
have functionality to go back and the next element. .
Undo Redo operations that we discussed at the beginning of the article.
The browser cache functionality in common web browsers like Chrome, Microsoft edge, which
allows us to move front and back within pages is also a great application of a doubly linked
list.
LRU cache, also Most Recently Used is also an instance of DLL.
A deck of cards in a game is a perfect example of the applicability of DLL. It is additionally
utilized to store the state of the game during play.