An algorithm is a procedure that takes any of the possible input instances and transforms it to the desired output.
Important issues: correctness, elegance and efficiency.
Is this really necessary?
Time complexity ≠ Space complexity ≠ Complexity of algorithm
import time
start = time.time() # Return the time in seconds since the epoch.
my_algo(some_input)
end = time.time()
print(end - start)
0.048032498359680176
import timeit
timeit.timeit('my_algo(some_input)', number=1000)
1000 loops, best of 3: 50.3 ms per loop
import timeit
inputs = [1000, 10000, 500000, 1000000]
for input in inputs:
timeit.timeit('my_algo(input)', number=1000)
list of 1000 items:
1000 loops, best of 3: 50.3 ms per loop
list of 10000 items:
1000 loops, best of 3: 104.7 ms per loop
list of 500000 items:
1000 loops, best of 3: 459.1 ms per loop
list of 1000000 items:
1000 loops, best of 3: 3.12 s per loop
# Intel Core i7-3970X @ 3.50GHz, RAM 8 Gb, Ubuntu 12.10 x64, Python 3.3.0
import timeit
inputs = [1000, 10000, 500000, 1000000]
for input in inputs:
timeit.timeit('my_algo(input)', number=1000)
list of 1000 items:
1000 loops, best of 3: 50.3 ms per loop
list of 10000 items:
1000 loops, best of 3: 104.7 ms per loop
list of 500000 items:
1000 loops, best of 3: 459.1 ms per loop
list of 1000000 items:
1000 loops, best of 3: 3.12 s per loop
T(n) – running time as a function of n, where n – size of input.
n → ∞
Random-Access Machine (RAM)
def linear_search(my_item, items):
for position, item in enumerate(items):
if my_item == item:
return position
T(n) = n ?
T(n) = 1/2 ⋅ n ?
T(n) = 1 ?
def linear_search(my_item, items):
for position, item in enumerate(items):
if my_item == item:
return position
Worst case: T(n) = n
Average case: T(n) = 1/2 ⋅ n
Best case: T(n) = 1
T(n) = O(n)
O(g(n)) = {f(n): there exist positive constants c and n0 such that 0 ≤ f(n) ≤ c⋅g(n) for all n ≥ n0}
T(n) ∈ O(g(n))
or
T(n) = O(g(n))
Ω(g(n)) = {f(n): there exist positive constants c and n0 such that 0 ≤ c⋅g(n) ≤ f(n) for all n ≥ n0}
T(n) ∈ Ω(g(n))
or
T(n) = Ω(g(n))
Θ(g(n)) = {f(n): there exist positive constants c1, c2 and n0 such that 0 ≤ c1⋅g(n) ≤ f(n) ≤ c2⋅g(n) for all n ≥ n0}
T(n) ∈ Θ(g(n))
or
T(n) = Θ(g(n))
3⋅n2 - 100⋅n + 6 = O(n2),
because we can choose c = 3 and
3⋅n2 > 3⋅n2 - 100⋅n + 6
100⋅n2 - 70⋅n - 1 = O(n2),
because we can choose c = 100 and
100⋅n2 > 100⋅n2 - 70⋅n - 1
3⋅n2 - 100⋅n + 6 ≈ 100⋅n2 - 70⋅n - 1
T(n) = O(n)
def linear_search(my_item, items):
for position, item in enumerate(items):
print('position – {0}, item – {0}'.format(position, item))
print('Compare two items.')
if my_item == item:
print('Yeah!!!')
print('The end!')
return position
T(n) = O(3⋅n + 2) = O(n)
Speed of "Villarriba version" ≈ Speed of "Villabajo version"
However, all you really need to understand is that:
n! ≫ 2n ≫ n3 ≫ n2 ≫ n⋅log(n) ≫ n ≫ log(n) ≫ 1
Each operation takes one nanosecond (10-9 seconds).
CPU ≈ 1 GHz
def binary_search(seq, t):
min = 0; max = len(seq) - 1
while 1:
if max < min:
return -1
m = (min + max) / 2
if seq[m] < t:
min = m + 1
elif seq[m] > t:
max = m - 1
else:
return m
T(n) = O(log(n))
Search with index vs Search without index
Binary search vs Linear search
O(log(n)) vs O(n)
On the basis of the issues discussed here, I propose that members of SIGACT, and editors of computer science and mathematics journals, adopt the O, Ω and Θ notations as defined above, unless a better alternative can be found reasonably soon.
D. E. Knuth, "Big Omicron and Big Omega and BIg Theta", SIGACT News, 1976.
Source: https://github.com/vaxXxa/talks