9A Complexity of Programs:Functions
9A Complexity of Programs:Functions
Cosmin E. Oancea
[email protected]
November 2022
Today’s Lecture
First hour:
examine two solutions to the problem of finding the index at
which a target element appears in an input array:
I when the array is not sorted,
I when the array is sorted.
learn how to reason empirically on whether two algorithms
have the same complexity (only from their runtimes).
Second hour:
introduce the notion of (asymptotic) complexity of an
algorithm, which models how the running time depends on
the “input size” (or problem size), where the input size can be
I the magnitude of the integer parameter, or
I the length of the input array, etc.
give examples from several complexity classes:
O(1), O(log(n)), O(n), O(n · log(n)), O(n2 ), O(2n ) . . ..
examine the problem of computing all the prime numbers up
to n — Eratosthenes’ sieve algorithm, which has complexity
O(n · log(log(n)))
Finding the Index at which an Element Appears in an Array
When the input array is NOT SORTED
When the input array IS SORTED
???
}
Searching for a Target Element in an Unsorted Array
Problem: For a given array and a given target element, find the
index in the array that contains an element that is equal to the
target element. If the target element does not appear in the array
then the result should be −1.
function linearSearch ( array , target elem ) {
f o r ( l e t i = 0 ; i < a r r a y . l e n g t h ; i = i + 1 ) { / / t r a v e r s e b o xe s
l e t c u r r e n t e l e m = a r r a y [ i ] ; / / l o o k i n s i d e t h e c u r r e n t box
i f ( c u r r e n t e l e m === t a r g e t e l e m ) { / / j u s t got l u cky
r e t u r n i ; / / y e l l t h e box ’ s i n d e x and l e a v e i m m e d i a t e l y
}
}
r e t u r n −1; / / u n l u c k y : t a r g e t e l e m e n t i s n o t i n a r r a y
}
Searching for a Target Element in an Unsorted Array
Problem: For a given array and a given target element, find the
index in the array that contains an element that is equal to the
target element. If the target element does not appear in the array
then the result should be −1.
function linearSearch ( array , target elem ) {
f o r ( l e t i = 0 ; i < a r r a y . l e n g t h ; i = i + 1 ) { / / t r a v e r s e b o xe s
l e t c u r r e n t e l e m = a r r a y [ i ] ; / / l o o k i n s i d e t h e c u r r e n t box
i f ( c u r r e n t e l e m === t a r g e t e l e m ) { / / j u s t got l u cky
r e t u r n i ; / / y e l l t h e box ’ s i n d e x and l e a v e i m m e d i a t e l y
}
}
r e t u r n −1; / / u n l u c k y : t a r g e t e l e m e n t i s n o t i n a r r a y
}
Each steps halves the size of the to-be-searched subarray, hence the algorithm takes
sublinear time (log (n) steps), where n is the size of the original array.
Binary Search on a Sorted Array
Variables L, H denote the low (start) and high (end) indices of the
current (to-be-searched) sub-array, and m the middle position.
function binarySearch( array, target ) {
let L = 0, H = array.length - 1;
while (L <= H) {
let m = Math.floor( (L + H) / 2 );
let elem = array[m];
if (elem === target) {
return m; // target found at index m
} else {
// compute the sub-array for next step
if (elem < target) L = m + 1;
else H = m - 1;
}
}
return -1;
}
Binary Search: Number of Operations in Worst Case
Assume the length of the original array is n. Each iteration halves
the length of the to-be-searched subarray. The worst case is when
the target element is not part of the array.
After iteration 1, the length of the to-be-searched subarray is n
2
= n
21
After iteration 2, the length of the to-be-searched subarray is n
4
= n
22
After iteration 3, the length of the to-be-searched subarray is n
8
= n
23
...
After iteration i, the length of the to-be-searched subarray is n
2i
The loop ends, say after x iterations, when the to-be-searched subarray has length 1.
log2 (n) grows much, much slower than n (see next slide)!
Runtime of Searching in an Unsorted vs Sorted Array
Assume the length of the original array is n.
searching in an unsorted array takes time n
binary search on a sorted array takes time log2 (n)
Example: assume a program that receives n and m as integral arguments, and performs, in
the worst case, a number of operations equal to
3 · m · n + 1000000 · m + 100 · n + 1000000000 + 235 · m · n2 .
Example: assume a program that receives n and m as integral arguments, and performs, in
the worst case, a number of operations equal to
3 · m · n + 1000000 · m + 100 · n + 1000000000 + 235 · m · n2 .
O(1) denotes a constant number of operations (i.e., does not depends on the input).
O(log(n)) denotes logarithmic time, i.e., the solution x of equation n = 2x . Can efficiently
compute for very large values of parameter n, think: binary search.
O(n) denotes linear time (proportional with the input n).
Can compute large sizes, think: linear search, map, reduce, filter, etc.
O(n · log(n)) think: computing n binary searches on an array of size n. Can compute largish sizes.
O(n2 ) denotes quadratic time; impractical but for small n.
O(2n ) denotes exponential time; impractical but for tiny n.
Constant Time Complexity O(1)
The number of operations is constant (does not depend on input)
function f ( a , b ) {
let x = a * b;
let y = a + b;
return x * y ;
}
f u n c t i o n min ( a , b ) {
i f ( a < b ) return a ;
else return b ;
}
function g ( array ) {
let s = 0;
f o r ( i = 0 ; i < 1000000; i = i + 1 ) {
s = s + arr [ i ] ;
}
return s ;
}
function h ( array ) {
let s = 0;
for ( i = 0; i < array . length ; i = i +1) {
s = s + arr [ i ] ;
}
return s ;
}
f o r ( l e t j = 0 ; j <n ; j = j + 1 ) {
table [ i ] [ j ] = ( i +1) * ( j + 1 ) ;
}
}
return table ;
}
One can test whether a√ number n is prime by checking whether it is divisible with any
integer between 2 and n.
we will use the modulo operator: x % y is the reminder resulted from dividing x
by y as integers; the reminder is always smaller than y, for example:
14 % 3 results in 2 because 14 = 3*4 + 2
15 % 2 results in 1 because 15 = 2*7 + 1
f u n c t i o n i s Pr i m e ( n ) {
i f ( n < 2 | | Math . f l o o r ( n ) ! = = n )
r e t u r n false ;
f o r ( l e t i = 2 ; i <= Math . s q r t ( n ) ; i = i + 1 ) {
i f ( ( n % i ) === 0 )
r e t u r n false ;
}
r e t u r n true ;
}
One can test whether a√ number n is prime by checking whether it is divisible with any
integer between 2 and n.
we will use the modulo operator: x % y is the reminder resulted from dividing x
by y as integers; the reminder is always smaller than y, for example:
14 % 3 results in 2 because 14 = 3*4 + 2
15 % 2 results in 1 because 15 = 2*7 + 1
f u n c t i o n i s Pr i m e ( n ) {
i f ( n < 2 | | Math . f l o o r ( n ) ! = = n )
r e t u r n false ;
f o r ( l e t i = 2 ; i <= Math . s q r t ( n ) ; i = i + 1 ) {
i f ( ( n % i ) === 0 )
r e t u r n false ;
}
r e t u r n true ;
}
Iteration i=4 finds A[i] to be false, hence does not execute the
inner loop.
Computing Prime Numbers up to N in Near-Linear Time
Initially the array A contains only true values. After step (2):
The indices (top) and values (bottom) of array A just before the loop:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
F F T T T T T T T T T T T T T T T
Iteration i=2 finds A[i] to be true and
sets locations j = 4, 6, 8, 10, 12, 14, 16 to False (F)
The indices (top) and values (bottom) of array A just before the loop:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
F F T T F T F T F T F T F T F T F
Iteration i=3 finds A[i] to be true and sets locations j = 9, 12, 15 to F
The indices (top) and values (bottom) of array A just before the loop:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
F F T T F T F T F F F T F T F F F