ADS UNIT 1
ADS UNIT 1
The word Algorithm means “a process or set of rules to be followed in calculations or other
problem-solving operations”. Therefore Algorithm refers to a set of rules/instructions that step-
by-step define how a work is to be executed upon in order to get the expected results.
It can be understood by taking an example of cooking a new recipe. To cook a new recipe,
one reads the instructions and steps and executes them one by one, in the given sequence.
The result thus obtained is the new dish cooked perfectly. Similarly, algorithms help to do a
task in programming to get the expected output.
The Algorithm designed are language-independent, i.e. they are just plain instructions that
can be implemented in any language, and yet the output will be the same, as expected.
Clear and Unambiguous: Algorithm should be clear and unambiguous. Each of its
steps should be clear in all aspects and must lead to only one meaning.
Well-Defined Inputs: If an algorithm says to take inputs, it should be well-defined
inputs.
Well-Defined Outputs: The algorithm must clearly define what output will be
yielded and it should be well-defined as well.
Finite-ness: The algorithm must be finite, i.e. it should not end up in an infinite loops
or similar.
Feasible: The algorithm must be simple, generic and practical, such that it can be
executed upon with the available resources. It must not contain some future
technology, or anything.
Language Independent: The Algorithm designed must be language-independent, i.e.
it must be just plain instructions that can be implemented in any language, and yet the
output will be same, as expected.
Advantages of Algorithms:
It is easy to understand.
Algorithm is a step-wise representation of a solution to a given problem.
In Algorithm the problem is broken down into smaller pieces or steps hence, it is
easier for the programmer to convert it into an actual program.
Disadvantages of Algorithms:
Program:
// C++ program to add three numbers with the help of above designed algorithm
#include <bits/stdc++.h>
using namespace std;
int main()
{
return 0;
}
Output
Enter the 1st number: 0
Algorithm Analysis:
1. Priori Analysis: “Priori” means “before”. Hence Priori analysis means checking the
algorithm before its implementation. In this, the algorithm is checked when it is written
in the form of theoretical steps. This Efficiency of an algorithm is measured by
assuming that all other factors, for example, processor speed, are constant and have no
effect on the implementation. This is done usually by the algorithm designer. It is in
this method, that the Algorithm Complexity is determined.
Space Factor: Space is measured by counting the maximum memory space required
by the algorithm
.
1. Space Complexity: Space complexity of an algorithm refers to the amount of
memory that this algorithm requires to execute and get the result. This can be for
inputs, temporary operations, or outputs.
Variable Part: This refers to the space that can be different based on the
implementation of the algorithm. For example, temporary variables, dynamic
memory allocation, recursion stack space, etc.
2. Time Complexity: Time complexity of an algorithm refers to the amount of time that
this algorithm requires to execute and get the result. This can be for normal operations,
conditional if-else statements, loop statements, etc.
Variable Time Part: Any instruction that is executed more than once, say n
times, comes in this part. For example, loops, recursion, etc.
Data Structures - Asymptotic Analysis
Asymptotic analysis of an algorithm refers to defining the mathematical boundation/framing of its run-time
performance. Using asymptotic analysis, we can very well conclude the best case, average case, and worst case
scenario of an algorithm.
Asymptotic analysis is input bound i.e., if there's no input to the algorithm, it is concluded to work in a constant time.
Other than the "input" all other factors are considered constant.
Asymptotic analysis refers to computing the running time of any operation in mathematical units of computation. For
example, the running time of one operation is computed as f(n) and may be for another operation it is computed as
2
g(n ). This means the first operation running time will increase linearly with the increase in n and the running time of
the second operation will increase exponentially when n increases. Similarly, the running time of both operations will
be nearly the same if n is significantly small.
Usually, the time required by an algorithm falls under three types −
Best Case − Minimum time required for program execution.
Asymptotic Notations
Following are the commonly used asymptotic notations to calculate the running time complexity of an algorithm.
Ο Notation
Ω Notation
θ Notation
Big Oh Notation, Ο
The notation Ο(n) is the formal way to express the upper bound of an algorithm's running time. It measures the worst
case time complexity or the longest amount of time an algorithm can possibly take to complete.
Ο(f(n)) = { g(n) : there exists c > 0 and n0 such that f(n) ≤ c.g(n) for all n > n0. }
Omega Notation,, Ω
The notation Ω(n) is the formal way to express the lower bound of an algorithm's running time. It measures the best
case time complexity or the best amount of time an algorithm can possibly take to complete.
For example, for a function f(n)
Ω(f(n)) ≥ { g(n) : there exists c > 0 and n0 such that g(n) ≤ c.f(n) for all n > n0. }
Theta Notation, θ
The notation θ(n) is the formal way to express both the lower bound and the upper bound of an algorithm's
running time. It is represented as follows −
θ(f(n)) = { g(n) if and only if g(n) = Ο(f(n)) and g(n) = Ω(f(n)) for all n > n0. }
constant − Ο(1)
logarithmic − Ο(log n)
linear − Ο(n)
quadratic − Ο(n2)
cubic − Ο(n3)
polynomial − nΟ(1)
exponential − 2Ο(n)
Importance of efficient algorithms
Understanding algorithm efficiency and why it’s important:
When you first begin programming, algorithms can sound intimidating (frightening). You think of
algorithms requiring a whiteboard, some PhD students, several hours and an impossible problem.
You soon find out that’s not the case. Algorithms are solved by programmers every day.
An important part of solving algorithms is efficiency. As your dataset grows so will the time it
takes to run functions on it. Understanding the efficiency of an algorithm is important for growth.
As programmers we code with the future in mind and to do that, efficiency is key.
Creating efficient algorithms is about reducing the amount of iterations needed to complete your
task in relation to the size of the dataset. BigO notation is used to describe time complexity. Here
Note here that there can be a huge discrepancy(difference) in the amount of “Operations” (or
iterations) as the elements increase, that’s why it is important to create efficient algorithms.
A really great example is hashes vs arrays. We are all familiar with both data structures. The time
complexity for each respectively is O(1) vs O(n). Let’s talk about why.
First lets look at an array. If we are searching for an element in an array, what is the maximum
amount of elements we would have to look through in order to find our value? The answer is the
length of the array, or n. If we have an array of [1, 4, 6, 10, 5] and we are looking for 6, how many
elements do we search through to find the value? Whats the worst case scenario?
Why do you think hashes have a search time complexity of O(1)? A hash uses keys to lookup the
value in which you are searching for. Let’s say it’s a function “find_value(key)”. Everytime you
look for a value, it will only have to run one time to find the answer. So the time complexity is
O(1).
Compare these two time complexities on the chart. As you can see the hash will perform better as
Understanding bigO runtime and being able to explain complexity is important for programmers.
Some general tips: Try to stay away from embedded loops, meaning loops inside of loops.
Remember that this is meant to be within reason. There are plenty of cases where an embedded
loop is a reasonable solution. Overusing variables can also weigh down runtime, if you don’t need
it don’t use it. Sandwich coding is an example of this. Hashes over arrays. You can often use
smaller datatypes to save memory bytes before integers, integers before floats. All of these tips
If we want to go from city "A" to city "B", there can be many ways of doing this. We can go by flight, by
bus, by train and also by bicycle. Depending on the availability and convenience, we choose the one
which suits us. Similarly, in computer science, there are multiple algorithms to solve a problem. When we
have more than one algorithm to solve a problem, we need to select the best one. Performance analysis
helps us to select the best algorithm from multiple algorithms to solve a problem.
When there are multiple alternative algorithms to solve a problem, we analyze them and pick the one
which is best suitable for our requirements. The formal definition is as follows...
Performance of an algorithm means predicting the resources which are required to an algorithm
That means when we have multiple algorithms to solve a problem, we need to select a suitable algorithm
We compare algorithms with each other which are solving the same problem, to select the best algorithm.
To compare algorithms, we use a set of parameters or set of elements like memory required by that
algorithm, the execution speed of that algorithm, easy to understand, easy to implement, etc.,
1. Whether that algorithm is providing the exact solution for the problem?
2. Whether it is easy to understand?
3. Whether it is easy to implement?
4. How much space (memory) it requires to solve the problem?
5. How much time it takes to solve the problem? Etc.,
When we want to analyse an algorithm, we consider only the space and time required by that particular
Based on this information, performance analysis of an algorithm can also be defined as follows...
Performance analysis of an algorithm is the process of calculating space and time required by
that algorithm.
Performance analysis of an algorithm is performed by using the following measures...
1. Space required to complete the task of that algorithm (Space Complexity). It includes program
space and data space
2. Time required to complete the task of that algorithm (Time Complexity)
Space Complexity
What is Space complexity?
When we design an algorithm to solve a problem, it needs some computer memory to complete its
execution. For any algorithm, memory is required for the following purposes...
Total amount of computer memory required by an algorithm to complete its execution is called as
Generally, when a program is under execution it uses the computer memory for THREE reasons. They
are as follows...
1. Instruction Space: It is the amount of memory used to store compiled version of instructions.
2. Environmental Stack: It is the amount of memory used to store information of partially executed
functions at the time of function call.
3. Data Space: It is the amount of memory used to store all the variables and constants.
Note - When we want to perform analysis of an algorithm based on its Space complexity, we consider only Data
That means we calculate only the memory required to store Variables, Constants, Structures, etc.,
To calculate the space complexity, we must know the memory required to store different datatype values
(according to the compiler). For example, the C Programming Language compiler requires the following...
Example 1
int square(int a)
{
return a*a;
}
In the above piece of code, it requires 2 bytes of memory to store variable 'a' and another 2 bytes of
memory is used for return value.
That means, totally it requires 4 bytes of memory to complete its execution. And this 4 bytes of
memory is fixed for any input value of 'a'. This space complexity is said to be Constant Space
Complexity.
If any algorithm requires a fixed amount of space for all input values then that space complexity is
said to be Constant Space Complexity.
Consider the following piece of code...
Example 2
int sum(int A[ ], int n)
{
int sum = 0, i;
for(i = 0; i < n; i++)
sum = sum + A[i];
return sum;
}
In the above piece of code it requires
'n*2' bytes of memory to store array variable 'a[ ]'
2 bytes of memory for integer parameter 'n'
4 bytes of memory for local integer variables 'sum' and 'i' (2 bytes each)
2 bytes of memory for return value.
That means, totally it requires '2n+8' bytes of memory to complete its execution. Here, the total
amount of memory required depends on the value of 'n'. As 'n' value increases the space required
also increases proportionately. This type of space complexity is said to be Linear Space
Complexity.
If the amount of space required by an algorithm is increased with the increase of input value, then
that space complexity is said to be Linear Space Complexity.
Time Complexity
What is Time complexity?
Every algorithm requires some amount of computer time to execute its instruction to perform the task.
The time complexity of an algorithm is the total amount of time required by an algorithm to
Note - When we calculate time complexity of an algorithm, we consider only input data and ignore the remaining
things, as they are machine dependent. We check only, how our program is behaving for the different input values to
perform all the operations like Arithmetic, Logical, Return value and Assignment etc.,
Calculating Time Complexity of an algorithm based on the system configuration is a very difficult task
because the configuration changes from one system to another system. To solve this problem, we must
assume a model machine with a specific configuration. So that, we can able to calculate generalized time
complexity according to that model machine.
To calculate the time complexity of an algorithm, we need to define a model machine. Let us assume a
machine with following configuration...
Now, we calculate the time complexity of following example code by using the above-defined model
machine...
Consider the following piece of code...
Example 1
int sum(int a, int b)
{
return a+b;
}
In the above sample code, it requires 1 unit of time to calculate a+b and 1 unit of time to return the value.
That means, totally it takes 2 units of time to complete its execution. And it does not change based on the
input values of a and b. That means for all input values, it requires the same amount of time i.e. 2 units.
If any program requires a fixed amount of time for all input values then its time complexity is said
to be Constant Time Complexity.
Consider the following piece of code...
Example 2
int sum(int A[], int n)
{
int sum = 0, i;
for(i = 0; i < n; i++)
sum = sum + A[i];
return sum;
}
For the above code, time complexity can be calculated as follows...
In above calculation
Cost is the amount of computer time required for a single operation in each line.
Repeatation is the amount of computer time required by each operation for all its repeatations.
Total is the amount of computer time required by each operation to execute.
So above code requires '4n+4' Units of computer time to complete the task. Here the exact time is not
fixed. And it changes based on the n value. If we increase the n value then the time required also
increases linearly.
Totally it takes '4n+4' units of time to complete its execution and it is Linear Time Complexity.
If the amount of time required by an algorithm is increased with the increase of input value then
that time complexity is said to be Linear Time Complexity.
Proving that 1+2+3+...+n is n(n+1)/2
We give two proofs here that the n-th Triangular number, 1+2+3+...+n is n(n+1)/2.
The first is a visual one involving only the formula for the area of a rectangle. This is
followed by second proofs using algebra.
T(n)= 1 3 6 10 15 21
For the proof, we will count the number of dots in T(n) but, instead of summing the
numbers 1, 2, 3, etc up to n we will find the total using only one multiplication and
one division!
To do this, we will fit two copies of a triangle of dots together, one red and an
upside-down copy in green.
E.g. T(4)=1+2+3+4
+ =
Notice that
we get a rectangle which is has the same number of rows (4) but has one extra
column (5)
so the rectangle is 4 by 5
it therefore contains 4x5=20 balls
but we took two copies of T(4) to get this
so we must have 20/2 = 10 balls in T(4), which we can easily check.
So T(5) is half of a rectangle of dots 5 tall and 6 wide, i.e. half of 30 dots, so T(5)=15.
For T(n)=1+2+3+...+n we take two copies and get a rectangle that is n by (n+1).
So there you have it - our visual proof that
Data Structures are the programmatic way of storing data so that data can be used efficiently.
Almost every enterprise application uses various types of data structures in one or the other way.
This topic will give you a great understanding on Data Structures needed to understand the
complexity of enterprise level applications and need of algorithms, and data structures.
Processor speed − Processor speed although being very high, falls limited if the data
grows to billion records.
To solve the above-mentioned problems, data structures come to rescue. Data can be organized
in a data structure in such a way that all items may not be required to be searched, and the
required data can be searched almost instantly.
From the data structure point of view, following are some important categories of algorithms −