0% found this document useful (0 votes)
50 views52 pages

Lec11 Complexity PDF

The document discusses program efficiency and different methods for evaluating it. It introduces timing programs, counting operations, and analyzing asymptotic complexity using Big O notation. Key points covered include: timing is inconsistent, counting operations is better but still imperfect, and asymptotic analysis using orders of growth such as constant, logarithmic, linear, and exponential is best for evaluating how algorithms scale with larger inputs.

Uploaded by

Torha Sleven
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
50 views52 pages

Lec11 Complexity PDF

The document discusses program efficiency and different methods for evaluating it. It introduces timing programs, counting operations, and analyzing asymptotic complexity using Big O notation. Key points covered include: timing is inconsistent, counting operations is better but still imperfect, and asymptotic analysis using orders of growth such as constant, logarithmic, linear, and exponential is best for evaluating how algorithms scale with larger inputs.

Uploaded by

Torha Sleven
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 52

UNDERSTANDING

PROGRAM
EFFICIENCY

6.00.01X LECTURE 1
WANT TO UNDERSTAND
EFFICIENCY OF PROGRAMS
 computers are fast and getting faster – so maybe efficient
programs don’t matter?
◦ but data sets can be very large
◦ thus, simple solutions may simply not scale with size in acceptable
manner
 so how could we decide which option for program is most
efficient?

 separate time and space efficiency of a program


 tradeoff between them – will focus on time efficiency

6.00.01X LECTURE 2
WANT TO UNDERSTAND
EFFICIENCY OF PROGRAMS
Challenges in understanding efficiency of solution to a
computational problem:
 a program can be implemented in many different
ways
 you can solve a problem using only a handful of
different algorithms
 would like to separate choices of implementation from
choices of more abstract algorithm

6.00.01X LECTURE 3
HOW TO EVALUATE
EFFICIENCY OF PROGRAMS
 measure with a timer
 count the operations
 abstract notion of order of growth

6.00.01X LECTURE 4
TIMING A PROGRAM
 use time module
import time
 recall that
importing means to
bring in that class def c_to_f(c):
into your own file return c*9/5 + 32

 start clock t0 = time.clock()


 call function c_to_f(100000)
t1 = time.clock() - t0
 stop clock Print("t =", t, ":", t1, "s,”)

6.00.01X LECTURE 5
TIMING PROGRAMS IS
INCONSISTENT
 GOAL: to evaluate different algorithms
 running time varies between algorithms
 running time varies between implementations
 running time varies between computers
 running time is not predictable based on small
inputs

 time varies for different inputs but


cannot really express a relationship
between inputs and time

6.00.01X LECTURE 6
COUNTING OPERATIONS
 assume these steps take def c_to_f(c):
constant time: return c*9.0/5 + 32
• mathematical operations def mysum(x):
• comparisons total = 0
• assignments for i in range(x+1):
total += i
• accessing objects in memory return total
• then count the number of
operations executed as mysum  1+3x ops
function of size of input

6.00.01X LECTURE 7
COUNTING OPERATIONS IS
BETTER, BUT STILL…
 GOAL: to evaluate different algorithms
 count depends on algorithm
 count depends on implementations
 count independent of computers
 no real definition of which operations to count

 count varies for different inputs and


can come up with a relationship
between inputs and the count

6.00.01X LECTURE 8
STILL NEED A BETTER WAY
• timing and counting evaluate implementations
• timing evaluates machines

• want to evaluate algorithm


• want to evaluate scalability
• want to evaluate in terms of input size

6.00.01X LECTURE 9
NEED TO CHOOSE WHICH INPUT TO
USE TO EVALUATE A FUNCTION
 want to express efficiency in terms of input, so need
to decide what your input is
 could be an integer
-- mysum(x)
 could be length of list
-- list_sum(L)
 you decide when multiple parameters to a function
-- search_for_elmt(L, e)

6.00.01X LECTURE 10
DIFFERENT INPUTS CHANGE
HOW THE PROGRAM RUNS
 a function that searches for an element in a list
def search_for_elmt(L, e):
for i in L:
if i == e:
return True
return False
 when e is first element in the list  BEST CASE
 when e is not in list  WORST CASE
 when look through about half of the elements in
list  AVERAGE CASE
 want to measure this behavior in a general way
6.00.01X LECTURE 11
BEST, AVERAGE, WORST CASES
 suppose you are given a list L of some length len(L)
 best case: minimum running time over all possible inputs
of a given size, len(L)
• constant for search_for_elmt
• first element in any list
 average case: average running time over all possible inputs
of a given size, len(L)
• practical measure
 worst case: maximum running time over all possible inputs
of a given size, len(L)
• linear in length of list for search_for_elmt
• must search entire list and not find it

6.00.01X LECTURE 12
ORDERS OF GROWTH
Goals:
 want to evaluate program’s efficiency when input is very
big
 want to express the growth of program’s run time as
input size grows
 want to put an upper bound on growth
 do not need to be precise: “order of” not “exact” growth
 we will look at largest factors in run time (which section
of the program will take the longest to run?)

6.00.01X LECTURE 13
TYPES OF ORDERS OF
GROWTH

6.00.01X LECTURE 14
6.00.01X LECTURE 15
MEASURING ORDER OF
GROWTH: BIG OH NOTATION
 Big Oh notation measures an upper bound on the
asymptotic growth, often called order of growth

 Big Oh or O() is used to describe worst case


• worst case occurs often and is the bottleneck when a
program runs
• express rate of growth of program relative to the input
size
• evaluate algorithm not machine or implementation

6.00.01X LECTURE 16
EXACT STEPS vs O()
def fact_iter(n):
"""assumes n an int >= 0"""
answer = 1
while n > 1:
answer *= n
n -= 1
return answer
 computes factorial
 number of steps:
 worst case asymptotic complexity:
• ignore additive constants
• ignore multiplicative constants

6.00.01X LECTURE 17
SIMPLIFICATION EXAMPLES
 drop constants and multiplicative factors
 focus on dominant terms

: n2 + 2n + 2
: n2 + 100000n + 31000
: log(n) + n + 4
: 0.0001*n*log(n) + 300n
: 2n30 + 3n

6.00.01X LECTURE 18
COMPLEXITY CLASSES
ORDERED LOW TO HIGH
O(1) : constant

O(log n) : logarithmic

O(n) : linear

O(n log n): loglinear

O(nc) : polynomial

O(cn) : exponential
6.00.01X LECTURE 19
ANALYZING PROGRAMS AND
THEIR COMPLEXITY
 combine complexity classes
• analyze statements inside functions
• apply some rules, focus on dominant term
Law of Addition for O():
• used with sequential statements
• O(f(n)) + O(g(n)) is O( f(n) + g(n) )
• for example,
for i in range(n):
print('a')
for j in range(n*n):
print('b')
is O(n) + O(n*n) = O(n+n2) = O(n2) because of dominant term
6.00.01X LECTURE 20
ANALYZING PROGRAMS AND
THEIR COMPLEXITY
 combine complexity classes
• analyze statements inside functions
• apply some rules, focus on dominant term
Law of Multiplication for O():
• used with nested statements/loops
• O(f(n)) * O(g(n)) is O( f(n) * g(n) )
• for example,
for i in range(n):
for j in range(n):
print('a')
is O(n)*O(n) = O(n*n) = O(n2) because the outer loop goes n
times and the inner loop goes n times for every outer loop iter.
6.00.01X LECTURE 21
6.00.01X LECTURE 22
COMPLEXITY CLASSES
 O(1) denotes constant running time
 O(log n) denotes logarithmic running time
 O(n) denotes linear running time
 O(n log n) denotes log-linear running time
 O(nc) denotes polynomial running time (c is a
constant)
 O(cn) denotes exponential running time (c is a
constant being raised to a power based on size of
input)

6.00.01X LECTURE 23
CONSTANT COMPLEXITY
 complexity independent of inputs
 very few interesting algorithms in this class, but can
often have pieces that fit this class
 can have loops or recursive calls, but number of
iterations or calls independent of size of input

6.00.01X LECTURE 24
LOGARITHMIC COMPLEXITY
 complexity grows as log of size of one of its inputs
 example:
◦ bisection search
◦ binary search of a list

6.00.01X LECTURE 25
LOGARITHMIC COMPLEXITY
def intToStr(i):
digits = '0123456789'
if i == 0:
return '0'
result = ''
while i > 0:
result = digits[i%10] + result
i = i//10
return result

6.00.01X LECTURE 26
LOGARITHMIC COMPLEXITY
def intToStr(i): only have to look at loop as
digits = '0123456789' no function calls
if i == 0:
return '0'
within while loop, constant
res = ''
number of steps
while i > 0: how many times through
res = digits[i%10] + res loop?
i = i//10 ◦ how many times can one
return result divide i by 10?
◦ O(log(i))

6.00.01X LECTURE 27
LINEAR COMPLEXITY
 searching a list in sequence to see if an element is present
 add characters of a string, assumed to be composed of
decimal digits
def addDigits(s):
val = 0
for c in s:
val += int(c)
return val
 O(len(s))

6.00.01X LECTURE 28
LINEAR COMPLEXITY
 complexity can depend on number of recursive calls
def fact_iter(n):
prod = 1
for i in range(1, n+1):
prod *= i
return prod
 number of times around loop is n
 number of operations inside loop is a constant
 overall just O(n)

6.00.01X LECTURE 29
O() FOR RECURSIVE
FACTORIAL
def fact_recur(n):
""" assume n >= 0 """
if n <= 1:
return 1
else:
return n*fact_recur(n – 1)
 computes factorial recursively
 if you time it, may notice that it runs a bit slower than
iterative version due to function calls
 still O(n) because the number of function calls is linear
in n
 iterative and recursive factorial implementations are
the same order of growth
6.00.01X LECTURE 30
LOG-LINEAR COMPLEITY
 many practical algorithms are log-linear
 very commonly used log-linear algorithm is merge sort
 will return to this

6.00.01X LECTURE 31
6.00.01X LECTURE 32
POLYNOMIAL COMPLEXITY
 most common polynomial algorithms are quadratic,
i.e., complexity grows with square of size of input
 commonly occurs when we have nested loops or
recursive function calls

6.00.01X LECTURE 33
QUADRATIC COMPLEXITY
def isSubset(L1, L2):
for e1 in L1:
matched = False
for e2 in L2:
if e1 == e2:
matched = True
break
if not matched:
return False
return True

6.00.01X LECTURE 34
QUADRATIC COMPLEXITY
def isSubset(L1, L2): outer loop executed len(L1)
for e1 in L1: times
matched = False
for e2 in L2: each iteration will execute
if e1 == e2: inner loop up to len(L2)
matched = True times
break O(len(L1)*len(L2))
if not matched:
return False worst case when L1 and L2
return True same length, none of
elements of L1 in L2
O(len(L1)2)

6.00.01X LECTURE 35
QUADRATIC COMPLEXITY
find intersection of two lists, return a list with each element
appearing only once
def intersect(L1, L2):
tmp = []
for e1 in L1:
for e2 in L2:
if e1 == e2:
tmp.append(e1)
res = []
for e in tmp:
if not(e in res):
res.append(e)
return res
6.00.01X LECTURE 36
QUADRATIC COMPLEXITY
def intersect(L1, L2): first nested loop takes
tmp = [] len(L1)*len(L2) steps
for e1 in L1:
for e2 in L2: second loop takes at
if e1 == e2: most len(L1) steps
tmp.append(e1)
res = []
latter term
for e in tmp:
overwhelmed by
if not(e in res): former term
res.append(e) O(len(L1)*len(L2))
return res

6.00.01X LECTURE 37
O() FOR NESTED LOOPS
def g(n):
""" assume n >= 0 """
x = 0
for i in range(n):
for j in range(n):
x += 1
return x

 computes n2 very inefficiently


 when dealing with nested loops, look at the ranges
 nested loops, each iterating n times
 O(n2)

6.00.01X LECTURE 38
6.00.01X LECTURE 39
EXPONENTIAL COMPLEXITY
 recursive functions where more than one recursive
call for each size of problem
◦ Towers of Hanoi
 many important problems are inherently exponential
◦ unfortunate, as cost can be high
◦ will lead us to consider approximate solutions more
quickly

6.00.01X LECTURE 40
EXPONENTIAL COMPLEXITY
def genSubsets(L):
res = []
if len(L) == 0:
return [[]] #list of empty list
smaller = genSubsets(L[:-1]) # all subsets without
last element
extra = L[-1:] # create a list of just last element
new = []
for small in smaller:
new.append(small+extra) # for all smaller
solutions, add one with last element
return smaller+new # combine those with last
element and those without
6.00.01X LECTURE 41
EXPONENTIAL COMPLEXITY
def genSubsets(L): assuming append is
res = [] constant time
if len(L) == 0:
time includes time to solve
return [[]]
smaller problem, plus time
smaller = genSubsets(L[:-1])
needed to make a copy of
extra = L[-1:]
all elements in smaller
new = []
problem
for small in smaller:
new.append(small+extra)
return smaller+new

6.00.01X LECTURE 42
EXPONENTIAL COMPLEXITY
def genSubsets(L): but important to think
res = [] about size of smaller
if len(L) == 0:
return [[]] know that for a set of size
smaller = genSubsets(L[:-1]) k there are 2k cases
extra = L[-1:]
new = [] so to solve need 2n-1 + 2n-2
for small in smaller: + … +20 steps
new.append(small+extra)
math tells us this is O(2n)
return smaller+new

6.00.01X LECTURE 43
COMPLEXITY CLASSES
 O(1) denotes constant running time
 O(log n) denotes logarithmic running time
 O(n) denotes linear running time
 O(n log n) denotes log-linear running time
 O(nc) denotes polynomial running time (c is a
constant)
 O(cn) denotes exponential running time (c is a
constant being raised to a power based on size of
input)

6.00.01X LECTURE 44
6.00.01X LECTURE 45
EXAMPLES OF ANALYZING
COMPLEXITY

6.00.01X LECTURE 46
TRICKY COMPLEXITY
def h(n):
""" assume n an int >= 0 """
answer = 0
s = str(n)
for c in s:
answer += int(c)
return answer
 adds digits of a number together
 tricky part
• convert integer to string
• iterate over length of string, not magnitude of input n
• think of it like dividing n by 10 each iteration
 O(log n) – base doesn’t matter

6.00.01X LECTURE 47
COMPLEXITY OF
ITERATIVE FIBONACCI
def fib_iter(n):
if n == 0:
 Best case:
return 0 O(1)
elif n == 1:
return 1  Worst case:
else:
fib_i = 0
O(1) + O(n) + O(1)  O(n)
fib_ii = 1
for i in range(n-1):
tmp = fib_i
fib_i = fib_ii
fib_ii = tmp + fib_ii
return fib_ii

6.00.01X LECTURE 48
COMPLEXITY OF
RECURSIVE FIBONACCI
def fib_recur(n):
""" assumes n an int >= 0 """
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib_recur(n-1) + fib_recur(n-2)

 Worst case:
O(2n)

6.00.01X LECTURE 49
WHEN THE INPUT IS A LIST…
def sum_list(L):
total = 0
for e in L:
total = total + e
return total
 O(n) where n is the length of the list
 O(len(L))
 must define what size of input means
• previously it was the magnitude of a number
• here, it is the length of list

6.00.01X LECTURE 50
BIG OH SUMMARY
 compare efficiency of algorithms
• notation that describes growth
• lower order of growth is better
• independent of machine or specific implementation

 use Big Oh
• describe order of growth
• asymptotic notation
• upper bound
• worst case analysis

6.00.01X LECTURE 51
COMPLEXITY OF COMMON
PYTHON FUNCTIONS
 Lists: n is len(L)  Dictionaries: n is len(d)
• index O(1)  worst case
• store O(1) • index O(n)
• length O(1) • store O(n)
• append O(1) • length O(n)
• == O(n) • delete O(n)
• remove O(n) • iteration O(n)
• copy O(n)  average case
• reverse O(n) • index O(1)
• iteration O(n) • store O(1)
• in list O(n) • delete O(1)
• iteration O(n)
6.00.01X LECTURE 52

You might also like