0% found this document useful (0 votes)
5 views20 pages

Introduction to Data Structures_unit1

Data structures are methods for storing and organizing data efficiently in computers, categorized into primitive and non-primitive types. They enable effective data management, fast searching, and sorting, with examples including arrays and trees. Object-oriented programming further utilizes classes and abstract data types to encapsulate data and behavior, promoting robustness, adaptability, and reusability in software design.
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)
5 views20 pages

Introduction to Data Structures_unit1

Data structures are methods for storing and organizing data efficiently in computers, categorized into primitive and non-primitive types. They enable effective data management, fast searching, and sorting, with examples including arrays and trees. Object-oriented programming further utilizes classes and abstract data types to encapsulate data and behavior, promoting robustness, adaptability, and reusability in software design.
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/ 20

INTRODUCTION TO DATA STRUCTURES

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.

Types of data structures:

Data structures can be classified into two categories.


i) Primitive Data structures
ii) Non-Primitive Data structures
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.
Linear Data Structures

The data are arranged in a sequential fashion. In the memory the arrangement may not be
sequential (in list)

Examples: Arrays, linked lists, stack and queues.

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.

Static Data Structures

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

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

Dynamic Data Structures

In dynamic data structures the size of the structure is not fixed and can able to grow as well
shrink dynamically.

Example: Linked list.

ABSTRACT DATA TYPES (ADTs)


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: 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.

Advantages of using ADTs

• 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.

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.

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.

Object-oriented design goal:


Software implementation should achieve the followings:

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:

• Software needs to be able to evolve overtime in response to changing conditions in its


environment. Thus, another important goal of quality software is that it achieves adaptability.
• Related to this concept is portability, which is the ability of software to run with minimal
changes on hardware and OS.

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.

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. 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.

S.No. Access Modifier Purpose


1. Public Accessed from anywhere
2. Protected Indicated by single underscore (_),
accessed with in the class and its sub
classes.
3. Private Indicated by double underscore (_ _),
accessed with in the class.
Object Oriented Design Patterns:
• 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.
• It consists of a name – which identifies the pattern
• A context -which descries the scenarios for which this pattern can be applied.
• A template- which describes how pattern in applied
• A result – which describes and analyzes what pattern produces.

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

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.
• 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.
Constructor:
A constructor is a special method used to create and initialize an object of a class. This method is
defined in the class.
Constructor is activated whenever object is created for the class.
In python _ _init_ _() method is called as the constructor of the class. It is always called when an
object is created.
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.
Types of constructors:

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')

s1 = Student('Raja') # create object


s1.display()
del s1 # delete object
ENCAPSULATION:
• Encapsulation is one of the fundamental concepts in OOPS. Encapsulation in Python describes
the concept of bundling data and method 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.

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('Jim', 40000, 'Infosys')

# calling public method of the class


emp.display()
emp.work()

Output:

Name: Jim Salary: 40000


Jim is working on Infosys

Data hiding using access modifiers:

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:

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.

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

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.

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.

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')
class SportsCar(Car): # Child class
def sports_car_info(self):
print('Inside SportsCar class')
s_car = SportsCar() # Create object of SportsCar
s_car.Vehicle_info() # access Vehicle's and Car info using SportsCar object
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):
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:

1. Built-in Namespaces (built-in libraries).


2. Global Namespaces (in module).
3. Local Namespaces (in methods).

Built-in Namespace: it contains the names of all built-in objects.


This namespace is available as long as the interpreter running,
So, we can access the names that live in this namespace at any time
in our code without explicitly importing them. Eg. print(), len(),
Global Namespace: it contains the names defined at the module level. Each module has its own
global namespace for any module that our program loads with the import statement.

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 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).

• 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.

Fig. warmtones alias palette

Shallow Copy

• In shallow copy, the new list is initialized so that its contents are precisely same as the
original sequence.

Fig. A Shallow copy of a list of colors

• If we execute the command,

palette = list (warmtones)

• 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,

palette = copy. deepcopy(warmtones)

• It will explicitly make a copy of the original color instance rather than aliasing.

Fig. A deep copy of a list of colors

What is the difference between shallow and deep copy?

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.

When should we use shallow copy?

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.

When is deep copy necessary?

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.

You might also like