Introduction to Data Structures_unit1
Introduction to Data Structures_unit1
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.
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.
The data are arranged in a sequential fashion. In the memory the arrangement may not be
sequential (in list)
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.
The content of the data structures can be modified without changing the memory space
allocated to it. Example: Array
In dynamic data structures the size of the structure is not fixed and can able to grow as well
shrink dynamically.
Examples: array ADT, 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.
• They are independent, since the implementation details are not visible, the rest of the program
is independent of the ADT, we can modify it without affecting the entire code.
• Modularity: the program is less dependent on the implementation of the ADT, it is easier to
spot bugs (errors) that belong to the ADT or rest of the program and treat them separately.
• ADTs provide well defined interfaces for interacting with the implementing code.
• ADTs increases the understandability of the code.
• Each operation associated with the ADT is implemented by a member function or method.
Class: A class Is a template for creating object. It defines the structure and behavior (attributes
and methods) the objects of that class will possess. Declaring a class does not allocate memory.
Memory is allocated when an object is associated with the class.
Objects: objects are instances of a class when we create an object from a class, it creates a specific
instance of that template with its own set of attribute values.
Example1: Example2
Class: Student
Attribute/variables/members:
Register_no
Name
Gender
Dob
Dept
Functions/Methods/member functions:
Update()
Modify()
Display()
INTRODUCTION TO OBJECT ORIENTED PROGRAMMING
• 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.
• 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.
1. Robustness
2. Adaptability
3. Reusability
Robustness:
• Every programmer wants to develop software that is correct which means that a program
produces the right output for all the input in the program.
• In addition, we want software to be robust, (i.e) capable of handling unexpected inputs. Eg. If
program excepts +ve integer and instead is given -ve integer, then the program should be able
to recover from the error.
• In life-critical applications, where a software error can lead to injury/loss of life, software that
is not robust eg. Therac-25, a radiation-therapy machine, which severely overdosed six
patients between 1985 and 1987, some of whom died from complications resulting from their
radiation overdose. All six accidents were traced to software errors.
Adaptability:
Reusability:
• The same code should be usable as a component of different systems in various applications.
• Developing quality software can be an expensive. And the expense can be reduced to some
extent if the software is built to be easily reused in future applications.
• Such reuse should be done with care, however, for one of the major sources of software errors
in the Therac-25 came from inappropriate reuse of Therac-20 software.
Object-Oriented Design Principles:
Chief among the principles of the object-oriented approach, which are intended to facilitate the goals
(Robustness, Adaptability, Reusability) outlined, are the following:
1. Modularity
2. Abstraction
3. Encapsulation
Modularity:
Modern software systems consist of several different components that must interact correctly in
order for the entire systems to work properly. In python, Module is a collection of closely related
functions and classes that are defined together in a single file of source code. Eg. Math module.
Abstraction:
• Abstraction allows dealing with the complexity of the object. And picking out the relevant
details of the object, and ignoring the non-essential details.
• Applying the abstraction paradigm to the design of data structures gives rise to abstract
data types (ADTs).
• 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 directly create an instance of that class, but it defines
one or more common methods.
• 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:
• It means information hiding. That is bundling attributes and methods that operate on the
data into s single unit typically a class.
• It restricts the access to some of the object’s components to ensure the integrity.
• Data hiding: encapsulation hides the internal details of an object, exposing only what is
necessary. This is achieved by access modifier.
The design pattern falls into two group. They are design pattern for solving algorithm design
problems and design pattern for solving software engineering problems.
Design pattern
For solving algorithm design problems For solving software engineering problems
Recursion Iterator
Amortization (A method for analyzing the Locator
performance of an algorithm over a sequence
of operation)
Divide and conquer ( Position
Prune and search (reducing something by Composition
removing which is not necessary-eg. Find kth
smallest element)
Broute force (direct approach/straight forward Adaptor
approach)
Greedy method (finding out all the feasible Template method
solutions and then choose the optimal one)
Dynamic programming (various operations to
be determined and executed at runtime)
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.
• 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).
• 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("jim",123,2000012)
c.display()
The Self Identifier and Self Parameter
The self parameter is a reference to the current instance of the class, and is used to access
variables that belong to the class.
It does not have to be named self, you can call it whatever you like, but it has to be the first
parameter of any function in the class:
Example: self.custName, self.custID, self.custAccNo
Creating Object:
• 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.
• We can give any name to a newly created object.
Syntax:
object_name = class_name
Types of constructors
1. Default constructor
2. Non- Parameterized constructor
3. Parameterized constructor
Default 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-Parameterized 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 = "Jim"
self.EmpId = 100
def display(self):
print("Employee Name = ", self.name, " \nEmployee Id = ",self.EmpId)
emp = Employee()
emp.display()
Parameterized Constructor:
• Constructor with parameters is known as parameterized constructor.
• 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('jim', 23, 25000)
emp1.display()
emp2 = Employee('tom', 25, 30000)
emp2.display()
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:
class Student:
def __init__(self, name): # constructor
self.name = name
def display(self):
print('Hello, my name is', self.name) # destructor
def __del__(self):
print('Object destroyed')
class Employee:
def __init__(self, name, salary, project):
# data members
self.name = name
self.salary = salary
self.project = project
Output:
Encapsulation can be achieved by declaring the data members and methods of a class either as private
or protected. But in Python, there is no direct access modifiers like public, private, and protected. This
can be achieved by using single underscore and double underscores. Single underscore represents the
protected and double underscore represents the private members.
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.
Public Member
Public data members are accessible within and outside of a class. All member variables of the class are
by default public.
Example:
class Employee:
def __init__(self, name, salary): # constructor
self.name = name # public data members
self.salary = salary
def display(self): # public instance methods
print("Name: ", self.name, 'Salary:', self.salary) # accessing public data member
emp = Employee('Jim', 40000) # creating object of a class
print("Name: ", emp.name, 'Salary:', emp.salary) # accessing public data members
emp.display() # calling public method of the class
Output:
Name: Jim Salary: 40000
Name: Jim Salary: 40000
Private Member
The variables can be protected in the class by marking them as private. To define a private member,
prefix the variable name with two underscores. Private members are accessible only within the class.
Example: Access private member outside of a class using an instance method.
class Employee:
def __init__(self, name, salary): # constructor
self.name = name # public data member
self.__salary = salary # private member
def display(self): # public instance methods
print("Name: ", self.name, 'Salary:', self.__salary) # private members are accessible from a class
emp = Employee('Jim', 40000)
emp.display()
Output:
Name: Jim Salary: 40000
Protected Member
Protected members are accessible within the class and also available to its sub-classes. To define a
protected member, prefix the member name with a single underscore.
Protected data members are used in inheritance and to allow data members access to only child
classes.
Example: Protected member in inheritance.
class Company:
def _ _init_ _(self):
self._project = "Infosys" # Protected member
class Employee(Company):
def _ _init_ _(self, name):
self.name = name
Company. _ _init_ _(self)
def display(self):
print ("Employee name:", self.name)
print ("Working on project:", self._project) # Accessing protected member in child class
c = Employee("Jim")
c.display ()
print ('Project:', c._project) # Direct access protected data member
Output:
Employee name: Jim
Working on project: Infosys
Project: Infosys
INHERITANCE
Inheritance in Python is a fundamental concept in object-oriented programming (OOP) that allows
one class (called the child class or derived class) to inherit attributes and methods from another
class (called the parent class or base class).
This promotes code reuse, modularity, and a hierarchical structure in programming. The followings
are the categories of the inheritance.
1. Single inheritance
2. Multiple inheritance
3. Multilevel inheritance
4. Hierarchical inheritance
5. 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.
parent class is also called as base class/super class. Child class is also called as derived class/sub class.
Example:
class Vehicle: # Base class
def Vehicle_info(self):
print('Inside Vehicle class')
class Car(Vehicle): # Child class
def car_info(self):
print('Inside Car class')
car = Car() # Create object of Car
car.Vehicle_info() # access Vehicle's info using car object
car.car_info()
Output:
Multiple Inheritance
In multiple inheritance, one child class can inherit from multiple parent classes. So here is one child
class and multiple parent classes.
In multiple inheritance, one child class can inherit from multiple parent classes. So here is one child
class and multiple parent classes.
class Person: # Parent class 1
def person_info(self, name, age):
print('Inside Person class')
print('Name:', name, 'Age:', age)
class Company: # Parent class 2
def company_info(self, company_name, location):
print('Inside Company class')
print('Name:', company_name, 'location:', location)
class Employee(Person, Company): # Child class
def Employee_info(self, salary, skill):
print('Inside Employee class')
print('Salary:', salary, 'Skill:', skill)
emp = Employee() # Create object of Employee
emp.person_info('Jessica', 28) # access data
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
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.
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.
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):
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")
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
Namespace
Namespace: Namespace is a mapping from names to object, and its functionality like dictionary,
it has two attributes one is key and another one is value. In this scenario object is a key and value
is the object themselves. It ensures the unique name for all the attributes and function with in its
context. It prevents naming conflicts and defining scope, which determine where a particular name
can be accessed and used with a program.
Types of Namespaces:
When the variable is not inside a function or a class, it’s accessible from anywhere in the program.
These variables are called global variables.
Eg. output
X=300 300
def fun1(): 300
print(X)
def fun2():
print(X)
obj.fun1()
obj.fun2()
Local Namespace: It creates a new and dedicated namespace whenever methods (functions) are
invoked. It is local to the method and exists within that method. (functions).
When a variable is defined inside a function or a class then it’s accessible only inside it. They are
called local variables and their scope is only limited to that function or class boundary.
SHALLOW AND DEEP COPYING
• 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.
• 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.
Shallow Copy
• In shallow copy, the new list is initialized so that its contents are precisely same as the
original sequence.
• It creates a new instance of the list. This is known as the shallow copy.
• The new list represents a sequence of references to the same elements as in the first. We
can add or remove elements from palette without affecting warmtones.
• However, if we edit a color instance from the palette list, we effectively change the contents
of warmtones. Although palette and warmtones are distinct lists, there remains indirect
aliasing.
Deep Copy
• In a deep copy, the new copy references its own copies of those objects referenced by the
original version. In this application, we prefer that palette as a deep copy of warmtones.
• If we execute a command,
• It will explicitly make a copy of the original color instance rather than aliasing.
Shallow Copy: Copies the top-level structure of an object or array, but nested elements are shared
references with the original.
Deep Copy: Creates a completely new object or array and recursively copies all nested objects and
arrays, ensuring no shared references with the original.
Shallow copy is suitable when we want to duplicate simple data structures or when we intentionally
want shared references to nested objects. It’s faster to create compared to deep copy and sufficient
for many use cases where immutability of nested objects is not critical.
Deep copy is necessary when we need to ensure that modifications to the copied object do not affect
the original or when working with complex nested data structures that require independent copies. It
ensures data integrity by creating completely separate copies of nested elements.