Data Structures (R220503)
Data Structures (R220503)
ON
DATA STRUCTURES
(R22A0503)
B.TECH II YEAR–I-SEM
(R22 Regulation)
(2024-2025)
Prepared By
Dr. N. Satheesh Kumar
Associate Professor
MALLAREDDYCOLLEGEOFENGINEERING&TECHNOLOGY
(Autonomous Institution– UGC, Govt. of India)
Recognized under2(f) and 12(B) of UGC ACT 1956
(Affiliated to JNTUH, Hyderabad, Approved by AICTE- Accredited by NBA & NAAC –‘A’ Grade-
ISO9001:2015Certified)
Maisammaguda, Dhulapally(PostVia.Hakimpet), Secunderabad–500100,Telangana,India
DEPARTMENT OF COMPUTER SCIENCE AND ENGINEERING
Vision
To acknowledge quality education and instill high patterns of discipline making the
students technologically superior and ethically strong which involves the
improvement in the quality of life in human race.
Mission
To achieve and impart holistic technical education using the best of infrastructure,
outstanding technical and teaching expertise to establish the students into competent
and confident engineers.
Evolving the center of excellence through creative and innovative teaching learning
practicesforpromotingacademicachievementtoproduceinternationallyacceptedcompetiti
veand world class professionals.
PROGRAMME EDUCATIONAL OBJECTIVES (PEOs)
PEO1–ANALYTICALSKILLS
To facilitate the graduates with the ability to visualize, gather information, articulate, analyze,
solve complex problems, and make decisions. These are essential to address the challenges of
complex and computation intensive problems increasing their productivity.
PEO2–TECHNICALSKILLS
Tofacilitatethegraduateswiththetechnicalskillsthatpreparethemforimmediateemploymentandpurs
ue certification providing a deeper understanding of the technology in advanced areas of
computer science and related fields, thus encouraging pursuing higher education and research
based on their interest.
PEO3–SOFTSKILLS
To facilitate the graduates with the soft skills that include fulfilling the mission, setting goals,
showing self confidence by communicating effectively, having a positive attitude, get
involved in team-work, being a leader, managing their career and their life.
PEO4–PROFESSIONALETHICS
To facilitate the graduates with the knowledge of professional and ethical responsibilities by
paying attention to grooming, being conservative with style, following dress codes, safety
codes, and adapting them to technological advancements.
1. FundamentalsandcriticalknowledgeoftheComputerSystem:-
AbletoUnderstandtheworkingprinciples of the computer System and its components, Apply
the knowledge to build, asses, and analyze the software and hardware aspects of it.
3. Applications of Computing Domain & Research: Able to use the professional, managerial,
interdisciplinary skill set, and domain specific tools in development processes, identify their
search gaps, and provide innovative solutions to them.
PROGRAM OUTCOMES (POs)
Engineering Graduates should possess the following:
5. Modern tool usage: Create, select, and apply appropriate techniques, resources, and modern
engineering and IT tools including prediction and modeling to complex engineering activities
with an understanding of the limitations.
6. The engineer and society: Apply reasoning informed by the contextual knowledge to assess
societal, health, safety, legal and cultural issues and the consequent responsibilities relevant to
the professional engineering practice.
8. Ethics: Apply ethical principles and commit to professional ethics and responsibilities and
norms of the engineering practice.
9. Individual and team work: Function effectively as an individual, and as member or leader in
diverse teams, and in multidisciplinary settings.
11. Project management and finance: Demonstrate knowledge and understanding of the
engineering and management principles and apply these to one’s own work, as a member and
leader in a team, to manage projects and in multidisciplinary environments.
12. Life-long learning: Recognize the need for, and have the preparation and ability to engage
in independent and life-long learning in the broadest context of technological change.
SYLLABUS
MALLAREDDY COLLEGE OF ENGINEERING ANDTECHNOLOGY
UNIT – I
Oops Concepts- class, object, constructors, types of variables, types of methods. Inheritance:
single, multiple, multi-level, hierarchical, hybrid, Polymorphism: with functions and objects,
with class methods, with inheritance, Abstraction: abstract classes.
UNIT – II
Searching- Linear Search and Binary Search.
Sorting - Bubble Sort, Selection Sort, Insertion Sort, Merge Sort, Quick Sort.
UNIT -III
Data Structures –Definition, Linear Data Structures, Non-Linear Data Structures,
Stacks - Overview of Stack, Implementation of Stack (List), Applications of Stack
Queues: Overview of Queue, Implementation of Queue (List), Applications of Queues, Priority
Queues
Linked Lists – Implementation of Singly Linked Lists, Doubly Linked Lists, Circular Linked Lists.
Implementation of Stack and Queue using Linked list.
UNIT -IV
Dictionaries: linear list representation, skip list representation, operations - insertion, deletion
and searching. Directed vs Undirected Graphs, Weighted vs Unweighted Graphs,
Representations, Breadth First Search, Depth First Search.
UNIT -V
Trees - Overview of Trees, Tree Terminology, Binary Trees: Introduction, Implementation,
Applications. Tree Traversals
Binary Search Trees: Introduction, Implementation
AVL Trees: Introduction, Rotations, Implementation, B-Trees and B+ Trees.
TEXTBOOKS:
1. Data structures and algorithms in python by Michael T. Goodrich
2. Data Structures and Algorithmic Thinking with Python by Narasimha Karumanchi
REFERENCE BOOKS:
1. Hands-On Data Structures and Algorithms with Python: Write complex and powerful code
using the latest features of Python 3.7, 2nd Edition by Dr. Basant Agarwal, Benjamin Baka.
2. Data Structures and Algorithms with Python by Kent D. Lee and Steve Hubbard.
3. Problem Solving with Algorithms and Data Structures Using Python by Bradley N Miller and
David L.Ranum.
4. Core Python Programming -Second Edition, R. Nageswara Rao, Dreamtech Press
COURSE OUTCOMES:
The students should be able to:
1. Examine Python syntax and semantics and apply Python flow control and functions.
2. Create, run and manipulate Python Programs using core data structures like Lists,
3. Apply Dictionaries and use Regular Expressions.
4. Interpret the concepts of Object-Oriented Programming as used in Python.
INDEX
4 IV DICTIONARIES, GRAPHS 53 – 71
UNIT – I
1. INTRODUCTION:
Python is a high-level programming language that is translated by the python interpreter. As is
known, an interpreter works by translating line-by-line and executing. It was developed by
Guido-van-rossum in 1990, at the National Research Institute for Mathematics and Computer
Science in Netherlands. Python doesn’t refer to the snake but was named after the famous
British comedy troupe, Monty Python’s Flying Circus.
Python is Interpreted: Python is processed at runtime by the interpreter. You do not need
to compile your program before executing it.
Python is Interactive: we can actually sit at a Python prompt and interact with the interpreter
directly to write our programs.
Python is Object-Oriented: Python supports Object-Oriented style or technique of
programming that encapsulates code within objects.
Application of python used in Search engine. In mission critical projects in Naza, in processing
financial transaction at New york stock Exchange.
The following are some of the features of Python:
Python is an Open Source: It is freely downloadable, from the link “http:// python.org/”
Python is portable: It runs on different operating systems / platforms3
Python has automatic memory management
Python is flexible with both procedural oriented and object oriented programming
Python is easy to learn, read and maintain
Python is Extendable. You can add low-level modules to the Python is Interpreted.
These modules enable programmers to add to or customize their tools to be more
efficient.
Python supports GUI applications that can be created and ported to many system calls,
libraries and windows systems, such as Windows MFC, Macintosh, and the X Window
system of Unix.
IT supports automatic garbage collection.
It can be easily integrated with C, C++, COM, ActiveX, CORBA, and Java.
It supports functional and structured programming methods as well as OOP.
Data Structures A. Y. 2024 - 2025
Points to Remember:
Case sensitive : Example - In case of print statement use only lower case and not upper case
Punctuation is not required at end of the statement
In case of string use single or double quotes i.e. ‘ ’ or “ ”
Must use proper indentation.
The following are used Without Indentation
o Special characters like (,),# etc. are used
o () ->Used in opening and closing parameters of functions
o #-> The Pound sign is used to comment a line
2. OOPS CONCEPTS
An object-oriented paradigm is to design the program using classes and objects. The object is
related to real-word entities such as book, house, pencil, etc. The oops concept focuses on
writing the reusable code. It is a widespread technique to solve the problem by creating
objects. The main concept of object-oriented Programming (OOPs) or oops concepts in Python
is to bind the data and the functions that work together as a single unit so that no other part of
the code can access this data which includes Class, Objects, Polymorphism, Encapsulation,
Inheritance and Data Abstraction.
I. CLASS:
A class is a collection of objects. A class contains the blueprints or the prototype from which
the objects are being created. It is a logical entity that contains some attributes and methods.
Classes are created by keyword class.
Attributes are the variables that belong to a class.
Attributes are always public and can be accessed using the dot (.) operator.
Syntax:
class ClassName:
# Statement-1
.
.
# Statement-N
Dept. of CSE 2
Data Structures A. Y. 2024 - 2025
II. OBJECT:
In object-oriented programming Python, The object is an entity that has a state and behavior
associated with it. It may be any real-world object like a mouse, keyboard, chair, table, pen,
etc. Integers, strings, floating-point numbers, even arrays, and dictionaries, are all objects.
An object consists of:
State: It is represented by the attributes of an object. It also reflects the properties of
an object.
Behavior: It is represented by the methods of an object. It also reflects the response of
an object to other objects.
Identity: It gives a unique name to an object and enables one object to interact with
other objects.
All classes have a function called __init__(), which is always executed when the class is being
initiated. Use the __init__() function to assign values to object properties, or other operations
that are necessary to do when the object is being created:
Create a class named Person, use the __init__() function to assign values for name and age:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("John", 36)
print(p1.name)
print(p1.age)
Note: The __init__() function is called automatically every time the class is being used to create
a new object.
III. METHOD:
The method is a function that is associated with an object. In Python, a method is not unique to
class instances. Any object type can have methods.
Dept. of CSE 3
Data Structures A. Y. 2024 - 2025
IV. INHERITANCE:
Inheritance is a way of creating a new class for using details of an existing class without
modifying it. The newly formed class is a derived class (or child class). Similarly, the existing
class is a base class (or parent class).
Inheritance is the most important aspect of object-oriented programming, which simulates the
real-world concept of inheritance. It specifies that the child object acquires all the properties
and behaviors of the parent object. By using inheritance, we can create a class which uses all
the properties and behavior of another class.
The benefits of inheritance are:
It represents real-world relationships well.
It provides the reusability of a code. We don’t have to write the same code again and
again. Also, it allows us to add more features to a class without modifying it.
It is transitive in nature, which means that if class B inherits from another class A, then
all the subclasses of B would automatically inherit from class A.
V. POLYMORPHISM:
Polymorphism contains two words "poly" and "morphs". Poly means many, and morph means
shape. By polymorphism, we understand that one task can be performed in different ways. For
example, we need to determine if the given species of birds fly or not, using polymorphism we
can do this using a single function.
VI. ENCAPSULATION:
It describes the idea of wrapping data and the methods that work on data within one unit. This
puts restrictions on accessing variables and methods directly and can prevent the accidental
modification of data. To prevent accidental change, an object’s variable can only be changed by
an object’s method. Those types of variables are known as private variables. A class is an
example of encapsulation as it encapsulates all the data that is member functions, variables,
etc.
Dept. of CSE 4
Data Structures A. Y. 2024 - 2025
Example 2:
# This program adds two numbers
num1 = 1.5
Dept. of CSE 5
Data Structures A. Y. 2024 - 2025
num2 = 6.3
# Add two numbers
sum = num1 + num2
# Display the sum
print('The sum of {0} and {1} is {2}'.format(num1, num2, sum))
Output : The sum of 1.5 and 6.3 is 7.8
Example 3:
# Store input numbers
num1 = input('Enter first number: ')
num2 = input('Enter second number: ')
# Add two numbers
sum = float(num1) + float(num2)
# Display the sum
print('The sum of {0} and {1} is {2}'.format(num1, num2, sum))
Output:
Enter first number: 1.5
Enter second number: 6.3
The sum of 1.5 and 6.3 is 7.8
4. CONSTRUCTORS
A constructor is an instance method in a class, that is automatically called whenever a new
object of the class is created. The constructor's role is to assign value to instance variables as
soon as the object is declared.
Python uses a special method called __init__() to initialize the instance variables for the object,
as soon as it is declared. The “__init__()” method acts as a constructor. It needs a mandatory
argument self, which is the reference to the object.
Syntax
def __init__(self): #initialize instance variables
The __init__() method as well as any instance method in a class has a mandatory parameter,
self. However, you can give any name to the first parameter, not necessarily self.
Ex:
class Employee:
'Common base class for all employees'
def __init__(self):
self.name = "Bhavana"
self.age = 24
e1 = Employee()
Dept. of CSE 6
Data Structures A. Y. 2024 - 2025
Parameterized Constructor
For the above Employee class, each object we declare will have same value for its instance
variables name and age. To declare objects with varying attributes instead of the default,
define arguments for the __init__() method. (A method is nothing but a function defined inside
a class.)
Ex: The __init__() constructor has two formal arguments. We declare Employee objects with
different values –
class Employee:
'Common base class for all employees'
def __init__(self, name, age):
self.name = name
self.age = age
e1 = Employee("Bhavana", 24)
e2 = Employee("Bharat", 25)
print ("Name: {}".format(e1.name))
print ("age: {}".format(e1.age))
print ("Name: {}".format(e2.name))
print ("age: {}".format(e2.age))
Output:
Name: Bhavana
age: 24
Name: Bharat
age: 25
Non-parameterized Constructor
On the opposite spectrum, we have non-parameterized constructors. These don’t take any
parameters, ensuring that every object has a standardized structure by allocating default
values. The highlight of non-parameterized constructors is their simplicity. When an object
comes to life, it gets furnished with predefined values, ensuring uniformity and consistency.
Dept. of CSE 7
Data Structures A. Y. 2024 - 2025
5. TYPES OF VARIABLES
Scope refers to the region of a program where a variable is defined and can be accessed. In
Python, there are three types of variable scopes: global, local, and nonlocal.
Global Variables - Global variables are defined outside any function or class and can be
accessed from anywhere within the program. They have a global scope, meaning they are
visible to all functions and classes. Global variables are useful when you want to share data
between different parts of your program.
Ex:
count = 0
def increment():
global count
count += 1
increment()
print(count)
Output : 1
Local Variables - Local variables are defined within a function or a block of code and can only
be accessed within that specific function or block. They have a local scope, meaning they are
only visible within the function or block where they are defined. Local variables are temporary
and are destroyed once the function or block of code is executed.
Ex:
def calculate_sum(a, b):
result = a + b
return result
sum = calculate_sum(5, 10)
print(sum)
Output : 15
Nonlocal Variables - Nonlocal variables are used in nested functions, where a function is
defined inside another function. They are neither global nor local variables. Nonlocal variables
can be accessed and modified by the inner function, as well as the outer function that encloses
it.
Ex;
def outer_function():
x = 10
def inner_function():
nonlocal x
x += 5
print(x)
# Output: 15
Dept. of CSE 8
Data Structures A. Y. 2024 - 2025
inner_function()
outer_function()
Output : 15
Identifier :
Identifier is a name given to various programming elements such as a variable, function,
class, module or any other object. Following are the rules to create an identifier.
The allowed characters are a-z, A-Z, 0-9 and underscore (_)
It should begin with an alphabet or underscore
It should not be a keyword
It is case sensitive
No blank spaces are allowed.
It can be of any size
Valid identifiers examples : si, rate_of_interest, student1, ageStudent
Invalid identifier examples : rate of interest, 1student, @age
Keywords
Keywords are the identifiers which have a specific meaning in python, there are 33
keywords in python. These may vary from version to version.
o False, None, True, and, as, assert, break, class, continue, def, del, elif, else,
except, finally, for, From, global, if, import, in, is, lambda, nonlocal, not, or,
pass, raise, return, try, while, with, yield
Variables and Types
When we create a program, we often need store values so that it can be used in a
program. We use variables to store data which can be manipulated by the computer
program.
Every variable:
Can be of any size
Have allowed characters, which are a-z, A-Z, 0-9 and underscore (_)
should begin with an alphabet or underscore
should not be a keyword
Dept. of CSE 9
Data Structures A. Y. 2024 - 2025
6. TYPES OF METHODS
In Python, there are three different method types: the static method, the class method and the
instance method. Each one of them has different characteristics and should be used in
different situations.
Static Methods
A static method in Python must be created by decorating it with @staticmethod. This lets
Python know that the method should be static. The main characteristic of a static method is
that they can be called without instantiating the class. These methods are self-contained,
meaning that they can’t access any other attribute or call any other method within that class.
You could use a static method when you have a class, but you don’t need a specific instance in
order to access that method. For example, if you have a class called Math and you have a
method called factorial, you probably won’t need a specific instance to call that method. So,
you could use a static method.
class Math:
@staticmethod
def factorial(number):
if number == 0:
return 1
else:
return number * MethodTypes.factorial(number - 1)
factorial = MethodTypes.factorial(5)
print(factorial)
Class Method
Class methods have to be created with the decorator @classmethod, and these methods share
a characteristic with the static methods in that they can be called without having an instance of
the class. The difference relies on the capability to access other methods and class attributes
but no instance attributes.
Instance Methods
This method can only be called if the class has been instantiated. Once an object of that class
has been created, the instance method can be called and can access all the attributes of that
class through the reserved word self. An instance method is capable of creating, getting and
setting new instance attributes and calling other instance, class and static methods.
Dept. of CSE 10
Data Structures A. Y. 2024 - 2025
In simple terms, it comes down to method types. self is used to access an instance method, cls
is used to access a class method and nothing is applied when it’s a static method.
The difference between the keywords selfand cls reside only in the method type. If the created
method is an instance method then the reserved word self to be used, but if the method is a
class method then the keyword cls must be used. Finally, if the method is a static method then
none of those words will be used. Static methods are self-contained and don’t have access to
the instance or class variables nor to the instance or class methods.
7. INHERITANCE
The new class inherits from older classes. The new class copies all the older class's functions
and attributes without rewriting the syntax in the new classes. These new classes are called
derived classes, and old ones are called base classes.
Syntax:
class DerivedClass(BaseClass): # Class definition
Derived Class is the class that will inherit from the Base Class. The Base Class is the class that
will serve as the parent or superclass.
Example:
class Person:
def __init__(self, name, age):
# Constructor to initialize name and age attributes
self.name = name
self.age = age
Dept. of CSE 11
Data Structures A. Y. 2024 - 2025
person.say_hello()
Output:
Hello, my name is John and I am 30 years old.
Hello, my name is Mary and I am 18 years old.
I am a student in grade 12.
a. SINGLE INHERITANCE
Single Inheritance is the simplest form of inheritance where a single child class is derived from
a single parent class. Due to its candid nature, it is also known as Simple Inheritance.
# python 3 syntax
# single inheritance example
class child(parent):
# child class
def func2(self): # we include the parent class
print("Hello Child") # as an argument in the child
# class
# Driver Code
test = child() # object created
test.func1() # parent method called via child object
test.func2() # child method called
Output:
Hello Parent
Hello Child
b. MULTIPLE INHERITANCE
In multiple inheritance, a single child class is inherited from two or more parent classes. It
means the child class has access to all the parent classes' methods and attributes.
However, if two parents have the same “named” methods, the child class performs the method
of the first parent in order of reference. To better understand which class’s methods shall be
executed first, we can use the Method Resolution Order function (mro). It tells the order in
which the child class is interpreted to visit the other classes.
Dept. of CSE 12
Data Structures A. Y. 2024 - 2025
# python 3 syntax
# multiple inheritance example
# Driver Code
test = child() # object created
test.func1() # parent1 method called via child
test.func2() # parent2 method called via child instead of parent3
test.func3() # child method called
# to find the order of classes visited by the child class, we use __mro__ on the child class
print(child.__mro__)
Output:
> Hello Parent1
> Hello Parent2
> Hello Child
>(<class '__main__.child'>, <class '__main__.parent1'>, <class '__main
c. MULTI-LEVEL INHERITANCE
In multilevel inheritance, we go beyond just a parent-child relation. We introduce
grandchildren, great-grandchildren, grandparents, etc. We have seen only two levels of
inheritance with a superior parent class/es and a derived class/es, but here we can have
multiple levels where the parent class/es itself is derived from another class/es.
class grandparent: # first level
def func1(self):
print("Hello Grandparent")
Dept. of CSE 13
Data Structures A. Y. 2024 - 2025
# Driver Code
test = child() # object created
test.func1() # 3rd level calls 1st level
test.func2() # 3rd level calls 2nd level
test.func3() # 3rd level calls 3rd level
Output:
> Hello Grandparent
> Hello Parent
> Hello Child
d. HIERARCHICAL INHERITANCE
Hierarchical Inheritance is the right opposite of multiple inheritance. It means that there are
multiple derived child classes from a single-parent class.
# python 3 syntax
# hierarchical inheritance example
# Driver Code
test1 = child1() # objects created
test2 = child2()
Dept. of CSE 14
Data Structures A. Y. 2024 - 2025
Output:
> Hello Parent
> Hello Child1
> Hello Parent
> Hello Child2
e. HYBRID INHERITANCE
Hybrid Inheritance is the mixture of two or more different types of inheritance. Here we can
have many relationships between parent and child classes with multiple levels.
# python 3 syntax
# hybrid inheritance example
# Driver Code
test1 = child1() # object created
test2 = child2()
Dept. of CSE 15
Data Structures A. Y. 2024 - 2025
Output:
> Hello Parent1
> Hello Child1
> Hello Parent1
> Hello Parent2
> Hello Child1
> Hello Child2
8. POLYMORPHISM
Polymorphism is defined as the circumstance of occurring in several forms. It refers to the
usage of a single type entity (method, operator, or object) to represent several types in various
contexts. Polymorphism is made from 2 words – ‘poly‘ and ‘morphs.’ The word ‘poly’ means
‘many’ and ‘morphs’ means ‘many forms.
Polymorphism has the following advantages:
It is beneficial to reuse the codes.
The codes are simple to debug.
A single variable can store multiple data types.
Dept. of CSE 16
Data Structures A. Y. 2024 - 2025
Types of Polymorphism:
Compile-Time Polymorphism (Static Binding / Method Overloading): Compile-time
polymorphism is primarily achieved through function overloading, although Python
does not support true function overloading. However, you can define functions with the
same name in Python, but only the latest defined function will be considered.
class Dog(Animal):
def make_sound(self):
print("Dog barks")
my_animal = Dog()
my_animal.make_sound() # Calls the overridden method in the Dog class
Interface Polymorphism: Python follows a concept called "duck typing," which is a form
of interface polymorphism. If an object behaves like a particular interface (has the
required methods and attributes), it can be treated as an instance of that interface.
class Bird:
def fly(self):
pass
class Sparrow(Bird):
def fly(self):
print("Sparrow flies")
class Airplane:
Dept. of CSE 17
Data Structures A. Y. 2024 - 2025
def fly(self):
print("Airplane flies")
def perform_flight(flying_object):
flying_object.fly()
sparrow = Sparrow()
airplane = Airplane()
Method Overriding: Method overriding is a type of polymorphism in which a child class which
is extending the parent class can provide different definition to any function defined in the
parent class as per its own requirements.
Method Overloading : Method overriding or function overloading is a type of polymorphism in
which we can define a number of methods with the same name but with a different number of
parameters as well as parameters can be of different types. These methods can perform a
similar or different function.
Python doesn't support method overloading on the basis of different number of parameters in
functions.
a. FUNCTION POLYMORPHISM
There are certain Python functions that can be used with different data types. The len()
function is one example of such a function. Python allows it to work with a wide range of data
types.
The built-in function len() estimates an object’s length based on its type. If an object is a string,
it returns the number of characters; or if an object is a list, it returns the number of elements in
the list. If the object is a dictionary, it gives the total number of keys found in the dictionary.
Example:
mystr = 'Programming'
print('Length of string:', len(mystr))
mylist = [1, 2, 3, 4, 5]
print('Length of list:', len(mylist))
Dept. of CSE 18
Data Structures A. Y. 2024 - 2025
Output:
Length of string: 11
Length of list: 5
Length of dict: 2
b. CLASS POLYMORPHISM
Because Python allows various classes to have methods with the same name, we can leverage
the concept of polymorphism when constructing class methods. We may then generalize
calling these methods by not caring about the object we’re working with. Then we can write a
for loop that iterates through a tuple of items.
Example:
class Tiger():
def nature(self):
print('I am a Tiger and I am dangerous.')
def color(self):
print('Tigers are orange with black strips')
class Elephant():
def nature(self):
print('I am an Elephant and I am calm and harmless')
def color(self):
print('Elephants are grayish black')
obj1 = Tiger()
obj2 = Elephant()
for animal in (obj1, obj2): # creating a loop to iterate through the obj1 and
obj2
animal.nature()
animal.color()
Output:
I am a Tiger and I am dangerous.
Tigers are orange with black strips
I am an Elephant and I am calm and harmless
Elephants are grayish black
Dept. of CSE 19
Data Structures A. Y. 2024 - 2025
Example:
class Vehicle:
def __init__(self, brand, model, price):
self.brand = brand
self.model = model
self.price = price
def show(self):
print('Details:', self.brand, self.model, 'Price:', self.price)
def max_speed(self):
print('Vehicle max speed is 160')
def gear_system(self):
print('Vehicle has 6 shifter gearbox')
def gear_system(self):
print('Car has Automatic Transmission')
# Car Object
car = Car('Audi', 'R8', 9000000)
car.show()
# call methods from Car class
car.max_speed()
car.gear_system()
# Vehicle Object
vehicle = Vehicle('Nissan', 'Magnite', 550000)
Dept. of CSE 20
Data Structures A. Y. 2024 - 2025
vehicle.show()
# call method from a Vehicle class
vehicle.max_speed()
vehicle.gear_system()
Output:
Details: Audi R8 Price: 9000000
Car max speed is 260
Car has Automatic Transmission
Details: Nissan Magnite Price: 550000
Vehicle max speed is 160
Vehicle has 6 shifter gearbox
9. ABSTRACTION
Abstraction is used to hide the internal functionality of the function from the users. The users
only interact with the basic implementation of the function, but inner working is hidden. User
is familiar with that "what function does" but they don't know "how it does." An abstraction is
used to hide the irrelevant data/class in order to reduce the complexity. It also enhances the
application efficiency
For example, When we use the TV remote to increase the volume. We don't know how
pressing a key increases the volume of the TV. We only know to press the "+" button to
increase the volume.
Dept. of CSE 21
Data Structures A. Y. 2024 - 2025
class Triangle(Polygon):
class Pentagon(Polygon):
class Hexagon(Polygon):
class Quadrilateral(Polygon):
# Driver code
R = Triangle()
R.noofsides()
K = Quadrilateral()
K.noofsides()
R = Pentagon()
R.noofsides()
K = Hexagon()
K.noofsides()
Output
I have 3 sides
I have 4 sides
I have 5 sides
I have 6 sides
Dept. of CSE 22
Data Structures A. Y. 2024 - 2025
UNIT II
SORTING AND SEARCHING
1. SEARCHING
Search algorithms are methods that allow us to find the location of a specific element within a
list of elements. Depending on the list, you will need to use one algorithm or another; for
example, if the list has ordered elements, you can use a binary search algorithm, but if the list
contains the elements in an unordered way this algorithm will not work. To search for an
element in an unordered list you must use a linear search algorithm.
2. LINEAR SEARCH
Linear search algorithms, also known as sequential search, involve going through a list of items
one by one until a specific item is found. This algorithm is very simple to implement in code but
can be very inefficient depending on the length of the list and the location of the item.
def linear_search(list, objective):
Algorithm:
LinearSearch ( Array A, Value x)
Step 1: Set i to 1
Step 2: if i > n then go to step 7
Step 3: if A[i] = x then go to step 6
Step 4: Set i to i + 1
Step 5: Go to Step 2
Step 6: Print Element x Found at index i and go to step 8
Step 7: Print element not found
Step 8: Exit
for i in range(len(list)):
if list[i] == objective:
return i
return -1
list = [1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 15, 20, 27, 34, 39, 50]
objective_number = 39
result = linear_search(list, objective_number)
if result != -1:
print(fThe number {objective_number} is located at position: {result}")
else:
print(f"The number {objective_number} is NOT in the list")
code output:
The Number 39 is located at position: 14
Dept. of CSE 23
Data Structures A. Y. 2024 - 2025
Pros:
Simplicity: Linear search is one of the simplest and easiest search algorithms to
implement. It only requires iterating through the list of items one by one until the
target is found.
flexibility: The linear search can be applied to any type of list, regardless of whether it is
sorted or not.
Cons:
Inefficiency in large lists: The main disadvantage of linear search is its inefficiency in
large lists. Because it compares each item one by one, its execution time grows linearly
with the size of the list.
Not suitable for ordered lists: Although it can work on unordered lists, linear search is
not efficient for ordered lists. In such cases, more efficient search algorithms, such as
binary search, are preferable.
3. BINARY SEARCH
The binary search algorithm is a very efficient algorithm that applies only to ordered lists. It
works by repeatedly dividing the list into two halves and comparing the target element with
the middle element, this significantly reduces the number of comparisons needed.
Algorithm:
In binary search, we follow the following steps:
i. We start by comparing the element to be searched with the element in the middle of the
list/array.
ii. If we get a match, we return the index of the middle element.
iii. If we do not get a match, we check whether the element to be searched is less or greater
than invalue than the middle element.
iv. If the element/number to be searched is greater in value than the middle number, then we
pick the elements on the right side of the middle element(as the list/array is sorted, hence
on the right, we will have all the numbers greater than the middle number), and start again
from the step 1.
v. If the element/number to be searched is lesser in value than the middle number, then we
pick the elements on the left side of the middle element, and start again from the step 1.
Dept. of CSE 24
Data Structures A. Y. 2024 - 2025
return -1
# Example of use
list = [1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 15, 20, 27, 34, 39, 50]
objective = 27
start_search = 0
end_search = len(list) - 1
if result != -1:
print(f"The number {objective_number} is in position: {result}.")
else:
print(f"The number {objective_number} is NOT in the list")
code output:
Dept. of CSE 25
Data Structures A. Y. 2024 - 2025
4. SORTING
A sorting algorithm allows us to rearrange a list of elements or nodes in a specific order, for
example in ascending or descending order depending on the occasion.
a. BUBBLE SORT
The bubble sort algorithm is one of the simplest but least efficient algorithms. It works by
comparing pairs of elements and swapping them if they are in the wrong order, this process is
done over and over again until the list is sorted correctly. It has a time complexity of O(n2) in the
average and worst cases scenarios and O(n) in the best-case scenario.
def bubble_sort(list):
length = len(list)
for i in range(length):
for j in range(0, (length-i) - 1):
Pros:
Simplicity: The bubble algorithm is easy to understand and implement, which makes
it a good choice for introducing ordering concepts in programming.
Simple deployment: Requires little amount of code and does not involve complex
data structures.
Cons:
Slow for large lists: Due to its quadratic complexity the bubble algorithm becomes
slow in practice for lists of considerable size.
Does not consider partial order: Unlike other algorithms, the bubble algorithm
performs the same number of comparisons and swaps regardless of whether the list
is already largely sorted.
Dept. of CSE 26
Data Structures A. Y. 2024 - 2025
b. SELECTION SORT
This sorting technique repeatedly finds the minimum element and sort it in order. Bubble Sort
does not occupy any extra memory space. During the execution of this algorithm, two subarrays
are maintained, the subarray which is already sorted, and the remaining subarray which is
unsorted. During the execution of Selection Sort for every iteration, the minimum element of the
unsorted subarray is arranged in the sorted subarray. Selection Sort is a more efficient algorithm
than bubble sort. Sort has a Time-Complexity of O(n2) in the average, worst, and in the best
cases.
Algorithm
Step 1: Set MIN to location 0
Step 2: Search the minimum element in the list
Step 3: Swap with value at location MIN
Step 4: Increment MIN to point to next element
Step 5: Repeat until list is sorted
for s in range(size):
min_idx = s
# Driver code
data = [ 7, 2, 1, 6 ]
size = len(data)
selectionSort(data, size)
Output
Sorted Array in Ascending Order is : [1, 2, 6, 7]
Dept. of CSE 27
Data Structures A. Y. 2024 - 2025
Advantages
The main advantage of the selection sort is that it performs well on a small list.
Because it is an in-place sorting algorithm, no additional temporary storage is required
beyond
what is needed to hold the original list.
Its performance is easily influenced by the initial ordering of the items before the
sorting process.
Disadvantages
The primary disadvantage of the selection sort is its poor efficiency when dealing with a
huge list of items.
The selection sort requires n-squared number of steps for sorting n elements.
Quick Sort is much more efficient than selection sort
c. INSERTION SORT
The insertion sort algorithm is a simple but efficient algorithm. It works by dividing the list into
two parts, an ordered part and an unordered part. As the unordered list is traversed, elements
are inserted in the correct position in the ordered part. Insertion Sort has a Time-Complexity of
O(n2) in the average and worst case, and O(n) in the best case.
Algorithm
Step 1: If it is the first element, it is already sorted. return 1;
Step 2: Pick next element
Step 3: Compare with all elements in the sorted sub-list
Step 4: Shift all the elements in the sorted sub-list that is greater than the value to be
sorted
Step 5: Insert the value
Step 6: Repeat until list is sorted
"""
This loop interchanges the two position numbers, as long as the previous
number is larger than the current number.
"""
Dept. of CSE 28
Data Structures A. Y. 2024 - 2025
return list
unordered_list = [39, 45, 32, 4, 2, 85, 43, 7, 18, 16, 5, 67, 32]
ordered_list = insertion_sort(unordered_list)
print(ordered_list)
# output: [2, 4, 5, 7, 16, 18, 32, 32, 39, 43, 45, 67, 85]
Pros:
Low overhead: Requires fewer comparisons and moves than algorithms such as
bubble sort, which makes it more efficient in terms of item exchanges.
Simplicity: insertion sort is one of the simplest sorting algorithms to implement and
understand. This makes it suitable for teaching basic sorting concepts.
Cons:
Inefficiency in large lists: As the list size increases, the performance of insertion sort
decreases. Its quadratic complexity of O(n^2) in the worst case makes it inefficient for
large lists.
Non-scalable: Like other quadratic complexity algorithms, insertion sort is not scalable
for large lists, as its execution time increases considerably with the size of the list.
d. MERGE SORT
Merge Sort is a Divide and Conquer algorithm. It divides input array in two halves, calls itself for
the two halves and then merges the two sorted halves. The merge() function is used for
merging two halves. The merge(arr, l, m, r) is key process that assumes that arr[l..m] and
arr[m+1..r] are sorted and merges the two sorted sub-arrays into one. Time Complexity of
merge sort is O(n*log(n))
Dept. of CSE 29
Data Structures A. Y. 2024 - 2025
Dept. of CSE 30
Data Structures A. Y. 2024 - 2025
mergeSort(arr, 0, n-1)
print("\n\nSorted array is")
for i in range(n):
print("%d" % arr[i],end=" ")
Output
Given array is 12 11 13 5 6 7
Sorted array is 5 6 7 11 12 13
e. QUICK SORT
Quick sort is a well-known sorting algorithm. It is highly efficient and also known as partition
exchange sort. In this sorting algorithm the array is divided into 2 sub array. One contain
smaller
values than pivot value and other array contain elements having greater values than pivot
value.
Pivot is an element that is used to compare and divide the elements of the main array into two.
Quick sort partitions an array and then calls itself recursively twice to sort the two resulting sub
arrays. This algorithm is quite efficient for large data sets. The Average and worst case
Dept. of CSE 31
Data Structures A. Y. 2024 - 2025
Advantages
The quick sort is regarded as the best sorting algorithm.
It is able to deal well with a huge list of items.
Because it sorts in place, no additional storage is required as well
Disadvantages
The slight disadvantage of quick sort is that its worst-case performance is similar to
average performances of the bubble, insertion or selections sorts.
If the list is already sorted than bubble sort is much more efficient than quick sort
If the sorting element is integers than radix sort is more efficient than quick sort.
Dept. of CSE 32
Data Structures A. Y. 2024 - 2025
UNIT – III
DATA STRUCTURES
1. DEFINITION
A data structure refers to organizing and storing data in a computer's memory in a way that
enables efficient access, manipulation, and retrieval of the data. Data structures are
fundamental concepts in computer science and are extensively used in programming and
software development to solve various computational problems. Data structures are essential
for organizing and storing data in a manner that allows users to utilize it effectively.
There are two categories of data structure - linear data structure and non-linear data structure.
In real life, linear data structure is used to develop software, and non-linear data structure is
used in image processing and artificial intelligence.
Dept. of CSE 33
Data Structures A. Y. 2024 - 2025
a linear manner. There are multiple levels of nonlinear data structures. It is also called a
multilevel data structure. Implementing a non-linear data structure can be more challenging
compared to its linear counterpart. However, it offers more efficient memory utilization.
Examples of non-linear data structures include trees and graphs.
Factor Linear Data Structure Non-Linear Data Structure
Data Element In a linear data structure, data In a non-linear data structure, data
Arrangement elements are sequentially connected, elements are hierarchically
allowing users to traverse all elements connected, appearing on multiple
in one run. levels.
Implementation Linear data structures are relatively Non-linear data structures require a
Complexity easier to implement. higher level of understanding and
are more complex to implement.
Levels All data elements in a linear data Data elements in a non-linear data
structure exist on a single level. structure span multiple levels.
Traversal A linear data structure can be Traversing a non-linear data
traversed in a single run. structure is more complex, requiring
multiple runs.
Memory Utilization Linear data structures do not efficiently Non-linear data structures are more
utilize memory. memory friendly.
Time Complexity The time complexity of a linear data The time complexity of a non-linear
structure is directly proportional to its data structure often remains
size, increasing as input size increases. constant, irrespective of its input
size.
Applications Linear data structures are ideal for Non-linear data structures are
application software development. commonly used in image processing
and Artificial Intelligence.
Examples Linked List, Queue, Stack, Array. Tree, Graph, Hash Map.
Can push/pop data from a single end of the stack. Users can insert the data into the stack via
push operation and remove data from the stack via pop operation. The stack follows the rule of
LIFO (last in first out). Users can access all the stack data from the top of the stack in a linear
manner. In real-life problems, the stack data structure is used in many applications. For
Dept. of CSE 34
Data Structures A. Y. 2024 - 2025
example, the web browsers use the stack to store the backward/forward operations. For the
array-based implementation of a stack, the push and pop operations take constant time, i.e.
O(1)
There are some basic operations that allow us to perform different actions on a stack.
Push: Add an element to the top of a stack
Pop: Remove an element from the top of a stack
IsEmpty: Check if the stack is empty
IsFull: Check if the stack is full
Peek: Get the value of the top element without removing it
Python’s built-in list data structure is a simple and effective way to implement a stack. The key
operations in a stack, namely ‘push’ (add an element to the top of the stack) and ‘pop’ (remove
an element from the top of the stack), can be easily achieved using the append() and pop()
methods of a list.
To add an element to the stack (push operation), you can use the append() method.
This method adds an element to the end of the list, which corresponds to the top of the
Dept. of CSE 35
Data Structures A. Y. 2024 - 2025
To remove an element from the stack (pop operation), you can use the pop() method.
This method removes the last element from the list, which corresponds to the top of
the stack.
last_element = stack.pop()
print(f'Popped Element: {last_element}')
print(f'Stack after Pop: {stack}')
# Output:
# Popped Element: c
# Stack after Pop: ['a', 'b']
# Creating a stack
def create_stack():
stack = []
return stack
Dept. of CSE 36
Data Structures A. Y. 2024 - 2025
return stack.pop()
stack = create_stack()
push(stack, str(1))
push(stack, str(2))
push(stack, str(3))
push(stack, str(4))
print("popped item: " + pop(stack))
print("stack after popping an element: " + str(stack))
b. APPLICATIONS OF STACK
Dept. of CSE 37
Data Structures A. Y. 2024 - 2025
Dept. of CSE 38
Data Structures A. Y. 2024 - 2025
Output:
Queue before any operation [‘x’ , ‘y’, ‘z’]
Dequeue items x , y, z
Output:
Queue length is 5
Dept. of CSE 39
Data Structures A. Y. 2024 - 2025
b. APPLICATIONS OF QUEUES
A queue data structure is generally used in scenarios where the FIFO approach (First In First
Out) has to be implemented. The following are some of the most common queue applications
in data structure:
Managing requests on a single shared resource such as CPU scheduling and disk
scheduling
Handling hardware or real-time systems interrupts
Handling website traffic
Routers and switches in networking
Maintaining the playlist in media players
Queues can be used to manage and allocate resources, such as printers or CPU
processing time.
Queues can be used to handle batch processing jobs, such as data analysis or image
rendering.
Queues can be used to buffer messages in communication systems, such as message
queues in messaging systems or buffers in computer networks.
c. PRIORITY QUEUES
Priority queue in Python is a special type of queue that is executed based on priorities. It is not
a completely FIFO queue as it sorts and dequeues an element based on priority and not based
on when they were added. It calculates the priorities based on the ordering of their key pairs.
They are most useful in scheduling tasks where priority is of importance. For instance, an
operating system executes and completes a task based on priority; hence, a priority queue can
be used here.
There are multiple ways to implement a priority queue. The two standard ways are through:
Manually sorted list
queue.PriorityQueue Class
Dept. of CSE 40
Data Structures A. Y. 2024 - 2025
inserting new elements can be challenging as it follows an O(n) time complexity. Hence, the
best use of a manually sorted list can be when the number of insertions is minimal. In the code
below, you will manually sort a list to implement a priority queue in Python.
priority_queu = []
priority_queu.append((3, 'Huindai'))
priority_queu.append((4, 'Mahindra'))
priority_queu.append((1, 'Maruthi'))
priority_queu.append((2, 'TATA'))
# Resort everytime a new element is added
priority_queu.sort(reverse=True)
while priority_queu:
nxt_itm = priority_queu.pop()
print(nxt_itm)
Output:
( 1, ‘Maruthi’)
(2, TATA’)
(3, Huindai’)
(4, ‘Mahindra’)
Output:
( 1, ‘Maruthi’)
(2, TATA’)
(3, Huindai’)
(4, ‘Mahindra’)
Dept. of CSE 41
Data Structures A. Y. 2024 - 2025
6. ARRAYS
The array is the most used Linear data type. The array stores the objects of the same data type
in a linear fashion. Users can use an array to construct all linear or non-linear data structures.
For example, Inside the car management software to store the car names array of the strings is
useful. We can access the element of the array by the index of elements. In an array, the index
always starts at 0. To prevent memory wastage, users should create an array of dynamic sizes.
7. LINKED LISTS
LinkedList data structure stores the data in the form of a node. Every linked list node contains
the element value and address pointer. The address pointer of the LinkedList consists of the
address of the next node. It can store unique or duplicate elements.
A singly-linked list is the simplest type of linked list, where each node contains some data and a
reference to the next node in the sequence. They can only be traversed in a single direction -
from the head (the first node) to the tail (the last node).
Each node in a singly-linked list typically consists of two parts:
Data: The actual information stored in the node.
Next Pointer: A reference to the next node. The last node’s next pointer is usually set to
null.
Dept. of CSE 42
Data Structures A. Y. 2024 - 2025
Since these data structures can only be traversed in a single direction, accessing a specific
element by value or index requires starting from the head and sequentially moving through the
nodes until the desired node is found. This operation has a time complexity of O(n), making it
less efficient for large lists.
Inserting and deleting a node at the beginning of a singly-linked list is highly efficient with a
time complexity of O(1). However, insertion and deletion in the middle or at the end requires
traversing the list until that point, leading to an O(n) time complexity.
The design of singly-linked lists makes them a useful data structure when performing
operations that occur at the beginning of the list.
The below program creates the linked list with three data elements.
class Node:
def __init__(self, dataval=None):
self.dataval = dataval
self.nextval = None
class SLinkedList:
def __init__(self):
self.headval = None
list1 = SLinkedList()
list1.headval = Node("Mon")
e2 = Node("Tue")
e3 = Node("Wed")
# Link first Node to second node
list1.headval.nextval = e2
Dept. of CSE 43
Data Structures A. Y. 2024 - 2025
Dept. of CSE 44
Data Structures A. Y. 2024 - 2025
Dept. of CSE 45
Data Structures A. Y. 2024 - 2025
Algorithm:
Define a Node class which represents a node in the list. It will have three properties:
data, previous which will point to the previous node and next which will point to the
next node.
Define another class for creating a doubly linked list, and it has two nodes: head and
tail. Initially, head and tail will point to null.
addNode() will add node to the list:
o It first checks whether the head is null, then it will insert the node as the head.
o Both head and tail will point to a newly added node.
o Head's previous pointer will point to null and tail's next pointer will point to null.
o If the head is not null, the new node will be inserted at the end of the list such
that new node's previous pointer will point to tail.
o The new node will become the new tail. Tail's next pointer will point to null.
display() will show all the nodes present in the list.
o Define a new node 'current' that will point to the head.
o Print current.data till current points to null.
o Current will point to the next node in the list in each iteration.
Dept. of CSE 46
Data Structures A. Y. 2024 - 2025
dList = DoublyLinkedList();
#Add nodes to the list
dList.addNode(1);
dList.addNode(2);
dList.addNode(3);
dList.addNode(4);
dList.addNode(5);
Dept. of CSE 47
Data Structures A. Y. 2024 - 2025
Output:
Nodes of doubly linked list: 12345
c. CIRCULAR LINKED LISTS
Circular linked lists are a specialized form of linked list where the last node points back to the
first node, creating a circular structure. This means that, unlike the singly and doubly linked
lists we’ve seen so far, the circular linked list does not end; instead, it loops around.
The cyclical nature of circular linked lists makes them ideal for scenarios that need to be looped
through continuously, such as board games that loop back from the last player to the first, or in
computing algorithms such as round-robin scheduling.
ALGORITHM:
Define a Node class which represents a node in the list. It has two properties data and
next which will point to the next node.
Define another class for creating the circular linked list, and it has two nodes: head and
tail. It has two methods: add() and display() .
add() will add the node to the list:
o It first checks whether the head is null, then it will insert the node as the head.
o Both head and tail will point to the newly added node.
o If the head is not null, the new node will be the new tail, and the new tail will
point to the head as it is a circular linked list.
display() will show all the nodes present in the list.
Define a new node 'current' that will point to the head.
Print current.data till current will points to head
Current will point to the next node in the list in each iteration
Program:
#Represents the node of list.
class Node:
def __init__(self,data):
self.data = data;
self.next = None;
Dept. of CSE 48
Data Structures A. Y. 2024 - 2025
class CreateList:
#Declaring head and tail pointer as null.
def __init__(self):
self.head = Node(None);
self.tail = Node(None);
self.head.next = self.tail;
self.tail.next = self.head;
#This function will add the new node at the end of the list.
def add(self,data):
newNode = Node(data);
#Checks if the list is empty.
if self.head.data is None:
#If list is empty, both head and tail would point to new node.
self.head = newNode;
self.tail = newNode;
newNode.next = self.head;
else:
#tail will point to new node.
self.tail.next = newNode;
#New node will become new tail.
self.tail = newNode;
#Since, it is circular linked list tail will point to head.
self.tail.next = self.head;
class CircularLinkedList:
cl = CreateList();
#Adds data to the list
cl.add(1);
cl.add(2);
cl.add(3);
cl.add(4);
Dept. of CSE 49
Data Structures A. Y. 2024 - 2025
Output:
Nodes of the circular linked list:
1234
Dept. of CSE 50
Data Structures A. Y. 2024 - 2025
UNIT -IV
DICTIONARIES
1. INTRODUCTION
Python provides another composite data type called a dictionary, which is similar to a list in
that it is a collection of objects. Dictionaries are Python’s implementation of a data structure
that is more generally known as an associative array. A dictionary consists of a collection of
key-value pairs. Each key-value pair maps the key to its associated value.
You can define a dictionary by enclosing a comma-separated list of key-value pairs in curly
braces ({}). A colon (:) separates each key from its associated value:
d={
<key>: <value>,
<key>: <value>,
.
.
.
<key>: <value>
}
Dept. of CSE 51
Data Structures A. Y. 2024 - 2025
Let's take an example to understand the working of the skip list. In this example, we have 14
nodes, such that these nodes are divided into two layers, as shown in the diagram.
The lower layer is a common line that links all nodes, and the top layer is an express line that
links only the main nodes, as you can see in the diagram.
Suppose you want to find 47 in this example. You will start the search from the first node of
the express line and continue running on the express line until you find a node that is equal a
47 or more than 47.
You can see in the example that 47 does not exist in the express line, so you search for a node
of less than 47, which is 40. Now, you go to the normal line with the help of 40, and search the
47, as shown in the diagram.
Dept. of CSE 52
Data Structures A. Y. 2024 - 2025
Search Operation: The search operation is used to search a particular node in a skip list.
Algorithm of the insertion operation
for i = 0 to level do
a → forward[i] = update[i] → forward[i]
update[i] → forward[i] = a
Dept. of CSE 53
Data Structures A. Y. 2024 - 2025
a = a → forward[0]
if a → key = SKey then return a → value
else return failure
3. GRAPHS
A graph is an advanced data structure that is used to organize items in an interconnected
network. Each item in a graph is known as a node(or vertex) and these nodes are connected by
edges.
In the figure below, we have a simple graph where there are five nodes in total and six edges.
Dept. of CSE 54
Data Structures A. Y. 2024 - 2025
The nodes in any graph can be referred to as entities and the edges that connect different
nodes define the relationships between these entities. In the above graph we have a set of
nodes {V} = {A, B, C, D, E} and a set of edges, {E} = {A-B, A-D, B-C, B-D, C-D, D-E} respectively
Types of Graphs
A) Null Graphs
A graph is said to be null if there are no edges in that graph. A pictorial representation of the
null graph is given below:
B) Undirected Graphs
If we take a look at the pictorial representation that we had in the Real-world example above,
we can clearly see that different nodes are connected by a link (i.e. edge) and that edge doesn't
have any kind of direction associated with it. For example, the edge between "Anuj" and
"Deepak" is bi-directional and hence the relationship between them is two ways, which turns
out to be that "Anuj" knows "Deepak" and "Deepak" also knows about "Anuj". These types of
graphs where the relation is bi-directional or there is not a single direction, are known as
Undirected graphs.
A pictorial representation of another undirected graph is given below:
Dept. of CSE 55
Data Structures A. Y. 2024 - 2025
C) Directed Graphs
What if the relation between the two people is something like, "Shreya" know "Abhishek" but
"Abhishek" doesn't know "Shreya". This type of relationship is one-way, and it does include a
direction. The edges with arrows basically denote the direction of the relationship and such
graphs are known as directed graphs.
A pictorial representation of the graph is given below:
It can also be noted that the edge from "Shreya" to "Abhishek" is an outgoing edge for "Shreya" and an
incoming edge for "Abhishek".
D) Cyclic Graph
A graph that contains at least one node that traverses back to itself is known as a cyclic graph.
In simple words, a graph should have at least one cycle formation for it to be called a cyclic
graph.
A pictorial representation of a cyclic graph is given below:
It can be easily seen that there exists a cycle between the nodes (a, b, c) and hence it is a cyclic graph.
Dept. of CSE 56
Data Structures A. Y. 2024 - 2025
E) Acyclic Graph
A graph where there's no way we can start from one node and can traverse back to the same
one, or simply doesn't have a single cycle is known as an acyclic graph.
A pictorial representation of an acyclic graph is given below:
F) Weighted Graph
When the edge in a graph has some weight associated with it, we call that graph as a weighted
graph. The weight is generally a number that could mean anything, totally dependent on the
relationship between the nodes of that graph.
A pictorial representation of the weighted graph is given below:
It can also be noted that if any graph doesn't have any weight associated with it, we simply call
it an unweighted graph.
G) Connected Graph
A graph where we have a path between every two nodes of the graph is known as a connected
graph. A path here means that we are able to traverse from a node "A" to say any node "B". In
simple terms, we can say that if we start from one node of the graph we will always be able to
traverse to all the other nodes of the graph from that node, hence the connectivity.
A pictorial representation of the connected graph is given below:
Dept. of CSE 57
Data Structures A. Y. 2024 - 2025
H) Disconnected Graph
A graph that is not connected is simply known as a disconnected graph. In a disconnected
graph, we will not be able to find a path from between every two nodes of the graph.
A pictorial representation of the disconnected graph is given below:
I) Complete Graph
A graph is said to be a complete graph if there exists an edge for every pair of vertices(nodes)
of that graph. A pictorial representation of the complete graph is given below:
J) Multigraph
A graph is said to be a multigraph if there exist two or more than two edges between any pair
of nodes in the graph.
A pictorial representation of the multigraph is given below:
Dept. of CSE 58
Data Structures A. Y. 2024 - 2025
Dept. of CSE 59
Data Structures A. Y. 2024 - 2025
Dept. of CSE 60
Data Structures A. Y. 2024 - 2025
5. REPRESENTATIONS
Breadth-first search and Depth-first search in python are algorithms used to traverse a graph or
a tree.
a. BREADTH FIRST SEARCH
Traversing means visiting each node of the graph. Breadth-First Search is a recursive algorithm
to search all the vertices of a graph or a tree. BFS in python can be implemented by using data
structures like a dictionary and lists. Breadth-First Search in tree and graph is almost the same.
The only difference is that the graph may contain cycles, so we may traverse to the same node
again.
As breadth-first search is the process of traversing each node of the graph, a standard BFS
algorithm traverses each vertex of the graph into two parts: 1) Visited 2) Not Visited. So, the
purpose of the algorithm is to visit all the vertex while avoiding cycles.
BFS starts from a node, then it checks all the nodes at distance one from the beginning node,
then it checks all the nodes at distance two, and so on. So as to recollect the nodes to be
visited, BFS uses a queue.
The steps of the algorithm work as follow:
Start by putting any one of the graph’s vertices at the back of the queue.
Now take the front item of the queue and add it to the visited list.
Create a list of that vertex's adjacent nodes. Add those which are not within the visited
list to the rear of the queue.
Keep continuing steps two and three till the queue is empty.
The pseudocode for BFS in python goes as below:
create a queue Q
mark v as visited and put v into Q
while Q is non-empty
remove the head u of Q
mark and enqueue all (unvisited) neighbors of u
Dept. of CSE 61
Data Structures A. Y. 2024 - 2025
graph = {
'5' : ['3','7'],
'3' : ['2', '4'],
'7' : ['8'],
'2' : [],
'4' : ['8'],
'8' : []
}
# Driver Code
print("Following is the Breadth-First Search")
bfs(visited, graph, '5') # function calling
Dept. of CSE 62
Data Structures A. Y. 2024 - 2025
Dept. of CSE 63
Data Structures A. Y. 2024 - 2025
We begin from the vertex P, the BFS algorithmic program starts by putting it within the Visited
list and puts all its adjacent vertices within the stack.
Fig 2: Visit starting verted and add its adjacent vertices to queue
Next, we have a tendency to visit the part at the front of the queue i.e. Q and visit its adjacent
nodes. Since P has already been visited, we have a tendency to visit R instead.
Dept. of CSE 64
Data Structures A. Y. 2024 - 2025
Fig 4: Visit R which was added to queue earlier to add its neighbor
Fig 6: Visited all nodes hence list visited is full and queue is empty
Since the queue is empty, we've completed the Traversal of the graph.
The time complexity of the Breadth first Search algorithm is in the form of O(V+E), where V is
Dept. of CSE 65
Data Structures A. Y. 2024 - 2025
the representation of the number of nodes and E is the number of edges. Also, the space
complexity of the BFS algorithm is O(V).
Applications of BFS Algorithm
Breadth-first Search Algorithm has a wide range of applications in the real-world. Some
of them are as discussed below:
In GPS navigation, it helps in finding the shortest path available from one point to
another.
In pathfinding algorithms
Cycle detection in an undirected graph
In minimum spanning tree
To build index by search index
In Ford-Fulkerson algorithm to find maximum flow in a network.
Dept. of CSE 66
Data Structures A. Y. 2024 - 2025
The pseudocode for Depth-First Search in python goes as below: In the init() function, notice
that we run the DFS function on every node because many times, a graph may contain two
different disconnected part and therefore to make sure that we have visited every vertex, we
can also run the DFS algorithm at every node.
DFS(G, u)
u.visited = true
for each v ∈ G.Adj[u]
if v.visited == false
DFS(G,v)
init() {
For each u ∈ G
u.visited = false
For each u ∈ G
DFS(G, u)
}
Example
Let us see how the DFS algorithm works with an example. Here, we will use an undirected
graph with 5 vertices.
We begin from the vertex P, the DFS rule starts by putting it within the Visited list and putting
all its adjacent vertices within the stack.
Dept. of CSE 67
Data Structures A. Y. 2024 - 2025
Fig 2: Visit starting vertex and add its adjacent vertices to stack
Next, we tend to visit the part at the highest of the stack i.e. Q, and head to its adjacent nodes.
Since P has already been visited, we tend to visit R instead.
Vertex R has the unvisited adjacent vertex in T, therefore we will be adding that to the highest
of the stack and visit it.
Fig 4: Vertex R has unvisited node T so we include it to the top of the stack and then visit it.
Dept. of CSE 68
Data Structures A. Y. 2024 - 2025
Fig 5: Vertex R has an unvisited node T, so we include it to the top of stack and then visit it.
At last, we will visit the last component S, it does not have any unvisited adjacent nodes, thus
we've completed the Depth First Traversal of the graph.
Fig 6: After visiting last node S, it doesn’t have any adjacent node
The time complexity of the Depth-First Search algorithm is represented within the sort of O(V +
E), where V is that the number of nodes and E is that the number of edges. The space
complexity of the algorithm is O(V).
Applications
Depth-First Search Algorithm has a wide range of applications for practical purposes. Some of
them are as discussed below:
Dept. of CSE 69
Data Structures A. Y. 2024 - 2025
Dept. of CSE 70
Data Structures A. Y. 2024 - 2025
UNIT –V
TREES
1. OVERVIEW OF TREES
Trees are non-linear data structures that store data hierarchically and are made up of nodes
connected by edges. For example, in a family tree, a node would represent a person, and an
edge would represent the relationship between two nodes. Tree data structure is a hierarchical
structure that is used to represent and organize data in a way that is easy to navigate and
search. It is a collection of nodes that are connected by edges and has a hierarchical
relationship between the nodes.
Example of tree:
a. TREE TERMINOLOGY
Root: A node that doesn’t have any parent, and the entire tree originates from it. In Fig.
1, the root is node a.
Leaf: Nodes that don’t have any child. In Fig. 1, leaf nodes are nodes c, d, and e.
Parent node: Immediate predecessor of a node. In Fig. 1, node a is the parent node of
nodes b and c.
Child node: Immediate successor of a node. In Fig. 1, Node b is the child node of Node
a.
Ancestors: All predecessors of a node. In Fig. 1, nodes a and b are ancestors of node d.
Descendants: All successors of a node. In Fig. 1, nodes d and e are descendants of node
b.
Siblings: Nodes that have the same parent. In Fig. 1, node d and e are siblings.
Dept. of CSE 71
Data Structures A. Y. 2024 - 2025
Left sibling: Sibling to the left of the node. In Fig. 1, node d is the left sibling of node e.
Right sibling: Sibling to the right of the node. In Fig. 1, node e is the right sibling of node
d.
Depth: Length of the path from node to root. In Fig. 1, the depth of node b is 2, and
Node d is 3.
Height/Max depth: Maximum depth of root to a leaf node. In Fig. 1, the height of the
tree is 3.
Dept. of CSE 72
Data Structures A. Y. 2024 - 2025
2. BINARY TREES
Binary Tree is a form of a tree whose nodes cannot have more than two children. Each node of
the binary tree has two pointers associated with it, one points to the left child, and the other
points to the right child of the node. It is an unordered tree having no fixed organized structure
for the arrangement of nodes. Binary Tree is slow for the searching, insertion, or deletion of
the data because of its unordered structure. The time complexity of these operations is (𝑁).
Dept. of CSE 73
Data Structures A. Y. 2024 - 2025
Leaf Nodes: Nodes that do not have any children are called leaf nodes or terminal
nodes. They are the nodes at the bottom-most level of the tree.
Internal Nodes: Nodes that have at least one child are called internal nodes. They are
not leaf nodes and are located somewhere between the root node and the leaf nodes.
Depth and Height: The depth of a node is the length of the path from the root node to
that node. The height of a node is the length of the longest path from that node to a
leaf node. The height of the binary tree is the height of the root node.
Binary Search Property: In a binary search tree (a specific type of binary tree), the
values stored in the left subtree of a node are less than the value of the node, and the
values stored in the right subtree are greater than the value of the node. This property
allows for efficient searching, insertion, and deletion operations.
a. IMPLEMENTATION
Crete the Node class
class Node:
"""
A node class representing a single element in the binary tree.
"""
def __init__(self, data):
self.data = data
self.left = None # Left child
self.right = None # Right child
Dept. of CSE 74
Data Structures A. Y. 2024 - 2025
output
print("Inorder traversal: ")
inorder_traversal(root)
After inserting 50, 30, 20, 40, 70, 60
Dept. of CSE 75
Data Structures A. Y. 2024 - 2025
c. TREE TRAVERSALS
There are three types of tree traversal techniques, These traversal in trees are types of depth
first search.
Inorder Traversal in Tree
o In this traversal method, the left subtree is visited first, then the root and later
the right sub-tree. We should always remember that every node may represent
a subtree itself.
PREORDER(n)
if(n != null)
print(n.data) // visiting root PREORDER(n.left) // visiting left subtree
PREORDER(n.right) // visiting right subtree
Now according to the inorder tree traversal method, we first traverse the left subtree of the
original tree. Remember, even while traversing the left subtree, we will follow the same
process i.e. left -> root -> right if the left child node of the original tree has furthermore child
nodes. After traversing the left subtree, we will add the result to the array as shown below.
Dept. of CSE 76
Data Structures A. Y. 2024 - 2025
After following the first step, we will traverse the root node of the original tree as shown
below.
Lastly, we will traverse the right subtree following the same process i.e. left -> root -> right if
the right child node of the original tree has more than one child node.
Dept. of CSE 77
Data Structures A. Y. 2024 - 2025
According to the preorder traversal method, we will first traverse the root node of the original
tree and put it in the result array as shown in the figure below.
Then, we will traverse the left subtree of the original tree by calling the preorder traversal
method. Here we will recursively call the function preorder to maintain the same process of
traversal i.e root -> left -> right if the left child of the original tree has more than one child node
and add the answer in the array as shown in the figure below.
Dept. of CSE 78
Data Structures A. Y. 2024 - 2025
Lastly, we will traverse the right subtree of the original tree similarly like we did with the left
subtree and put the result in the answer array as shown below.
Using the postorder tree traversal method we first visit the left subtree of the original tree
followed by the right subtree and lastly the root node of the original tree. Below is the Python
code for Postorder Tree Traversal with recursion:
Calling left-subtree
Calling right-subtree
Visit root node
Let us consider the following example tree:
Dept. of CSE 79
Data Structures A. Y. 2024 - 2025
Using the postorder traversal method, we will first traverse the left subtree of the original tree.
Remember that we will follow the same process of traversing of left subtree i.e left -> right ->
root if the left subtree has more than one child node and then put the result in the answer
array as shown in the figure.
Later we will traverse the right subtree of the original tree similarly like we did with the left
subtree and add the answer in the result array as shown below.
Dept. of CSE 80
Data Structures A. Y. 2024 - 2025
And lastly, we will traverse the root node of the original tree and finish our traversal method as
shown in the figure below.
Dept. of CSE 81
Data Structures A. Y. 2024 - 2025
a. IMPLEMENTATION
How to Insert a Node in the Binary Search Tree?
In a Binary Search Tree Python, while inserting a node, we need to take care of its property,
i.e., left <= root <= right. In this, we try to insert a new key value in an existing Binary Search
Tree while retaining its properties and all the existing keys and values.
Algorithm:
Start from the root node.
When inserting an element, compare it to the root; if it is smaller than the root, call the
left subtree recursively; otherwise, call the right subtree recursively.
Simply insert the node at the left (if less than current) or the right (if not) after reaching
the end.
Dept. of CSE 82
Data Structures A. Y. 2024 - 2025
Dept. of CSE 83
Data Structures A. Y. 2024 - 2025
Time Complexity
Average cases : O(logn)
Worst case : O(n) , when the tree is unbalanced.
Space Complexity : O(n)
4. AVL TREES
AVL tree is a height-balanced binary search tree. That means, an AVL tree is also a binary
search tree but it is a balanced tree. A binary tree is said to be balanced if, the difference
between the heights of left and right subtrees of every node in the tree is either -1, 0 or +1. In
other words, a binary tree is said to be balanced if the height of left and right children of every
node differ by either -1, 0 or +1. In an AVL tree, every node maintains an extra information
known as balance factor. The AVL tree was introduced in the year 1962 by G.M. Adelson Velsky
and E.M. Landis.
Balance factor of a node is the difference between the heights of the left and right subtrees of
that node. The balance factor of a node is calculated either height of left subtree - height of
right subtree (OR) height of right subtree - height of left subtree. In the following explanation,
Dept. of CSE 84
Data Structures A. Y. 2024 - 2025
we calculate as follows...
The below tree is AVL because the differences between the heights of left and right subtrees
for every node are less than or equal to 1.
Dept. of CSE 85
Data Structures A. Y. 2024 - 2025
Single RR(Right Right) Rotation - Here, every node of the tree moves toward the right
from the current position. Therefore, the parent becomes a left child in RR rotation. Let
us see the below example:
Double Rotation
Single rotation does not fix the LR rotation and RL rotation. For this, we require double rotation
involving three nodes. Therefore, double rotation is equivalent to the sequence of two single
rotations.
LR(Left-Right) Rotation - The LR rotation is the process where we perform a single left
rotation followed by a single right rotation. Therefore, first, every node moves towards
the left and then the node of this new tree moves one position towards the right. Let us
see the below example:
Dept. of CSE 86
Data Structures A. Y. 2024 - 2025
RL (Right-Left) Rotation - The RL rotation is the process where we perform a single right
rotation followed by a single left rotation. Therefore, first, every node moves towards
the right and then the node of this new tree moves one position towards the left. Let us
see the below example:
Python's standard library does not include an AVL tree implementation. There are, however, a
number of third-party libraries available that provide AVL tree functionality.
The 'avl tree' is a popular Python library for working with AVL trees, and it can be installed with
pip: pip install avl_tree.
Dept. of CSE 87
Data Structures A. Y. 2024 - 2025
rotation process and continue it until we are back at the root. Remember that the
modification of the balance factor must happen in a bottom-up fashion.
Ex: we have to delete the node '25' from the tree. As the node to be deleted does not have any
child node, we will simply remove the node from the tree:
Dept. of CSE 88
Data Structures A. Y. 2024 - 2025
5. B-TREES
A B-tree is a self-balancing tree data structure that maintains sorted data and allows searches,
sequential access, insertions, and deletions in logarithmic time.
B-TREE TRAVERSALS
Step-by-step algorithm:
Create a node with the name ‘BTreeNode‘ to create a default node with a list of keys
and a list of children nodes.
The b-tree class will be created which has two attributes ‘root‘ and ‘t‘, root represents
Dept. of CSE 89
Data Structures A. Y. 2024 - 2025
the root node, and ‘t‘ represents the minimum degree of the B-tree.
The display function in class ‘Btree‘ will be used to print to nodes of the tree in level-
wise format.
The final tree is created in the main function by inserting keys in the B-tree.
Dept. of CSE 90
Data Structures A. Y. 2024 - 2025
Dept. of CSE 91
Data Structures A. Y. 2024 - 2025
6. B+ TREES
A B+ tree is an advanced form of a self-balancing tree in which all the values are present in the
leaf level. An important concept to be understood before learning B+ tree is multilevel
indexing. In multilevel indexing, the index of indices is created as in figure below. It makes
accessing the data easier and faster.
Properties of a B+ Tree
All leaves are at the same level.
The root has at least two children.
Each node except root can have a maximum of m children and at least m/2 children.
Each node can contain a maximum of m - 1 keys and a minimum of ⌈ m/2⌉ - 1 keys.
Dept. of CSE 92
Data Structures A. Y. 2024 - 2025
SEARCHING ON A B+ TREE
The following steps are followed to search for data in a B+ Tree of order m. Let the data to be
searched be k.
Start from the root node. Compare k with the keys at the root node [k1, k2, k3,......km - 1].
If k < k1, go to the left child of the root node.
Else if k == k1, compare k2. If k < k2, k lies between k1 and k2. So, search in the left child of
k2.
If k > k2, go for k3, k4,...km-1 as in steps 2 and 3.
Repeat the above steps until a leaf node is reached.
If k exists in the leaf node, return true else return false.
EXAMPLE:
Let us search k = 45 on the following B+ tree.
ii. Since k > 25, go to the right child. (Go to right of the root)
Dept. of CSE 93
Data Structures A. Y. 2024 - 2025
iii. Compare k with 35. Since k > 30, compare k with 45. (K Not Found)
v. k is found. (k is found)
Dept. of CSE 94