0% found this document useful (0 votes)
0 views34 pages

DS Notes

This document provides an overview of Data Structures and Algorithms, focusing on Abstract Data Types (ADTs) and their implementation in object-oriented programming, particularly in Python. It discusses the classification of data structures, the principles of ADTs, and the importance of encapsulation, abstraction, and modularity in software design. Additionally, it highlights the role of classes in Python, including the creation of objects and the use of member functions.

Uploaded by

Chandran Kavi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
0 views34 pages

DS Notes

This document provides an overview of Data Structures and Algorithms, focusing on Abstract Data Types (ADTs) and their implementation in object-oriented programming, particularly in Python. It discusses the classification of data structures, the principles of ADTs, and the importance of encapsulation, abstraction, and modularity in software design. Additionally, it highlights the role of classes in Python, including the creation of objects and the use of member functions.

Uploaded by

Chandran Kavi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 34

lOMoARcPSD|12131744

DSA notes-Unit1 - Notes

DAta structure and algorithm (Anna University)

Scan to open on Studocu

Studocu is not sponsored or endorsed by any college or university


Downloaded by Rama Chandrankavi ([email protected])
lOMoARcPSD|12131744

DATA STRUCTURES AND ALGORITHMS

UNIT – 1
ABSTRACT DATA TYPE

Abstract Data Types (ADTs) – ADTs and classes – introduction to OOP – classes in Python –
inheritance – namespaces – shallow and deep copying Introduction to analysis of algorithms
– asymptotic notations – divide & conquer – recursion – analyzing recursive algorithms

INTRODUCTION TO DATA STRUCTURES

• Data structure is a branch of computer science. The study of data structure helps to
understand how data is organized and how data flow is managed to increase the
efficiency of any process or program.

• Data structure is the structural representation of logical relationship between data


elements.

Definition

Data structure is a way of storing, organizing and retrieving data in a computer, so that it can
be used efficiently. It provides a way to manage large amount of data proficiently.

Need for Data Structures

 It gives different level of organization of data.

 It tells how data can be stored and accessed.

 It provides a means to manage large amount of data efficiently.

 It provides fast searching and sorting of data.

Classification of Data structures

• Data structures can be classified into two categories.

i) Primitive Data structures

ii) Non-Primitive Data structures

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

Primitive Data Structures

• Primitive data structures are basic data structures. These can be manipulated or
operated directly by the machine level instructions. Basic data types such as integer,
real, character and Boolean come under this type. Example: int, char, float.

Non-Primitive Data Structures

 Non-primitive data structures are derived from primitive data structures.

 These data structures cannot be operated or manipulated directly by the


machine level instructions.

 They define the group of homogeneous and non-homogenous data items.

 Examples: Array, Lists, Graphs, Trees etc.

1. Linear Data Structures

 A data structure that maintains a linear relationship among the elements is called a
linear data structure.

 Here, the data are arranged in a sequential fashion. But in the memory the
arrangement may not be sequential.

 Examples: Arrays, linked lists, stack and queues.

2. Non-Linear Data Structures

 A data structure that maintains the data elements in hierarchical order are
known as nonlinear data structure. Thus, they are present at various levels.

 They are comparatively difficult to implement and understand as compared to


the linear data structures. Examples: Trees and Graphs.

3. Static Data Structures

• In static data structures the size of the structure is fixed.


2

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

• The content of the data structures can be modified without changing the memory
space allocated to it. Example: Array

4. Dynamic Data Structures

In dynamic data structures the size of the structure is not fixed and can be modified during
the operations performed on it.

• Dynamic data structures are designed to facilitate change of data structures in the run
time. Example: Linked list.

ABSTRACT DATA TYPES (ADTs)

• In the real-world, the programs evolve as a result of new requirements or constraints.


So, a modification to a program commonly requires a change in one or more of its
data structures.

• For example, to add a new field to a student record, to keep track of more
information about each student, then it will be better to replace an array with a linked
structure to improve the program’s efficiency. In such a scenario, rewriting every
procedure that uses the changed structure is not desirable.

• Therefore, a better alternative is to separate the use of a data structure from the details
of its implementation. This is the principle underlying use of abstract data type.

Definition

• An abstract data type or ADT is a mathematical abstraction. It specifies a set of


operations (or methods) and the semantics of the operations (what they do), but it
does not specify the implementation of the operations.

• Examples: List ADT, Stack ADT, Queue ADT, Trees, Graphs etc.

• The definition of ADT only mentions what operations are to be performed but not
how these operations will be implemented. It does not specify how data will be
organized in memory and what algorithms will be used for implementing the
operations. It is called “abstract” because it gives an implementation-independent
view. The process of hiding the nonessential details and providing only the essentials
of problem solving is known as data abstraction.

The set of operations can be grouped into four categories. They are,

• Constructors : Functions used to initialize new instances of the ADT at the time of
instantiation.

• Accessors : Functions used to access the data elements contained in an instance

• without making any modification to it.


3

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

• Mutators : Functions that modify the data elements of an ADT instance.

• Iterators : Functions that enable sequential access of the data elements.

Advantages of using ADTs

• ADTs give the feel of plug and play interface. So it is easy for the programmer to
implement ADT. For example, to store collection of items, it can be easily put the
items in a list. That is,

BirdsList=[‘Parrot’,’Dove’,’Duck’,’Cuckoo’]

• To find number of items in a list, the programmer can use len function without
implementing the code.

• The ADTs reduces the program developing time. Because the programmer can use the
predefined functions rather than developing the logic and implementing the same.

• The usage of ADTs reduces the chance of logical errors in the code, as the usage of
predefined functions in the ADTs are already bug free.

• ADTs provide well defined interfaces for interacting with the implementing code.

• ADTs increase the understandability and modularity of the code.

ADTs AND CLASSES

• A data structure is the implementation for an ADT. In an object-oriented language, an


ADT and its implementation together make up a class. Each operation associated with
the ADT is implemented by a member function or method. The variables that define
the space required by a data item are referred to as data members. An object is an
instance of a class that is created and takes up storage during the execution of a
computer program.

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

In this way, the user can use an abstract data type to define new class by specifying
attributes and operations

INTRODUCTION TO OBJECT ORIENTED PROGRAMMING

Goals, Principles and Patterns

• The main ‘actors’ in the object-oriented paradigm are called objects. Each object is an
instance of a class.

• Each class presents to the outside world a concise and consistent view of the objects,
that are instances of this class, without going into too much unnecessary detail or
giving others access to the inner workings of the objects.

• The class definition typically specifies instance variables, also known as data
members, that the object contains, as well as the methods, also known as member
functions, that the object can execute.

• This view of computing is intended to fulfill several goals and incorporate several
design principles.

Example 2:

• Consider a user defined Student class with student_name, student_id and


student_mark as its data members along with operations get_name(), put_name(),
get_id(), put_id(), compute_marks() and display_marks(_) as the member functions.

• This Student class would represent a composite type of data, named Student,
consisting of student_name (String type), student_id (integer or string type) and
student mark (float type).

• Thus, an instance of this student class represents an abstract view of the student
details. The class Student can be considered to represent a data type that is abstract in
nature. Thus, the classes are known to be abstract data type (ADT).

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

Object-Oriented Design Goals

• Software implementations should achieve robustness, adaptability, and reusability.

Robustness

• Every good programmer wants to develop software that is correct, which means that a
program produces the right output for all the anticipated inputs in the program’s
application. The software is said to be robust, if it is capable of handling unexpected
inputs that are not explicitly defined for its application. For example, if a program is
expecting a positive integer and instead is given a negative integer, then the program
should be able to recover gracefully from this error.

• Object-Oriented Design Principles

The important principles in object-oriented approach, are as follows

• Modularity

• Abstraction

• Encapsulation

Modularity

• Modern software systems consist of several different components that must interact
correctly in order for the entire system to work properly. Keeping these interactions
requires that these different components be well organized. Modularity refers to an
organizing principle in which different components of software system are divided
into separate functional units.

• Modularity in a software system can provide a powerful organizing framework that


brings clarity to an implementation. In Python, a module is a collection of closely

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

related functions and classes that are defined together in a single file of source code.
For example, Python’s standard library math module, provides definitions for key
mathematical constants and functions, and the os module, provides support for
interacting with the operating system.

• The uses of modularity:

• It increases the robustness of the program.

• It is easier to test and debug separate components of the program.

• It enables software reusability.

Abstraction

• Abstraction allows dealing with the complexity of the object. Abstraction allows
picking out the relevant details of the object, and ignoring the non-essential details.

• Applying the abstraction to the design of data structures gives rise to Abstract Data
Types (ADTs). An ADT is a mathematical model of a data structure that specifies the
type of data stored, the operations supported on them, and the types of parameters of
the operations. An ADT specifies what each operation does, but not how it does it.

• Python supports abstract data types using a mechanism known as an abstract base
class (ABC). An abstract base class cannot be instantiated (i.e., you cannot directly
create an instance of that class), but it defines one or more common methods that all
implementations of the abstraction must have. An ABC is realized by one or more
concrete classes that inherit from the abstract base class while providing
implementations for those method declared by the ABC.

Encapsulation

• Encapsulation means information hiding. It hides the data defined in the class.
Encapsulation separates implementation of the class from its interface. The interaction
with the class is through the interface provided by the set of methods defined in the
class. This separation of interface from its implementation allows changes to be made
in the class without affecting its interface.

• One of the advantages of encapsulation is that it gives freedom to the programmer to


implement the details of a component, without concern that other programmers will
be writing the code that intricately depends on those internal decisions. The only
constraint on the programmer of a component is to maintain the public interface for
the component, as the other programmers will be writing the code that depends on
that interface.

Object Oriented Design Patterns

• Object-oriented design facilitates reusable, robust, and adaptable software. Designing


good code requires the effective use of object-oriented design techniques.
7

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

• Computing researchers and practitioners have developed a variety of organizational


concepts and methodologies for designing quality object-oriented software that is
concise, correct, and reusable.

• The concept of a design pattern, describes a solution to a “typical” software design


problem. A pattern provides a general template for a solution that can be applied in
many different situations.

• It describes the main elements of a solution in an abstract way that can be specialized
for a specific problem at hand. The design pattern can be consistently applied to
implementations of data structures and algorithms.

• These design patterns fall into two groups patterns for solving algorithm design
problems and patterns for solving software engineering problems.

• The algorithm design patterns include the following:

• Recursion
• Amortization
• Divide and conquer
• Prune and search
• Brute force
• Dynamic Programming
• The greedy method
Likewise, the software engineering design patterns include:
 Iterator
 Adapter
 Position
 Composition
 Template method
 Locator
CLASSES IN PYTHON

• A class serves as the primary means for abstraction in object-oriented programming.


In python everything is an object. Everything is an instance of some class. A class
also serves as a blueprint for its instances.

 The data values stored inside an object are called attributes. The state information for
each instance is represented in the form of attributes (also known as fields, instance
variables, or data members).
8

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

 A class provides a set of behaviors in the form of member functions (also known as
methods), with implementations that are common to all instances of that class.

Defining a class

A class is the definition of data and methods for a specific type of object.

• Syntax:

class classname:

<statement1>

<statement>

The class definition begins with the keyword class, followed by the name of the class,
a colon and an indented block of code that serves as the body of the class.

The body includes definitions for all methods of the class. These methods are defined
as functions, with a special parameter, named self, that is used to identify the
particular instance upon which a member is invoked.

When a class definition is entered, a new namespace is created, and used as the local
scope. Thus, all assignments to local variables go into this new namespace.

• Example:

class customer:

def _ _init_ _(self,name,iden,acno):

self.custName=name

self.custID=iden

self.custAccNo=acno

def display(self):

print("Customer Name = ",self.custName)

print("Customer ID = ",self.custID)

print("Customer Account Number = ",self.custAccNo)

c = customer("Ramesh",10046,327659)

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

c.display()

The Self Identifier and Self Parameter

• In python, the self-identifier places a key role. Self identifies the instance upon which
a method is invoked. While writing function in a class, at least one argument has to be
passed, that is called self-parameter. The self-parameter is a reference to the class
itself and is used to access variables that belongs to the class.

• In python programming self is a default variable that contains the memory address of
the instance of current class. So self is used to reuse all the instance variable and
instance methods.

• Example: self.custName, self.custID, self.custAccNo

Object Creation

 An object is the runtime entity used to provide the functionality to the python class.

 The attributes defined inside the class are accessed only using objects of that class.

 The user defined functions also accessed by using the object.

 As soon as a class is created with attributes and methods, a new class object is created
with the same name as the class.

 This class object permits to access the different attributes as well as to instantiate new
objects of that class.

 Instance of the object is created using the name same as the class name and it is
known as object instantiation.

 One can give any name to a newly created object.


Syntax:

object_name = class_name

The dot(.) operator is used to call the functions.

Syntax:

object_name . function_name()

Class variable and Instance variable

• Class variable is defined in the class and can be used by all the instances of that class.

 Instance variable is defined in a method and its scope is only with in the object that
defines it.

10

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

 Every object of the class has its own copy of that variable. Any change made to the
variable don’t reflect in other objects of that class.

 Instance variables are unique for each instance, while class variables are shared by all
instances.

 Example:

class Order:

def _ _init_ _(self, coffee_name, price):

self.coffee_name = coffee_name

self.price = price

ram_order = Order("Espresso", 210)

print(ram_order.coffee_name)

print(ram_order.price)

paul_order = Order("Latte", 275)

print(paul_order.coffee_name)

print(paul_order.price)

• In this example, coffee_name and price are the class variables. ram_order and
paul_order are the two instances of this class. Each of these instances has their own
values set for the coffee_name and price instance variables.

• When ram’s order details are printed in the console, the values Espresso and 210 are
returned. When Paul’s order details are printed in the console, the values Latte and
275 are returned.

• This shows that, instance variables can have different values for each instance of the
class, whereas class variables are the same across all instances.

Constructor

A constructor is a special method used to create and initialize an object of a class.


This method is defined in the class.

The constructor is executed automatically at the time of object creation.

In python _ _init_ _() method is called as the constructor of the class. It is always
called when an object is created.

11

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

The primary responsibility of _ _init_ _ method is to establish the state of a newly


created object with appropriate instance variables.

Syntax:

def _ _init_ _(self):

#body of the constructor

Where,

def keyword is used to define function.

_ _init_ _() method: It is a reserved method. This method gets called as soon as an
object of a class is instantiated.

self: The first argument self refers to the current object. It binds the instance to the _
_init_ _() method. It is usually named self to follow the naming convention.

Example:

class student:

def __init__(self,rollno,name,age):

self.rollno=rollno

self.name=name

self.age=age

print("student object is created")

s=student(1091,"Amala",21)

print("Roll no of the student=",s.rollno)

print("Name of the student=",s.name)

print("Age of the student=",s.age)

Output:

Student object is created

Roll no of the student=1091

Name of the student=Amala

Age of the student=21

Types of constructors
12

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

• There are three types of constructors

1. Default Constructor

2. Non-Parameterized Constructor

3. Parameterized Constructor

 Python will provide a default constructor if no constructor is defined.

 Python adds a default constructor when the programmer does not include the
constructor in the class or forget to declare it.

 Default constructor does not perform any task but initializes the objects. It is an empty
constructor without a body.

Non-Parametrized Constructor

• A constructor without any arguments is called a non-parameterized constructor. This


type of constructor is used to initialize each object with default values.

 This constructor does not accept the arguments during object creation. Instead, it
initializes every object with the same set of values.

Example:

class Employee:

def _ _init_ _(self):

self.name = "Ramesh"

self.EmpId = 100456

def display(self):

print("Employee Name = ", self.name, " \nEmployee Id = ",self.EmpId)

emp = Employee()

emp.display()

Output:

Employee Name = Ramesh

Employee Id = 100456

Parameterized constructor

• Constructor with parameters is known as parameterized constructor.

13

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

• The first parameter to constructor is self that is a reference to the being constructed,
and the rest of the arguments are provided by the programmer. A parameterized
constructor can have any number of arguments.

Example:

class Employee:

def __init__(self, name, age, salary):

self.name = name

self.age = age

self.salary = salary

def display(self):

print(self.name, self.age, self.salary)

# creating object of the Employee class

emp1 = Employee('Banu', 23, 17500)

emp1.display()

emp2 = Employee('Jack', 25, 18500)

emp2.display()

Output:

Banu 23 17500

Jack 25 18500

Destructor

Destructor is a special method that is called when an object gets destroyed.

A class can define a special method called destructor with the help of __del()__. In
Python, destructor is not called manually but completely automatic, when the
instance(object) is about to be destroyed.

It is mostly used to clean up non memory resources used by an instance(object).

Example: For Destructor

class Student:

# constructor

def __init__(self, name):


14

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

print('Inside Constructor')

self.name = name

print('Object initialized')

def display(self):

print('Hello, my name is', self.name)

# destructor

def __del__(self):

print('Inside destructor')

print('Object destroyed')

# create object

s1 = Student('Raja')

s1.display()

# delete object

del s1

Output:

Inside Constructor

Object initialized

Hello, my name is Raja

Inside destructor

Object destroyed

ENCAPSULATION

• Encapsulation is one of the fundamental concepts in object-oriented programming.


Encapsulation in Python describes the concept of bundling data and methods within a
single unit. A class is an example of encapsulation as it binds all the data members
(instance variables) and methods into a single unit.

15

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

class Employee:

def __init__(self, name, salary, project):

# data members

self.name = name

self.salary = salary

self.project = project

# method to display employee's details

def display(self):

# accessing public data member

print("Name: ", self.name, 'Salary:', self.salary)

def work(self):

print(self.name, 'is working on', self.project)

# creating object of a class

emp = Employee('Jess', 40000, 'Infosys')

# calling public method of the class

emp.display()

emp.work()

Output:

16

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

Name: Jess Salary: 40000

Jess is working on Infosys

Advantages of Encapsulation

1. The main advantage of using encapsulation is the security of the data. Encapsulation
protects an object from unauthorized access.

2. Encapsulation hide an object’s internal representation from the outside called data
hiding.

3. It simplifies the maintenance of the application by keeping classes separated and


preventing them from tightly coupling with each other.

4. Bundling data and methods within a class makes code more readable and
maintainable.

OPERATOR OVERLOADING

Operator overloading means giving extended meaning beyond their predefined


operational meaning. For example, operator + is used to add two integers as well as
join two strings and merge two lists.

The same built-in operator or function shows different behavior for objects of
different classes, this is called operator overloading.

Example:

# add 2 numbers

print(100 + 200)

# concatenate two strings

print('Python' + 'Programming')

# merger two list

print([10, 20, 30] + ['Data Structures', 'And', 'Algorithms'])

Output:

300

PythonProgramming

[10, 20, 30, 'Data Structures', 'And', 'Algorithms']

The operator + is used to carry out different operations for distinct data types. This is
one of the simplest occurrences of polymorphism in Python.

17

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

To perform operator overloading, python provides some special function or magic


function that is automatically invoked when it is associated with the particular object.
If + operator is used, the magic method _add_ is automatically invoked.

Example:

class item:

def _ _init_ _(self, price):

self.price = price

# Overloading + operator with magic method

def _ _add_ _(self, other):

return self.price + other.price

b1 = item(400)

b2 = item(300)

print("Total Price: ", b1 + b2)

Output:

Total Price: 700

In this example, addition is implemented by a special method in python called the


_add_ method. When two integers are added together, this method is called to create a
new integer object.

INHERITANCE

• A natural way to organize various structural components of a software package is in a


hierarchical fashion.

 A hierarchical design is useful in software development, as common functionality can


be grouped at the most general level, thereby promoting reuse of code, while
differentiated behaviors can be viewed as extensions of the general case.

 In object-oriented programming the mechanism for a modular and hierarchical


organization is a technique known as inheritance. This allows a new class to be
defined based upon an existing class as the starting point.

 In object-oriented programming, the existing class is typically described as the base


class, parent class or super class, while the newly defined class is known as the
subclass or child class.

 There are two ways in which a subclass can differentiate itself from its superclass. A
subclass may specialize an existing behavior by providing a new implementation that

18

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

overrides an existing method. A subclass may also extend its superclass by providing
brand new methods.

 Inheritance is a mechanism through which we can create a class or object based on


another class or object. In other words, the new objects will have all the features or
attributes of the class or object on which they are based. It supports code reusability.

In Python, based upon the number of child and parent classes involved, there are five
types of inheritance. The types of inheritance are listed below:

i. Single inheritance

ii. Multiple Inheritance

iii. Multilevel inheritance

iv. Hierarchical Inheritance

v. Hybrid Inheritance

Single Inheritance

In single inheritance, a child class inherits from a single-parent class. Here is one
child class and one parent class.

Example:

# Base class

class Vehicle:

def Vehicle_info(self):

print('Inside Vehicle class')

# Child class

class Car(Vehicle):

def car_info(self):

print('Inside Car class')

19

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

# Create object of Car

car = Car()

# access Vehicle's info using car object

car.Vehicle_info()

car.car_info()

Output:

Inside Vehicle class

Inside Car class

Here class car inherits from class Vehicle.

Multiple Inheritance

• In multiple inheritance, one child class can inherit from multiple parent classes. So
here is one child class and multiple parent classes.

# Parent class 1

class Person:

def person_info(self, name, age):

print('Inside Person class')

print('Name:', name, 'Age:', age)

# Parent class 2

class Company:

def company_info(self, company_name, location):

print('Inside Company class')

print('Name:', company_name, 'location:', location)

# Child class

class Employee(Person, Company):

20

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

def Employee_info(self, salary, skill):

print('Inside Employee class')

print('Salary:', salary, 'Skill:', skill)

# Create object of Employee

emp = Employee()

# access data

emp.person_info('Jessica', 28)

emp.company_info('YouTube', ' California ')

emp.Employee_info(12000, 'Cloud Computing')

Output:

Inside Person class

Name: Jessica Age: 28

Inside Company class

Name: YouTube location: California

Inside Employee class

Salary: 12000 Skill: Cloud Computing

Multilevel Inheritance

• In multilevel inheritance, a class inherits from a child class or derived class. Suppose
three classes A, B, C. A is the superclass, B is the child class of A, C is the child class
of B. In other words, a chain of classes is called multilevel inheritance.

# Base class

class Vehicle:

def Vehicle_info(self):

print('Inside Vehicle class')

# Child class

class Car(Vehicle):

def car_info(self):

print('Inside Car class')

21

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

# Child class

class SportsCar(Car):

def sports_car_info(self):

print('Inside SportsCar class')

# Create object of SportsCar

s_car = SportsCar()

# access Vehicle's and Car info using SportsCar object

s_car.Vehicle_info()

s_car.car_info()

s_car.sports_car_info()

Output:

Inside Vehicle class

Inside Car class

Inside SportsCar class

Hierarchical Inheritance

• In Hierarchical inheritance, more than one child class is derived from a single parent
class. In other words, one parent class and multiple child classes.

class Vehicle:

def info(self):

print("This is Vehicle")

class Car(Vehicle):

def car_info(self, name):


22

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

print("Car name is:", name)

class Truck(Vehicle):

def truck_info(self, name):

print("Truck name is:", name)

obj1 = Car()

obj1.info()

obj1.car_info('BMW')

obj2 = Truck()

obj2.info()

obj2.truck_info('Ford')

Output:

This is Vehicle

Car name is: BMW

This is Vehicle

Truck name is: Ford.

Hybrid Inheritance

• When inheritance consists of multiple types or a combination of different inheritance


then it is called hybrid inheritance.

class Vehicle:

def vehicle_info(self):

print("Inside Vehicle class")

class Car(Vehicle):

def car_info(self):

print("Inside Car class")


23

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

class Truck(Vehicle):

def truck_info(self):

print("Inside Truck class")

# Sports Car can inherits properties of Vehicle and Car

class SportsCar(Car, Vehicle):

def sports_car_info(self):

print("Inside SportsCar class")

# create object

s_car = SportsCar()

s_car.vehicle_info()

s_car.car_info()

s_car.sports_car_info()

Output:

Inside Vehicle class

Inside Car class

Inside SportsCar class

SHALLOW AND DEEP COPYING

• In some applications, it is necessary to subsequently modify either the original or


copy in an independent manner.

• Consider an application to manage various lists of colors. Each color is represented by


an instance of a presumed color class. warmtones is an identifier denote an existing
list of such colors (e.g., oranges, browns).
24

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

• In this application, we wish to create a new list named palette, which is a copy of the
warmtones list and subsequently wanted to add additional colors to palette,
or to modify or remove some of the existing colors, without affecting the contents of
warmtones.

• If we execute the command,

• palette = warmtones

• This creates an alias as shown in figure and no new list is created. Instead, the new
identifier palette references the original list. If we add or remove colors from palette,
it will change the wormtones list also.

INTRODUCTION TO ANALYSIS OF ALGORITHMS

• To bake a cake, we can get different recipes from the internet. We can find ‘n’
number of steps for different varieties of cakes. All those different step by step
procedure to make a cake can be called as an algorithm. We can choose a simple, easy
and most convenient way to make a cake.

• Similarly in computer science, multiple algorithms are available for solving the same
problem. The algorithm analysis helps us to determine which algorithm is most
efficient in terms of running time, memory and space consumed. etc.,

25

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

• The most straightforward reason for analyzing an algorithm is to discover its


characteristics in order to evaluate its suitability for various applications or compare it
with other algorithms for the same application. Moreover, the analysis of an algorithm
can help us to understand it better, and can suggest informed improvements.
Algorithms tend to become shorter, simpler, and more elegant during the analysis
process.

• The efficiency of an algorithm can be decided based on

1. Amount of time required by an algorithm to execute.

2. Amount of storage required by an algorithm.

3. Size of the input set.

Complexities of an Algorithm

• The complexity of an algorithm computes the amount of time and spaces required by
an algorithm for an input of size (n). The complexity of an algorithm can be two
types. The time complexity and the space complexity.

Time Complexity of an Algorithm

Time complexity measures the amount of time required to run an algorithm, as input size of
the algorithm increases.

Space Complexity of an Algorithm

Space complexity measures the total amount of memory that an algorithm or operation needs
to run according to its input size.

ASYMPTOTIC NOTATIONS

Asymptotic notation is one of the most efficient ways to calculate the time complexity of an
algorithm.

Asymptotic notations are mathematical tools to represent time complexity of algorithms for
asymptotic analysis.

The three asymptotic notations used to represent time complexity of algorithms are,

The Big O (Big-Oh) Notation

The Big Ω (Big-Omega) Notation

The Big q (Big-Theta) Notation

The Big (O) Notation

Big Oh is an Asymptotic Notation for the worst-case scenario. The Big-O notation defines
asymptotic upper bound of an algorithm, it bounds a function only from above.

26

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

It is represented as f(n) = O(g(n)). That is, at higher values of n, the upper bound of f(n) is
g(n).

The Mathematical Definition of Big-Oh

f(n) ∈ O(g(n)) if and only if there exist some positive constant c and some non-negative
integer n₀ such that, f(n) ≤ c g(n) for all n ≥ n₀, n₀ ≥ 1 and c>0.

The above definition says, in the worst case, let the function f(n) be the algorithm's runtime,
and g(n) be an arbitrary time complexity. Then O(g(n)) says that the function f(n) never
grows faster than g(n) that is f(n)<=g(n) and g(n) is the maximum number of steps that the
algorithm can attain.

In the above graph c. g(n) is a function that gives the

maximum runtime (upper bound) and f(n) is the

algorithm’s runtime.

The Big Omega (W) notation

• Big Omega is an Asymptotic Notation for the best-case scenario. Big W notation
defines an asymptotic lower bond.

• The Mathematical Definition of Big-Omega

• f(n) ∈ Ω(g(n)) if and only if there exist some positive constant c and some non-
negative integer n₀ such that, f(n) ≥ c g(n) for all n ≥ n₀, n₀ ≥ 1 and c>0.

• The above definition says, in the best case, let the function f(n) be the algorithm’s
runtime and g(n) be an arbitrary time complexity. Then Ω(g(n)) says that the
function g(n) never grows more than f(n) i.e. f(n)>=g(n), g(n) indicates
the minimum number of steps that the algorithm will attain.

• In the above graph, c.g(n) is a function that gives the minimum runtime (lower
bound) and f(n) is the algorithm’s runtime.

27

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

The Big Theta (q) Notation

• Big Theta is an Asymptotic Notation for the average case, which gives the average
growth for a given function. Theta Notation is always between the lower bound and
the upper bound. It provides an asymptotic average bound for the growth rate of an
algorithm. If the upper bound and lower bound of the function give the same result,
then the Θ notation will also have the same rate of growth.

• The Mathematical Definition of Big-Theta

• f(n) ∈ q (g(n)) if and only if there exist some positive constant c₁ and c₂ some non-
negative integer n₀ such that, c₁ g(n) ≤ f(n) ≤ c₂ g(n) for all n ≥ n₀, n₀ ≥ 1 and c>0.

DIVIDE AND CONQUER

• The divide and conquer algorithm work by recursively breaking down a problem into
two or more sub problems of the same type, until they become simple enough to be
solved directly. The solutions to the sub problems are then combined to give a
solution to the original problem.

• The divide-and-conquer algorithm have three parts. They are,

i. Divide the problem into a number of subproblems that are smaller instances of the
same problem.

ii. Conquer the subproblems by solving them recursively. If they are small enough,
solve the subproblems as base cases.

28

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

iii. Combine the solutions to the subproblems into the solution for the original problem.

Example: Merge Sort


 Merge sort is an example of divide and conquer method.
 Divide - break the problem into several subproblems that are similar to the original
problem but smaller in size.
 Conquer - solve the subproblems recursively.
 It is easier to solve than trying to tackle the whole problem at once because the
divided problems are smaller.
 Finally, it merges the solved smaller problems.
Algorithm
1. Divide the given list of elements into two halves.
2. Recursively sort the first half and second half of list elements.
3. Take two input lists A and B, an output list C.
4. The first element of A list and B list are compared, then the smaller element is stored
in the output list C, the corresponding pointer is incremented.
5. This step is repeated until all the elements are compared.

29

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

• Advantages of Divide and Conquer Algorithm

 It efficiently uses cache memory without occupying much space because it solves
simple subproblems within the cache memory instead of accessing the slower main
memory.

 It’s more effective than the Brute Force approach.

 Divide and conquer problem allows to solve the sub problems independently. This
allows for execution of subproblems in different processors.

Disadvantages of Divide and Conquer

Recursion is incorporated into the majority of divide and conquer algorithms; hence it
requires intensive memory management.

 The space could be overused by an explicit stack.

 If the recursion is carried through rigorously beyond the CPU stack, the system can
even crash.

Applications

 Binary Search

 Merge Sort and Quick Sort

 Min and Max finding

 Matrix multiplication

 Closest pair Problem

RECURSION

• Recursion is a technique by which a function makes one or more calls to itself during
execution.

• In computing, recursion provides an elegant and powerful alternative for performing


repetitive tasks. When one invocation of the function makes a recursive call, that
invocation is suspended until the recursive call completes.

30

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

• The factorial function

• The factorial function is a classic mathematical function that has a natural recursive
definition.

• The factorial of a positive integer n, is defined as the product of the integers from 1 to
n. if n = 0, then n! is defined as 1.

• For example, 5! = 5 . 4 . 3 . 2 . 1 = 120 and note that 5 ! = 5. (4.3.2.1) = 5. 4!.


Generally, for a positive integer n, we can define n ! = n. (n – 1) !.

• In this case, n = 0 is the base class. It is defined non recursively is terms of fixed
quantities. n (n-1)! is a recursive case.

• Recursive implementation of the factorial function

• A recursive implementation of the factorial function is,

def factorial(n):
if n = = 0:
return 1
else:
return n*factorial(n-1)
This function repetition is provided by the repeated recursive invocation of the
function. When the function is invoked, its argument is smaller by one and when a
base case is reached, no further recursive calls are made.

Binary Search

• When the sequence is unsorted the standard approach for searching a target value is
sequential search. When the sequence is sorted and indexable, then binary search is
used to efficiently locate a target value within a sorted sequence of n elements.

• For any index j, all the values stored at indices 0 to j-1 are less than or equal to the
value at index j, and all the values stored at indices j+1 to n-1 are greater than equal to
that at index j. This allows us to quickly search target value.

• The algorithm maintains two parameters, low and high, such that all the candidate
entries have index at least low and at most high. Initially, low = 0 and high = n−1.
Then we compare the target value to the median candidate, that is, the item data[mid]
with index mid = (low+high)/2.

Consider three cases:

If the target equals data[mid], then we have found the item, and the search terminates
successfully.

31

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

 If target < data[mid], then we recur on the first half of the sequence, that is, on the
interval of indices from low to mid−1.

 If target > data[mid], then we recur on the second half of the sequence, that is, on the
interval of indices from mid+1 to high.

An unsuccessful search occurs if low > high, as the interval [low, high] is empty. This
algorithm is known as binary search.

Implementation

def binarysearch(data, target, low, high):


if low > high:
return False
else:
mid = (low + high) // 2
if target == data[mid]:
return True
elif target < data[mid]:
return binarysearch(data, target, low, mid − 1)
else:

return binarysearch(data, target, mid + 1, high)

This binary search algorithm requires O(log n) time. Where as the sequential search
algorithm uses O(n) time.

ANALYZING RECURSIVE ALGORITHMS

Computing factorials

• To compute factorials (n), there are a total of n + 1 activations. And each individual
activations of factorials execute a constant number of operations. Therefore, we
conclude that the overall number of operations for computing factorial (n) is O(n) as
there are n+1 activations, each of which accounts for O (1) operations.

32

Downloaded by Rama Chandrankavi ([email protected])


lOMoARcPSD|12131744

Drawing an English Ruler

• The efficiency of an English ruler algorithm depends on the number of lines that are
generated by an initial call draw _ interval(c), where c denotes the center length. Each
line of output is based upon a call to the draw_line utility, and each recursive call to
draw_interval makes exactly one call to draw_line, unless 0.

• A call to draw_interval(c) for c>0 spawns two calls to draw_interval (c-1) and a single
call to draw_line. For c ≥ 0, a call to draw_interval (c ) results in precisely 2c – 1 lines
of output.

33

Downloaded by Rama Chandrankavi ([email protected])

You might also like