Chapter 4
Chapter 4
Algorithm analysis
BIG O
Algorithm: 2
Is a method or a process adopted in order to solve problem
Problem :
✓ Task to be performed
✓ Input and output
Algorithm
Is a step-by-step procedure(set of instructions)
……to solve a specific problem
…….. in a finite amount of time
➢ Algorithm must be expressed in steps that the processor is capable of
performing
➢ The algorithm must eventually terminate
➢ The stated problem must be solvable(step by step)
Principles 4
Given several algorithms to solve the same problem, which algorithm is the best
Given an algorithm, is it feasible to use it at all? Is it efficient enough to be usable in
practice?
How much time the algorithm require
How much space (memory) does the algorithm require?
Efficiency 6
How to validate correct algorithms?
By measuring time:
Time:
• instructions take time
• how fast does the algorithm perform
• What effects its runtime
Space(memory cost)
• Data structure take place
• What kind of data structures can be used
• How does the choice of data structures effect the
runtime?
➢ Quality, simplicity, compiler optimization, power consumption,
bandwidth,…..
Algorithm analysis 7
Other factors:
1.Speed of the host machine(hardware)
2.Quality of the compiler(programming language…)
3. Qualityof the program(in some case)(methodology e.g.
procedural vs. OOP)
Hypothetical profile of 2 sorting 9
algorithms:
A sequence of operations:
Count + = 1 cost c1
Sum + = I cost c2
Which means : total cost = c1+c2
Execution time of algorithm 12
If –Else
The runtime is:
The sum of the test +
The larger of the running times of If block or Else block
E.g.
Execution time of algorithm 13
Simple loop
Loop accumulates its instructions cost(add the costs time)
Even if we may break earlier, but we always consider the worst case:
E.g.
Execution time of algorithm 14
Nested loop
Analyze inside out and multiply
E.g.
15
i = 1, sum=0, j = 1;
while(i <= n) {
j = 0;
while(j <= m) {
operationX;
j++;
}
i++;
}
Complexity 16
Measuring the functions Rates of Growth
For many interesting algorithms, the exact number of operation is
too difficult to analyze mathematically
To simplify the analysis:
▪ Identify the fastest-growing term
▪ Neglect slower-growing terms
(small values of N generally are not important)
Big O notation tells you the number of operation an algorithm will make
Big O notation doesn't tell you the exact time in seconds
Big O notation is used to compare 2 different algorithm by number of
operation he made
Big O established the worst case run time
Big-O Notation 18
For polynomials, use only leading term, ignore coefficients : linear, Quadratic
Complexity 20
21
TIME-COMPLEXITY FUNCTIONS
Complexity analysis 24
The function f(n) = n2 is not the only choice for satisfying the
condition T(n) ≤ c.f(n)
We could have said the algorithms had a run time of O(n3) or O(n4)
since n2 ≤ n3 and n2 ≤ n4 when n > 1.
The objective, however, is to find a function f that provides the
lowest upper bound or limit for the run time of an algorithm
Example 26
2. Simplifying the time complexity equation: T(N) <= N/160 + N when N >= 2
Here, the inequality T(N) <= N/160 + N is derived by replacing the constant 2 with N, which is a reasonable
approximation since N is typically much larger than 2. This simplification allows for a more accurate
characterization of the time complexity.
1
Example 34
1
1
Example 35
1
1
1
Example 36
1
1
1
n+1
Example 37
1
1
1
n+1
2*n
Example 38
1
1
1
n+1
2*n
2*n
Example 39
1
1
1
n+1
2*n
2*n
2
Example 40
1
1
1
n+1
2*n
2*n
2
1
T(n)=5n+7
Example 41
T(n) = 5n+7
T(n) = 5n+7
T(n) <= 5n+n when n >= 7
T(n) = 5n+7
T(n) <= 5n+n when n >= 7
T(n) <= 6n
T(n) = 5n+7
T(n) <= 5n+n when n >= 7
T(n) <= 6n
• T(n) is O(N) with c=6 and n0=7
Constructing T(N) 42
COMPLEXITY OF AN ALGORITHM
• The complexity of a loop is the complexity of the body multiplied by the number
of times this loop is repeated
• The complexity of if-else is the maximum of the complexity of block if and the
complexity of the block else
T(n) = 6 O(1)
T(n) = 100000 O(1)
width = 5.5 1
height = 2 1
print("area is", width * height) 2
48
def ex1( n ):
total = 0 O(1)
for i in range( n ):
total += i n*O(1)= O(n)
return total O(1)
def ex3(n):
count = 0
for i in range(n) :
for j in range(n) :
count += 1
return count
Both loops will be executed n, but since the inner loop is nested
inside the outer loop, the total time required by the outer loop will
be T(n) = n * n, resulting in a time of O(n2)
51
print(ex4(1000))
To determine the run time of this function, we have to determine the number of loop iterations
Since the loop variable is cut in half each time, this will be less than n
For example, if n equals 16, variable i will contain the following five values during subsequent
iterations (16, 8, 4, 2, 1)
When the size of the input is reduced by half in each subsequent iteration, the number of
iterations required to reach a size of one will be equal to or the largest integer less than log2,
plus 1.
Thus, function ex6() requires O(log n) time.
LOGARITHMIC TIME EXAMPLES 54
def ex8(n):
count = 0
k = 1
while k <= n:
for i in range(n):
count += 1
k *= 2
return count
Since the while loop variable is multiplied by 2 each time, this will be log n
loop iteration
Since the for loop is executed n times, ex8() will have a run time of O(n
log n)
DIFFERENT CASES 56
Some algorithms can have run times that are different orders of magnitude for
different sets of inputs of the same size
These algorithms can be evaluated for their best, worst, and average cases
Algorithms that have different cases can typically be identified by the inclusion of
an event-controlled loop or a conditional statement
Consider the following example, which traverses an array containing integer values
to find the position of the first negative value
def findNeg(intList):
n = len(intList)
for i in range( n ) :
if intList[i] < 0 :
return i
return None
At first impression, it appears the loop will execute n times, where n is the size of the
array. But notice the return statement inside the loop, which can cause it to
terminate early.
DIFFERENT CASES 57
Now consider the case where the list contains a negative value in the first
element:
The average case is evaluated for an expected data set or how we expect
the algorithm to perform on average
For the findNeg() function, we would expect the search to iterate halfway
through the array before finding the first negative value, which on average
requires n/2 iterations.
The average case is more difficult to evaluate because it's not always readily
apparent what constitutes the average case for a particular problem
In general, we are more interested in the worst case time-complexity of an
algorithm as it provides an upper bound over all possible inputs.
In addition, we can compare the worst case run times of different
implementations of an algorithm to determine which is the most efficient for
any input
Exercises: 60
Calculate the running time T(n) and then deduce the complexity in
terms of Big-O of the following programs
1. Sorting an array
def sort(a):
n = len(a)
for i in range(n-1):
imin = i
j=i+1
while j < n:
if a[j] < a[imin]:
imin = j
j += 1
a[i], a[imin] = a[imin], a[i]
To calculate the running time T(n) of the given code, let's analyze the number of basic operations executed as a
function of the input size n.
def sort(a): 62
n = len(a) # 1 operation
imin = i # 1 operation
j=i+1 # 1 operation
imin = j # 1 operation
j += 1 # 1 operation
= 5n - 3
T(n) = 5n - 3
1. Outer loop: The outer loop iterates `n-1` times, where `n` is the size of the array `a`. This loop
compares each element with the subsequent elements to find the minimum element and places it in
63
the correct position.
2. Inner loop: The inner loop iterates `n-i-1` times in each iteration of the outer loop, where `i` is the
current iteration of the outer loop. This loop finds the index of the minimum element in the unsorted
portion of the array.
Based on the simplification rules, we can deduce the complexity of the algorithm as follows:
- The outer loop has a time complexity of O(n) since it iterates `n-1` times.
- The inner loop has a maximum time complexity of O(n) as well. Although it iterates `n-i-1` times in
each iteration of the outer loop, the average number of iterations decreases as the outer loop
progresses. However, we consider the worst-case scenario, in which the inner loop iterates `n-i-1`
times for each iteration of the outer loop.
The assignment statement `a[i], a[imin] = a[imin], a[i]` is a constant-time operation and does not
contribute significantly to the overall complexity.
Considering the worst-case scenario, where the outer loop iterates `n-1` times and the inner loop
iterates a maximum of `n-i-1` times for each iteration, we can deduce the overall complexity of
the algorithm as O(n^2).
Therefore, the complexity of the `sort` function using selection sort is O(n^2).
Exercises(Linear search) 64
Total operations = n * (1 + 1) + 1
= 2n + 1
T(n) = 2n + 1
Based on the simplification rules, we can deduce the complexity of the code as follows:
•The loop iterates n times, resulting in a complexity of O(n).
•The comparison and return statements are constant-time operations and do not impact the
overall complexity.
Therefore, we can conclude that the complexity of the given code is O(n) in terms of Big-O
notation.
Thus, the complexity of the code is linear, indicating that the running time grows linearly with the
size of the input array.
Exercises:(Binary search) 67
def binarysearch(a, low, high, x): a = [2, 44, 55, 66, 78, 90, 87, 112]
We can determine that the time complexity of the binary search algorithm implemented in the given code is O(log
N).
Therefore, the running time T(n) of the given code can be expressed as T(n) = O(log N).
To determine the time complexity of the given code, let's analyze the number of basic operations executed as a function of
the input size and deduce the complexity using simplification rules:
def binarysearch(a, low, high, x): 69
if high >= low: # 1 comparison
mid = high + low // 2 # 2 operations
if a[mid] == x: # 1 comparison
return mid # 1 operation
elif a[mid] > x: # 1 comparison
return binarysearch(a, low, mid - 1, x) # Recursion with reduced problem size
else:
return binarysearch(a, low, mid + 1, x) # Recursion with reduced problem size
else:
return -1 # 1 operation
70
Therefore, the time complexity of the binary search algorithm implemented in the given code is O(log
N) in terms of Big-O notation.
This complexity indicates that the algorithm's running time grows logarithmically with the size of the
input array, making it much more efficient than linear search for large sorted arrays.