Qbank DSD Cia-1
Qbank DSD Cia-1
1. Abstract Data type (ADT) is a type (or class) for objects whose behavior is defined by a
set of values and a set of operations. 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.
2. In Python object-oriented Programming (OOPs) is a programming paradigm that uses
objects and classes in programming. It aims to implement real-world entities like
inheritance, polymorphisms, encapsulation, abstraction,etc. in the programming. The
main concept of object-oriented Programming (OOPs) or oops concepts in Python is to
bind the data and the functions that work together as a single unit so that no other part of
the code can access this data.
3. In Python, a class is a user-defined data type that contains both the data itself and the
methods that may be used to manipulate it. In a sense, classes serve as a template to create
objects. They provide the characteristics and operations that the objects will employ.
4. Objects are key to understanding object-oriented technology. The purpose of the object-
oriented programming is to implement the real word entities in programming. It also
emphasis on the binding of data. There are various OOPs concepts among them Object is
one of them.
5. One of the core concepts in object-oriented programming (OOP) languages is
inheritance. It is a mechanism that allows you to create a hierarchy of classes that share a
set of properties and methods by deriving a class from another class. Inheritance is the
capability of one class to derive or inherit the properties from another class.
6. The advantages of using classes in python are,
• Cross-Platform Compatibility
• Strong Community Support
• Integration and Extensibility
• Scalability and Performance
• Versatility and Flexibility
7.
• Need not remember or specify the parent class name to access its methods. This
function can be used both in single and multiple inheritances.
• This implements modularity (isolating changes) and code reusability as there is no
need to rewrite the entire function.
• A shallow copy means constructing a new collection object and then populating it with
references to the child objects found in the original. In essence, a shallow copy is only one
level deep. The copying process does not recurse and therefore won’t create copies of the
child objects themselves. Making a shallow copy of an object won’t clone child objects.
Therefore, the copy is not fully independent of the original.
10. The main idea of asymptotic analysis is to have a measure of the efficiency of algorithms
that don’t depend on machine-specific constants and don’t require algorithms to be implemented
and time taken by programs to be compared.There are mainly three asymptotic notations:
1. Big-O Notation (O-notation)
2. Omega Notation (Ω-notation)
3. Theta Notation (Θ-notation)
• Recursion can be less efficient than iterative solutions in terms of memory and
performance.
• Recursive functions can be more challenging to debug and understand than iterative
solutions.
• Recursion can lead to stack overflow errors if the recursion depth is too high.
12. The data is generally stored in key sequence in a list which has a head structure consisting
of count, pointers and address of compare function needed to compare the data in the list.
• The data node contains the pointer to a data structure and a self-referential
pointer which points to the next node in the list.
13. The basic operations in the Arrays are insertion, deletion, searching, display, traverse, and
update. These operations are usually performed to either modify the data in the array or to report
the status of the array
14. A linked list is a data structure that stores a sequence of elements. Each element in the list is
called a node, and each node has a reference to the next node in the list. The first node in the list is
called the head, and the last node in the list is called the tail.
PART-B
16.a) An Abstract Data Type (ADT) is a programming concept that defines a high-level view of a
data structure, without specifying the implementation details. In other words, it is a blueprint for
creating a data structure that defines the behavior and interface of the structure, without specifying
how it is implemented.
An ADT in the data structure can be thought of as a set of operations that can be performed on a
set of values. This set of operations actually defines the behavior of the data structure, and they
are used to manipulate the data in a way that suits the needs of the program.
ADTs are often used to abstract away the complexity of a data structure and to provide a simple
and intuitive interface for accessing and manipulating the data. This makes it easier for
programmers to reason about the data structure, and to use it correctly in their programs.
Examples of abstract data type in data structures are List, Stack, Queue, etc.
List ADT
Lists are linear data structures that hold data in a non-continuous structure. The list is made up of
data storage containers known as "nodes." These nodes are linked to one another, which means
that each node contains the address of another block. All of the nodes are thus connected to one
another via these links. You can discover more about lists in this article: Linked List Data
Structure.
Some of the most essential operations defined in List ADT are listed below.
• front(): returns the value of the node present at the front of the list.
• back(): returns the value of the node present at the back of the list.
• push_front(int val): creates a pointer with value = val and keeps this pointer to the front
of the linked list.
• push_back(int val): creates a pointer with value = val and keeps this pointer to the back
of the linked list.
• size(): returns the number of nodes that are present in the list.
Stack ADT
A stack is a linear data structure that only allows data to be accessed from the top. It simply has
two operations: push (to insert data to the top of the stack) and pop (to remove data from the
stack). (used to remove data from the stack top).
Some of the most essential operations defined in Stack ADT are listed below.
• top(): returns the value of the node present at the top of the stack.
• push(int val): creates a node with value = val and puts it at the stack top.
• size(): returns the number of nodes that are present in the stack.
Queue ADT
A queue is a linear data structure that allows data to be accessed from both ends. There are two
main operations in the queue: push (this operation inserts data to the back of the queue) and pop
(this operation is used to remove data from the front of the queue).
Some of the most essential operations defined in Queue ADT are listed below.
• front(): returns the value of the node present at the front of the queue.
• back(): returns the value of the node present at the back of the queue.
• push(int val): creates a node with value = val and puts it at the front of the queue.
• size(): returns the number of nodes that are present in the queue.
• Provides abstraction, which simplifies the complexity of the data structure and allows
users to focus on the functionality.
• Enables code reusability as the same data structure can be used in multiple programs with
the same interface.
• Promotes the concept of data hiding by encapsulating data and operations into a single
unit, which enhances security and control over the data.
• Supports polymorphism, which allows the same interface to be used with different
underlying data structures, providing flexibility and adaptability to changing requirements.
• Overhead: Using ADTs may result in additional overhead due to the need for abstraction
and encapsulation.
• Limited control: ADTs can limit the level of control that a programmer has over the data
structure, which can be a disadvantage in certain scenarios.
Conclusion
Abstract Data Types (ADTs) stand as a cornerstone in computer science, offering a high-level
blueprint for organizing and manipulating data. By encapsulating data and operations, ADTs
enable developers to create robust, reusable, and scalable solutions without concerning themselves
with intricate implementation details.
In essence, ADTs provide a bridge between the theoretical and practical realms, fostering the
development of versatile data structures and algorithms. Embracing ADTs empowers
programmers to focus on problem-solving and efficient design while enhancing the
maintainability and flexibility of their codebases.
As technology evolves, the significance of ADTs persists, guiding the creation of innovative
software systems and ensuring data is managed and manipulated effectively. Understanding and
harnessing the power of ADTs is key to navigating the complexities of modern computing.
16.b)
The class creates a user-defined data structure, which holds its own data members and member
functions, which can be accessed and used by creating an instance of that class. A class is like a
blueprint for an object.
Some points on Python class:
• Classes are created by keyword class.
• Attributes are the variables that belong to a class.
• Attributes are always public and can be accessed using the dot (.) operator. Eg.: My
class.Myattribute
Creating a Python Class
Here, the class keyword indicates that you are creating a class followed by the name of the class
(Dog in this case).
Python3
class Dog:
sound = "bark"
Object of Python Class
An Object is an instance of a Class. A class is like a blueprint while an instance is a copy of the
class with actual values. It’s not an idea anymore, it’s an actual dog, like a dog of breed pug who’s
seven years old. You can have many dogs to create many different instances, but without the class
as a guide, you would be lost, not knowing what information is required.
An object consists of:
• State: It is represented by the attributes of an object. It also reflects the properties of
an object.
• Behavior: It is represented by the methods of an object. It also reflects the response
of an object to other objects.
• Identity: It gives a unique name to an object and enables one object to interact with
other objects.
# A simple class
# attribute
attr1 = "mammal"
attr2 = "dog"
# A sample method
def fun(self):
print("I'm a", self.attr1)
print("I'm a", self.attr2)
# Driver code
# Object instantiation
Rodger = Dog()
def show(self):
print("Hello my name is " + self.name+" and I" +
" work in "+self.company+".")
def show(somename):
print("Hello my name is " + somename.name +
" and I work in "+somename.company+".")
# Sample Method
def say_hi(self):
print('Hello, my name is', self.name)
p = Person('Nikhil')
p.say_hi()
Output:
Hello, my name is Nikhil
Explanation:
In this example, we are creating a Person class and we have created a name instance variable in
the constructor. We have created a method named as say_hi() which returns the string “Hello, my
name is {name}”.We have created a person class object and we pass the name Nikhil to the
instance variable. Finally, we are calling the say_hi() of the class.
__str__() method
Python has a particular method called __str__(). that is used to define how a class object should
be represented as a string. It is often used to give an object a human-readable textual
representation, which is helpful for logging, debugging, or showing users object information.
When a class object is used to create a string using the built-in functions print() and str(),
the __str__() function is automatically used. You can alter how objects of a class are represented
in strings by defining the __str__() method.
Python3
class GFG:
def __init__(self, name, company):
self.name = name
self.company = company
def __str__(self):
return f"My name is {self.name} and I work in {self.company}."
class Dog:
# Class Variable
animal = 'dog'
# Instance Variable
self.breed = breed
self.color = color
# Objects of Dog class
Rodger = Dog("Pug", "brown")
Buzo = Dog("Bulldog", "black")
print('Rodger details:')
print('Rodger is a', Rodger.animal)
print('Breed: ', Rodger.breed)
print('Color: ', Rodger.color)
print('\nBuzo details:')
print('Buzo is a', Buzo.animal)
print('Breed: ', Buzo.breed)
print('Color: ', Buzo.color)
class Dog:
# Class Variable
animal = 'dog'
# Instance Variable
self.breed = breed
# Driver Code
Rodger = Dog("pug")
Rodger.setColor("brown")
print(Rodger.getColor())
Output:
brown
Explanation:
In this example, We have defined a class named Dog and we have created a class variable animal.
We have created an instance variable breed in the constructor. The class Dog consists of two
methods setColor and getColor, they are used for creating and initializing an instance variable
and retrieving the value of the instance variable. We have made an object of the Dog class and
we have set the instance variable value to brown and we are printing the value in the terminal.
Syntax:
class subclass_name : access_mode base_class
{
// body of subclass
};
OR
class A
{
... .. ...
};
class B: public A
{
... .. ...
};
• CPP
// C++ program to explain
// Single inheritance
#include<iostream>
using namespace std;
// base class
class Vehicle {
public:
Vehicle()
{
cout << "This is a Vehicle\n";
}
};
};
// main function
int main()
{
// Creating object of sub class will
// invoke the constructor of base classes
Car obj;
return 0;
}
Output
This is a Vehicle
• C++
// Example:
#include<iostream>
using namespace std;
class A
{
protected:
int a;
public:
void set_A()
{
cout<<"Enter the Value of A=";
cin>>a;
}
void disp_A()
{
cout<<endl<<"Value of A="<<a;
}
};
class B: public A
{
int b,p;
public:
void set_B()
{
set_A();
cout<<"Enter the Value of B=";
cin>>b;
}
void disp_B()
{
disp_A();
cout<<endl<<"Value of B="<<b;
}
void cal_product()
{
p=a*b;
cout<<endl<<"Product of "<<a<<" * "<<b<<" = "<<p;
}
};
main()
{
B _b;
_b.set_B();
_b.cal_product();
return 0;
• C++
// Example:
#include<iostream>
using namespace std;
class A
{
protected:
int a;
public:
void set_A(int x)
{
a=x;
}
void disp_A()
{
cout<<endl<<"Value of A="<<a;
}
};
class B: public A
{
int b,p;
public:
void set_B(int x,int y)
{
set_A(x);
b=y;
}
void disp_B()
{
disp_A();
cout<<endl<<"Value of B="<<b;
}
void cal_product()
{
p=a*b;
cout<<endl<<"Product of "<<a<<" * "<<b<<" = "<<p;
}
};
main()
{
B _b;
_b.set_B(4,5);
_b.cal_product();
return 0;
}
Output
Product of 4 * 5 = 20
2. Multiple Inheritance: Multiple Inheritance is a feature of C++ where a class can inherit from
more than one class. i.e one subclass is inherited from more than one base class.
Syntax:
class subclass_name : access_mode base_class1, access_mode base_class2, ....
{
// body of subclass
};
class B
{
... .. ...
};
class C
{
... .. ...
};
class A: public B, public C
{
... ... ...
};
Here, the number of base classes will be separated by a comma (‘, ‘) and the access mode for
every base class must be specified.
• CPP
// main function
int main()
{
// Creating object of sub class will
// invoke the constructor of base classes.
Car obj;
return 0;
}
Output
This is a Vehicle
This is a 4 wheeler Vehicle
• C++
// Example:
#include<iostream>
using namespace std;
class A
{
protected:
int a;
public:
void set_A()
{
cout<<"Enter the Value of A=";
cin>>a;
void disp_A()
{
cout<<endl<<"Value of A="<<a;
}
};
class B: public A
{
protected:
int b;
public:
void set_B()
{
cout<<"Enter the Value of B=";
cin>>b;
}
void disp_B()
{
cout<<endl<<"Value of B="<<b;
}
};
class C: public B
{
int c,p;
public:
void set_C()
{
cout<<"Enter the Value of C=";
cin>>c;
}
void disp_C()
{
cout<<endl<<"Value of C="<<c;
}
void cal_product()
{
p=a*b*c;
cout<<endl<<"Product of "<<a<<" * "<<b<<" * "<<c<<" = "<<p;
}
};
main()
{
C _c;
_c.set_A();
_c.set_B();
_c.set_C();
_c.disp_A();
_c.disp_B();
_c.disp_C();
_c.cal_product();
return 0;
To know more about it, please refer to the article Multiple Inheritances.
3. Multilevel Inheritance: In this type of inheritance, a derived class is created from another
derived class.
Syntax:-
class C
{
... .. ...
};
class B:public C
{
... .. ...
};
class A: public B
{
... ... ...
};
• CPP
// base class
class Vehicle {
public:
Vehicle() { cout << "This is a Vehicle\n"; }
};
// main function
int main()
{
// Creating object of sub class will
// invoke the constructor of base classes.
Car obj;
return 0;
}
Output
This is a Vehicle
Objects with 4 wheels are vehicles
Car has 4 Wheels
4. Hierarchical Inheritance: In this type of inheritance, more than one subclass is inherited from
a single base class. i.e. more than one derived class is created from a single base class.
Syntax:-
class A
{
// body of the class A.
}
class B : public A
{
// body of class B.
}
class C : public A
{
// body of class C.
}
class D : public A
{
// body of class D.
}
• CPP
// base class
class Vehicle {
public:
Vehicle() { cout << "This is a Vehicle\n"; }
};
// main function
int main()
{
// Creating object of sub class will
// invoke the constructor of base class.
Car obj1;
Bus obj2;
return 0;
}
Output
This is a Vehicle
This is a Vehicle
#include <iostream>
using namespace std;
// base class
class Vehicle {
public:
Vehicle() { cout << "This is a Vehicle\n"; }
};
// base class
class Fare {
public:
Fare() { cout << "Fare of Vehicle\n"; }
};
// main function
int main()
{
// Creating object of sub class will
// invoke the constructor of base class.
Bus obj2;
return 0;
}
Output
This is a Vehicle
Fare of Vehicle
• C++
// Example:
#include <iostream>
using namespace std;
class A
{
protected:
int a;
public:
void get_a()
{
cout << "Enter the value of 'a' : ";
cin>>a;
}
};
class B : public A
{
protected:
int b;
public:
void get_b()
{
cout << "Enter the value of 'b' : ";
cin>>b;
}
};
class C
{
protected:
int c;
public:
void get_c()
{
cout << "Enter the value of c is : ";
cin>>c;
}
};
int main()
{
D d;
d.mul();
return 0;
}
#include <iostream>
using namespace std;
class ClassA {
public:
int a;
};
int main()
{
ClassD obj;
obj.b = 20;
obj.c = 30;
obj.d = 40;
Output
a from ClassB : 10
a from ClassC : 100
b : 20
c : 30
d : 40
Output:
a from ClassB : 10
a from ClassC : 100
b : 20
c : 30
d : 40
In the above example, both ClassB and ClassC inherit ClassA, they both have a single copy of
ClassA. However Class-D inherits both ClassB and ClassC, therefore Class-D has two copies of
ClassA, one from ClassB and another from ClassC.
If we need to access the data member of ClassA through the object of Class-D, we must specify
the path from which a will be accessed, whether it is from ClassB or ClassC, bcoz compiler can’t
differentiate between two copies of ClassA in Class-D.
There are 2 Ways to Avoid this Ambiguity:
1) Avoiding ambiguity using the scope resolution operator: Using the scope resolution
operator we can manually specify the path from which data member a will be accessed, as shown
in statements 3 and 4, in the above example.
• CPP
#include<iostream>
class ClassA
{
public:
int a;
};
obj.b = 20;
obj.c = 30;
obj.d = 40;
Output:
a : 100
b : 20
c : 30
d : 40
According to the above example, Class-D has only one copy of ClassA, therefore, statement 4
will overwrite the value of a, given in statement 3.
# parent class
class Person( object ):
def display(self):
print(self.name)
print(self.idnumber)
# child class
class Employee( Person ):
def __init__(self, name, idnumber, salary):
self.salary = salary
def show(self):
print(self.salary)
# creation of an object
# variable or an instance
a = Employee('Rahul', 886012, 30000000)
Output:
Rahul
886012
30000000
Note: For more information, refer to Inheritance in Python.
Accessing Parent class method from inner class
An inner class or nested class is a defined inside the body of another class. If an object is
created using a class, the object inside the root class can be used. A class can have one or more
than one inner classes.
Types of Inner Classes:
• Multiple Inner Class
• Multilevel Inner Class
Multiple Inner Class: A class containing more than one inner class.
Example:
class Electronics:
def __init__(self):
print('SINGLA ELECTRONICS')
self.laptop=self.Laptop()
self.mobile=self.Mobile()
# Inner Class 1
class Laptop:
def operation(self):
print('DELL Inspiron 15')
# Inner Class 2
class Mobile:
def operation(self):
print('Redmi Note 5')
# Driver Code
ele = Electronics()
ele.laptop.operation()
ele.mobile.operation()
Output:
SINGLA ELECTRONICS
DELL Inspiron 15
Redmi Note 5
Multilevel Inner Class: In multilevel inner classes, the inner class contains another class which
is inner classes to the previous one.
Example:
class Vehicle:
def __init__(self):
def show_classes(self):
print("This is in Outer class that is Vehicle")
# inner class
class Car:
# First Inner Class
def __init__(self):
# instantiating the
# 'InnerInner' class
self.innerinner = self.Maruti()
def show_classes(self):
print("This is in Inner class that is Car")
# Driver Code
outer = Vehicle()
outer.show_classes()
inner = outer.Car()
inner.show_classes()
innerinner = inner.Maruti()
Output:
This is in Outer class that is Vehicle
This is in Inner class that is Car
This is in multilevel InnerInner class that is Maruti
Just Print It!
18.a)
What is namespace:
A namespace is a system that has a unique name for each and every object in Python. An object
might be a variable or a method. Python itself maintains a namespace in the form of a Python
dictionary. Let’s go through an example, a directory-file system structure in computers.
Needless to say, that one can have multiple directories having a file with the same name inside
every directory. But one can get directed to the file, one wishes, just by specifying the absolute
path to the file.
Real-time example, the role of a namespace is like a surname. One might not find a single
“Alice” in the class there might be multiple “Alice” but when you particularly ask for “Alice
Lee” or “Alice Clark” (with a surname), there will be only one (time being don’t think of both
first name and surname are same for multiple students).
On similar lines, the Python interpreter understands what exact method or variable one is trying
to point to in the code, depending upon the namespace. So, the division of the word itself gives
a little more information. Its Name (which means name, a unique identifier) + Space(which
talks something related to scope). Here, a name might be of any Python method or variable and
space depends upon the location from where is trying to access a variable or a method.
Types of namespaces :
When Python interpreter runs solely without any user-defined modules, methods, classes, etc.
Some functions like print(), id() are always present, these are built-in namespaces. When a user
creates a module, a global namespace gets created, later the creation of local functions creates
the local namespace. The built-in namespace encompasses the global namespace and the
global namespace encompasses the local namespace.
Example:
• Python3
As shown in the following figure, the same object name can be present in multiple namespaces
as isolation between the same name is maintained by their namespace.
But in some cases, one might be interested in updating or processing global variables only, as
shown in the following example, one should mark it explicitly as global and the update or
process. Note that the line “count = count +1” references the global variable and therefore uses
the global variable, but compare this to the same line written “count = 1”. Then the line “global
count” is absolutely needed according to scope rules.
• Python3
# Python program processing
# global variable
count = 5
def some_method():
global count
count = count + 1
print(count)
some_method()
Output:
6
Scope of Objects in Python :
Scope refers to the coding region from which a particular Python object is accessible. Hence one
cannot access any particular object from anywhere from the code, the accessing has to be
allowed by the scope of the object.
Let’s take an example to have a detailed understanding of the same:
Example 1:
• Python3
def some_func():
print("Inside some_func")
def some_inner_func():
var = 10
print("Inside inner function, value of var:",var)
some_inner_func()
print("Try printing var from outer function: ",var)
some_func()
Output:
Inside some_func
Inside inner function, value of var: 10
18.b)
Last Updated : 08 Jun, 2023
••
Shallow Copy: Shallow repetition is quicker. However, it’s “lazy” it handles
pointers and references. Rather than creating a contemporary copy of the
particular knowledge the pointer points to, it simply copies over the pointer price.
So, each of the first and therefore the copy can have pointers that reference
constant underlying knowledge.
Deep Copy: Deep repetition truly clones the underlying data. It is not shared
between the first and therefore the copy.
Below is the tabular Difference between the Shallow Copy and Deep Copy:
Shallow Copy stores the references of Deep copy stores copies of the object’s
objects to the original memory address. value.
Shallow Copy stores the copy of the Deep copy stores the copy of the original
original object and points the references to object and recursively copies the objects as
the objects. well.
Below is the program to explain the shallow and deep copy of the class.
• C++
• Java
• Python3
// C++ program to illustrate the deepcopy and shallow copy
#include <algorithm>
#include <iostream>
#include <memory>
#include <vector>
// Class of Car
class Car {
public:
string name;
vector<string> colors;
this->name = name;
this->colors = colors;
};
int main()
// Deepcopy of Honda
deepcopy_honda.colors.push_back("Green");
copy_honda->colors.push_back("Green");
return 0;
Output
Deepcopy: ['Red', 'Blue', 'Green']
Original: ['Red', 'Blue']
Shallow Copy: ['Red', 'Blue', 'Green']
Original: ['Red', 'Blue', 'Green']
PART-A
1. Analyzing algorithms in data structures using the C programming language involves
evaluating their efficiency and performance. There are two types of analysis
namely,
i) Apriori analysis and
ii) Posterior analysis
2. In a classification problem, the category or classes of data is identified based on training data.
The model learns from the given dataset and then classifies the new data into classes or groups
based on the training.To evaluate the performance of a classification model, different metrics are
used, and some of them are as follows:
o Accuracy
o Confusion Matrix
o Precision
o Recall
o F-Score
o AUC(Area Under the Curve)-ROC
3.
4.
There are mainly three asymptotic notations:
1. Big-O Notation (O-notation)
2. Omega Notation (Ω-notation)
3. Theta Notation (Θ-notation)
5.
Changes in one entity is reflected in other Changes in one entity are not reflected in changes in an
entity. identity.
The default version of the clone() method In order to make the clone() method support the deep copy
supports shallow copy. has to override the clone() method.
Cloned object and the original object are not Cloned object and the original object are disjoint.
disjoint.
6. To give a basic definition of both terms, class attributes are class variables that are inherited by
every object of a class. The value of class attributes remain the same for every new object.
7.
Advantages of Recursion:
• Recursion can simplify complex problems by breaking them down into smaller, more
manageable pieces.
• Recursive code can be more readable and easier to understand than iterative code.
• Recursion is essential for some algorithms and data structures.
• Also with recursion, we can reduce the length of code and become more readable and
understandable to the user/ programmer.
8.
A recurrence relation is a mathematical expression that defines a sequence in terms of its
previous terms. In the context of algorithmic analysis, it is often used to model the time
complexity of recursive algorithms.
9.
When working with classes in Python, the term “self” refers to the instance of the class that is
currently being used. It is customary to use “self” as the first parameter in instance methods of a
class. Whenever you call a method of an object created from a class, the object is automatically
passed as the first argument using the “self” parameter. This enables you to modify the object’s
properties and execute tasks unique to that particular instance.
10.
Yes, the inheritance is possible in python.
One of the core concepts in object-oriented programming (OOP) languages is
inheritance. It is a mechanism that allows you to create a hierarchy of classes that
share a set of properties and methods by deriving a class from another class.
Inheritance is the capability of one class to derive or inherit the properties from
another class.
Benefits of inheritance are:
Inheritance allows you to inherit the properties of a class, i.e., base class to another,
i.e., derived class.
12.
As an introduction we show that the following recursive function has linear time
complexity.
if n == 1 {
return 1
}
return n + Sum(n-1)
Let the function T(n) denote the number of elementary operations performed by the
function call Sum(n).
• Since Sum(1) is computed using a fixed number of operations k1, T(1) = k1.
• If n > 1 the function will perform a fixed number of operations k2, and in
addition, it will make a recursive call to Sum(n-1). This recursive call will
perform T(n-1) operations. In total, we get T(n) = k2 + T(n-1).
If we are only looking for an asymptotic estimate of the time complexity, we don’t
need to specify the actual values of the constants k1 and k2. Instead, we
let k1 = k2 = 1. To find the time complexity for the Sum function can then be
reduced to solving the recurrence relation
• T(1) = 1, (*)
• T(n) = 1 + T(n-1), when n > 1. (**)
PART-B
16.a) The process in which a function calls itself directly or indirectly is called recursion and the
corresponding function is called a recursive function. Using a recursive algorithm, certain
problems can be solved quite easily. Examples of such problems are Towers of Hanoi
(TOH), Inorder/Preorder/Postorder Tree Traversals, DFS of Graph, etc. A recursive function
solves a particular problem by calling a copy of itself and solving smaller subproblems of the
original problems. Many more recursive calls can be generated as and when required. It is
essential to know that we should provide a certain case in order to terminate this recursion
process. So we can say that every time the function calls itself with a simpler version of the
original problem.
Need of Recursion
Recursion is an amazing technique with the help of which we can reduce the length of our code
and make it easier to read and write. It has certain advantages over the iteration technique which
will be discussed later. A task that can be defined with its similar subtask, recursion is one of
the best solutions for it. For example; The Factorial of a number.
Properties of Recursion:
• Performing the same operations multiple times with different inputs.
• In every step, we try smaller inputs to make the problem smaller.
• Base condition is needed to stop the recursion otherwise infinite loop will occur.
Algorithm: Steps
The algorithmic steps for implementing recursion in a function are as follows:
Step1 - Define a base case: Identify the simplest case for which the solution is known or trivial.
This is the stopping condition for the recursion, as it prevents the function from infinitely
calling itself.
Step2 - Define a recursive case: Define the problem in terms of smaller subproblems. Break the
problem down into smaller versions of itself, and call the function recursively to solve each
subproblem.
Step3 - Ensure the recursion terminates: Make sure that the recursive function eventually
reaches the base case, and does not enter an infinite loop.
step4 - Combine the solutions: Combine the solutions of the subproblems to solve the original
problem.
A Mathematical Interpretation
Let us consider a problem that a programmer has to determine the sum of first n natural
numbers, there are several ways of doing that but the simplest approach is simply to add the
numbers starting from 1 to n. So the function simply looks like this,
approach(1) – Simply adding one by one
f(n) = 1 + 2 + 3 +……..+ n
but there is another mathematical approach of representing this,
approach(2) – Recursive adding
f(n) = 1 n=1
f(n) = n + f(n-1) n>1
There is a simple difference between the approach (1) and approach(2) and that is
in approach(2) the function “ f( ) ” itself is being called inside the function, so this
phenomenon is named recursion, and the function containing recursion is called recursive
function, at the end, this is a great tool in the hand of the programmers to code some problems
in a lot easier and efficient way.
How are recursive functions stored in memory?
Recursion uses more memory, because the recursive function adds to the stack with each
recursive call, and keeps the values there until the call is finished. The recursive function uses
LIFO (LAST IN FIRST OUT) Structure just like the stack data
structure. https://www.geeksforgeeks.org/stack-data-structure/
int fact(int n)
{
if (n < = 1) // base case
return 1;
else
return n*fact(n-1);
}
In the above example, the base case for n < = 1 is defined and the larger value of a number can
be solved by converting to a smaller one till the base case is reached.
How a particular problem is solved using recursion?
The idea is to represent a problem in terms of one or more smaller problems, and add one or
more base conditions that stop the recursion. For example, we compute factorial n if we know
the factorial of (n-1). The base case for factorial would be n = 0. We return 1 when n = 0.
Why Stack Overflow error occurs in recursion?
If the base case is not reached or not defined, then the stack overflow problem may arise. Let us
take an example to understand this.
int fact(int n)
{
// wrong base case (it may cause
// stack overflow).
if (n == 100)
return 1;
else
return n*fact(n-1);
}
If fact(10) is called, it will call fact(9), fact(8), fact(7), and so on but the number will never
reach 100. So, the base case is not reached. If the memory is exhausted by these functions on the
stack, it will cause a stack overflow error.
What is the difference between direct and indirect recursion?
A function fun is called direct recursive if it calls the same function fun. A function fun is called
indirect recursive if it calls another function say fun_new and fun_new calls fun directly or
indirectly. The difference between direct and indirect recursion has been illustrated in Table 1.
// An example of direct recursion
void directRecFun()
{
// Some code....
directRecFun();
// Some code...
}
indirectRecFun2();
// Some code...
}
void indirectRecFun2()
{
// Some code...
indirectRecFun1();
// Some code...
}
What is the difference between tailed and non-tailed recursion?
A recursive function is tail recursive when a recursive call is the last thing executed by the
function. Please refer tail recursion article for details.
How memory is allocated to different function calls in recursion?
When any function is called from main(), the memory is allocated to it on the stack. A recursive
function calls itself, the memory for a called function is allocated on top of memory allocated to
the calling function and a different copy of local variables is created for each function call.
When the base case is reached, the function returns its value to the function by whom it is called
and memory is de-allocated and the process continues.
Let us take the example of how recursion works by taking a simple function.
• CPP
• Java
• Python3
• C#
• PHP
• Javascript
// Driver Code
int main()
{
int test = 3;
printFun(test);
}
Recursion VS Iteration
SR
Recursion Iteration
No.
Every recursive call needs extra space in the Every iteration does not require any
3)
stack memory. extra space.
Now, let’s discuss a few practical problems which can be solved by using recursion and
understand its basic working. For basic understanding please read the following articles.
Basic understanding of Recursion.
Problem 1: Write a program and recurrence relation to find the Fibonacci series of n where n>2
.
Mathematical Equation:
n if n == 0, n == 1;
fib(n) = fib(n-1) + fib(n-2) otherwise;
Recurrence Relation:
T(n) = T(n-1) + T(n-2) + O(1)
Recursive program:
Input: n = 5
Output:
Fibonacci series of 5 numbers is : 0 1 1 2 3
Implementation:
• C++
• C
• Java
• Python3
• C#
• Javascript
int fib(int n)
{
// Stop condition
if (n == 0)
return 0;
// Stop condition
if (n == 1 || n == 2)
return 1;
// Recursion function
else
return (fib(n - 1) + fib(n - 2));
}
// Driver Code
int main()
{
// Initialize variable n.
int n = 5;
cout<<"Fibonacci series of 5 numbers is: ";
Problem 2: Write a program and recurrence relation to find the Factorial of n where n>2 .
Mathematical Equation:
1 if n == 0 or n == 1;
f(n) = n*f(n-1) if n> 1;
Recurrence Relation:
T(n) = 1 for n = 0
T(n) = 1 + T(n-1) for n > 0
Recursive Program:
Input: n = 5
Output:
factorial of 5 is: 120
Implementation:
• C++
• C
• Java
• Python3
• C#
• Javascript
// Factorial function
int f(int n)
{
// Stop condition
if (n == 0 || n == 1)
return 1;
// Recursive condition
else
return n * f(n - 1);
}
// Driver code
int main()
{
int n = 5;
cout<<"factorial of "<<n<<" is: "<<f(n);
return 0;
}
Recursion is a powerful technique that has many applications in computer science and
programming. Here are some of the common applications of recursion:
• Tree and graph traversal: Recursion is frequently used for traversing and searching
data structures such as trees and graphs. Recursive algorithms can be used to explore
all the nodes or vertices of a tree or graph in a systematic way.
• Sorting algorithms: Recursive algorithms are also used in sorting algorithms such as
quicksort and merge sort. These algorithms use recursion to divide the data into
smaller subarrays or sublists, sort them, and then merge them back together.
• Divide-and-conquer algorithms: Many algorithms that use a divide-and-conquer
approach, such as the binary search algorithm, use recursion to break down the
problem into smaller subproblems.
• Fractal generation: Fractal shapes and patterns can be generated using recursive
algorithms. For example, the Mandelbrot set is generated by repeatedly applying a
recursive formula to complex numbers.
• Backtracking algorithms: Backtracking algorithms are used to solve problems that
involve making a sequence of decisions, where each decision depends on the
previous ones. These algorithms can be implemented using recursion to explore all
possible paths and backtrack when a solution is not found.
• Memoization: Memoization is a technique that involves storing the results of
expensive function calls and returning the cached result when the same inputs occur
again. Memoization can be implemented using recursive functions to compute and
cache the results of subproblems.
These are just a few examples of the many applications of recursion in computer science and
programming. Recursion is a versatile and powerful tool that can be used to solve many
different types of problems.
• C++
• Java
• Python3
• C#
• Javascript
#include <iostream>
using namespace std;
int factorial(int n)
{
16.b)
Class attributes are an important aspect of object-oriented programming and play a crucial role in
creating organized and efficient code in Python. Below are some of a few reasons why attributes
are indispensable items in OOP:
1. Define default values: Class attributes provide a way to define default values for objects. With
this, developers can create objects with pre-set values, reducing the need for manual initialization
and minimizing the risk of errors.
2. Share information among objects: They allow developers to share information among different
objects. This is useful in cases where a single instance of an object needs to be shared across
different parts of the codebase.
3. Create singletons: They can be used to create singletons, which are objects that are instantiated
only once and shared among different parts of the code. Again, this is particularly useful in
situations where a single instance of an object needs to be shared across different parts of the
codebase.
4. Improve code organization and efficiency: Class attributes are a powerful tool in the OOP
paradigm, improving the organization and efficiency of the code. They let developers create code
that is more readable, understandable, and maintainable, as they provide a way to define common
characteristics among objects in a clear and concise manner.
5. Prevent unintended consequences: Python provides a way to define class methods that can be
used to change class attributes without affecting all instances of a class. This is a useful technique
to avoid unintended consequences when modifying class attributes.
Understanding class attributes
Python classes allow for the creation of objects that can have both attributes (data) and methods
(functions). Attributes are defined within a class and can be accessed and modified by both the
class and its objects.
In Python, class attributes are defined directly within the class definition and are shared among all
instances of the class. They can be accessed using the class name and through an instance of the
class.
Class attributes are defined outside of any method, including the init method, and are typically
assigned a value directly.
class MyClass:
class_attribute = "I am a class attribute"
print(MyClass.class_attribute)
It's also possible to define class attributes within the class constructor (init) method. This isn’t
common practice, however, as the class attributes are shared among all instances of the class and
should be constant for all instances.
class MyClass:
def init(self):
self.class_attribute = "I am a class attribute"
On the other hand, instance attributes are defined within the class constructor (init) method and
are unique to each instance of the class. They can be accessed using the instance name and
through the class name.
class MyClass:
def init(self):
self.instance_attribute = "I am an instance attribute"
my_object = MyClass()
print(my_object.instance_attribute) # Output: "I am an instance attribute"
Class attributes can be accessed using the dot notation, just like you would with instance
variables.
Here’s an example:
class Cat:
species = "Felis catus"
self.name = name
self.breed = breed
Modifying class attributes is just as easy as accessing them. However, be careful when doing so as
it changes for all instances of that class. If you change the species of one cat, you're changing it
for all cats.
class Pig:
species = "Sus scrofa"
self.name = name
self.breed = breed
When we talk about attributes in OOP, we can’t forget the existence of the method they work
with. Let’s look at it in detail.
Methods are functions that are associated with an object and can be called on that object. In OOP,
methods allow objects to perform operations and interact with each other. They are defined inside
classes and can take parameters, perform calculations, and modify object data.
Methods provide a way to encapsulate behavior and logic into objects, making it easier to
understand and maintain the code. In Python, they can be defined using the def keyword, just like
any other function. However, they are associated with an object and can be called on that object
using dot notation.
They play a crucial role in the implementation of object-oriented programming principles and are
an essential aspect of creating organized and maintainable code.
When it comes to creating class methods, there are a few things to keep in mind. First, it's
important to use the @classmethod decorator to indicate that the method is a class method. This is
because class methods behave differently than regular instance methods and need to be treated as
such.
class Book:
books_sold = 0
self.title = title
self.author = author
@classmethod
def update_books_sold(cls, amount):
cls.books_sold += amount
In the code snippet above, we have as object a book with attributes title, author and with a method
update_books_sold, which has a function to update the quantity of the total books sold. The
methods defined will help us interact with the object and update some of its content.
One of the most common use cases for class methods is to modify class attributes. Class attributes
are shared among all instances of a class. By using class methods, you can manipulate these
shared attributes in a controlled and organized way.
For example, you have a Book class and want to keep track of the total number of pages in all the
books. Here's how you could do that using class methods:
class Book:
total_pages = 0
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
@classmethod
def update_total_pages(cls, pages):
cls.total_pages += pages
def add_to_total_pages(self):
Book.update_total_pages(self.pages)
In this example, we've added a class attribute total_pages to keep track of the total number of
pages across all books. We've also added a class method update_total_pages to modify this class
attribute. Finally, we've added an instance method add_to_total_pages that calls the
update_total_pages method, passing in the number of pages for the current book.
Practical uses of class attributes
Python class attributes are a versatile feature of OOP, allowing you to add class-level data to
classes. This data can serve as default values for instance attributes, track class-level statistics, and
much more.
In this section, we'll dive into the practical uses of Python class attributes, showcasing how they
can be used in real-world scenarios to enhance the organization, maintainability, and performance
of code.
Whether you have extensive experience with Python or are just starting out with OOP, this section
will provide valuable insights and techniques for leveraging class attributes to elevate your code.
One of the most practical uses of class attributes is setting default values for objects. In object-
oriented programming, objects are instances of a class and can have their own attributes.
However, sometimes it's useful to set a default value for an attribute that applies to all instances of
the class. This is where class attributes come in handy.
class Restaurant:
tip = 18
def __init__(self, name, cuisine, bill, tip=None):
self.name = name
self.cuisine = cuisine
self.bill = bill
if tip is not None:
self.tip = tip
class ClothingStore:
discount = 10
def __init__(self, name, clothes, price, discount=None):
self.name = name
self.clothes = clothes
self.price = price
if discount is not None:
self.discount = discount
class MovieTheater:
age_limit = 17
def init(self, name, movie, age_limit=None):
self.name = name
self.movie = movie
if age_limit is not None:
self.age_limit = age_limit
Class attributes can also be used to share information among objects in your code. Say you want
to keep track of the number of books in your library. You can create a Book class with a class
attribute count that will keep track of the total number of books in the library:
class Book:
count = 0
def init(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
Book.count += 1
Now, every time you create a new Book object, the count class attribute will be updated
accordingly:
print(Book.count) # Output: 3
This demonstrates how class attributes can be used to share information among objects, making it
easier to keep track of important information in your code. It's also a great way to maintain
consistency and ensure that all objects use the same information.
Creating singletons
Class attributes can be used to create singleton objects, which are objects that are created only
once and can be shared among multiple parts of your code. Singletons are commonly used for
shared resources, such as database connections, caches, and other objects that should only exist
once in a system.
To create a singleton in Python, you can create a class with a class attribute that holds the
singleton object:
class Database:
_instance = None
def new(cls):
if cls._instance is None:
cls._instance = super().new(cls)
return cls._instance
This class uses the new method to ensure that only one instance of the class is created. Every time
you try to create a new Database object, the new method will check if an instance already exists
and return that instead of creating a new one.
db1 = Database()
db2 = Database()
In this example, db1 and db2 refer to the same object, demonstrating that the singleton pattern has
been implemented successfully. This allows you to share the same database connection across
your code, ensuring that all parts of the code use the same database and avoiding the creation of
multiple connections.
Best practices for using class attributes and methods
Last but certainly not least, let's talk about some best practices for using class attributes and
methods. After all, you want to make sure they’re used in the most effective and efficient way
possible.
Class attributes are meant to be shared among all instances of a class, so mutating them from an
instance method can lead to unexpected behavior. Instead, you should use instance attributes if
you need to store information that is specific to an individual object.
Class methods are useful in certain cases but they can also make your code more complex and
harder to maintain. Only use them when you really need to modify class attributes or when you
want to provide a way to create objects that is different from the standard "init" method.
Make sure your class attributes and methods have clear and descriptive names so that other
developers (and your future self!) can understand what they do. This makes it easier to maintain
your code and reduces the risk of bugs.
Object-oriented programming is a powerful tool that can help you write better code, so don't be
afraid to experiment and try new things. The more you play with class attributes and methods, the
better you'll get at using them effectively.
Conclusion
We've covered a lot of ground on the topic of Python class attributes and methods. We've
discussed what class attributes and methods are, how to create and use them, and their practical
uses in object-oriented programming. We've also explored some best practices for using them
effectively.
Class attributes and methods play a vital role in OOP, allowing you to structure code in a way that
is easy to understand, maintain, and debug. By using class attributes and methods, you can create
objects that share information, store default values, and provide a way to modify class-level data.
17.a)
The efficiency of an algorithm depends on the amount of time, storage and other resources
required to execute the algorithm. The efficiency is measured with the help of asymptotic
notations.
An algorithm may not have the same performance for different types of inputs. With the increase
in the input size, the performance will change.
The study of change in performance of the algorithm with the change in the order of the input size
is defined as asymptotic analysis.
Asymptotic Notations
Asymptotic notations are the mathematical notations used to describe the running time of an
algorithm when the input tends towards a particular value or a limiting value.
For example: In bubble sort, when the input array is already sorted, the time taken by the
algorithm is linear i.e. the best case.
But, when the input array is in reverse condition, the algorithm takes the maximum time
(quadratic) to sort the elements i.e. the worst case.
When the input array is neither sorted nor in reverse order, then it takes average time. These
durations are denoted using asymptotic notations.
• Big-O notation
• Omega notation
• Theta notation
Big-O notation represents the upper bound of the running time of an algorithm. Thus, it gives the
worst-case complexity of an algorithm.
Big-
O gives the upper bound of a function
The above expression can be described as a function f(n) belongs to the set O(g(n)) if there exists
a positive constant c such that it lies between 0 and cg(n), for sufficiently large n.
For any value of n, the running time of an algorithm does not cross the time provided by O(g(n)).
Since it gives the worst-case running time of an algorithm, it is widely used to analyze an
algorithm as we are always interested in the worst-case scenario.
Omega Notation (Ω-notation)
Omega notation represents the lower bound of the running time of an algorithm. Thus, it provides
the best case complexity of an algorithm.
The above expression can be described as a function f(n) belongs to the set Ω(g(n)) if there exists
a positive constant c such that it lies above cg(n), for sufficiently large n.
For any value of n, the minimum time required by the algorithm is given by Omega Ω(g(n)).
Theta Notation (Θ-notation)
Theta notation encloses the function from above and below. Since it represents the upper and the
lower bound of the running time of an algorithm, it is used for analyzing the average-case
complexity of an algorithm.
The above expression can be described as a function f(n) belongs to the set Θ(g(n)) if there exist
positive constants c1 and c2 such that it can be sandwiched between c1g(n) and c2g(n), for
sufficiently large n.
If a function f(n) lies anywhere in between c1g(n) and c2g(n) for all n ≥ n0, then f(n) is said to be
asymptotically tight bound.
er recurrencesUpdated : 15 Feb, 2023
17.b)
The•• Master Theorem is a tool used to solve recurrence relations that arise in the
analysis of divide-and-conquer algorithms. The Master Theorem provides a
systematic way of solving recurrence relations of the form:
T(n) = aT(n/b) + f(n)
1. where a, b, and f(n) are positive functions and n is the size of the
problem. The Master Theorem provides conditions for the solution of the
recurrence to be in the form of O(n^k) for some constant k, and it gives a
formula for determining the value of k.
2. The advanced version of the Master Theorem provides a more general
form of the theorem that can handle recurrence relations that are more
complex than the basic form. The advanced version of the Master
Theorem can handle recurrences with multiple terms and more complex
functions.
3. It is important to note that the Master Theorem is not applicable to all
recurrence relations, and it may not always provide an exact solution to a
given recurrence. However, it is a useful tool for analyzing the time
complexity of divide-and-conquer algorithms and provides a good
starting point for solving more complex recurrences.
Master Theorem is used to determine running time of algorithms (divide and
conquer algorithms) in terms of asymptotic notations.
Consider a problem that is solved using recursion.
2. if a = bk, then
(a) if p > -1, then T(n) = θ(nlog a logp+1n)
b
T(n) = θ(logn)
• Example-2: Merge Sort – T(n) = 2T(n/2) + O(n)
a = 2, b = 2, k = 1, p = 0
bk = 2. So, a = bk and p > -1 [Case 2.(a)]
T(n) = θ(nlog a logp+1n)
b
T(n) = θ(nlogn)
• Example-3: T(n) = 3T(n/2) + n2
a = 3, b = 2, k = 2, p = 0
bk = 4. So, a < bk and p = 0 [Case 3.(a)]
T(n) = θ(nk logpn)
T(n) = θ(n2)
• Example-4: T(n) = 3T(n/2) + log2n
a = 3, b = 2, k = 0, p = 2
bk = 1. So, a > bk [Case 1]
T(n) = θ(nlog a )
b
T(n) = θ(nlog 3)
2
T(n) = θ(nlog3n)
Here are some important points to keep in mind regarding the Master
Theorem:
d. A
ll rights reserve
18.a) A factorial is positive integer n, and denoted by n!. Then the product of
all positive integers less than or equal to n.
For example:
Input: 6
Output: 720
Implementation:
If fact(5) is called, it will call fact(4), fact(3), fact(2) and fact(1). So it means
keeps calling itself by reducing value by one till it reaches 1.
• Python3
def factorial(n):
# is 1 or 0 then
# return 1
# factorial
if (n==1 or n==0):
return 1
else:
# Driver Code
num = 5;
print("number : ",num)
print("Factorial : ",factorial(num))
Output
number : 5
Factorial : 120
A Fibonacci sequence is a sequence of integers which first two terms are 0 and 1 and all
other terms of the sequence are obtained by adding their preceding two numbers.
1. def recur_fibo(n):
2. if n <= 1:
3. return n
4. else:
5. return(recur_fibo(n-1) + recur_fibo(n-2))
6. # take input from the user
7. nterms = int(input("How many terms? "))
8. # check if the number of terms is valid
9. if nterms <= 0:
10. print("Plese enter a positive integer")
11. else:
12. print("Fibonacci sequence:")
13. for i in range(nterms):
14. print(recur_fibo(i))
Output: