- Защо е важно СДА-то?
- HackerRank демо
- Обектите в Python
- Bigint
- List comprehension
- GIL (Global Interpreter Lock)
- Видове сложности
- Правила за смятане на Big O изрази
- Основи при изчисляване на сложности
- copy() vs deepcopy()
- using filter()
Присвояване на стойността на масив чрез оператор =, без използването на copy(). Промени в бащата/ детето, водят до промени и в бащата, и в детето:
father = [i for i in range(5)]
child = father
father.insert(2, 99) # same with child.insert()
print("Father: ", father) # [0, 1, 99, 2, 3, 4]
print("Child: ", child) # [0, 1, 99, 2, 3, 4]
Родителят и детето са един и същи обект:
print(id(child) == id(father) # True
Копиране на масив чрез метода copy(). Промени в бащата не се отразяват на промени в детето. Обратното също е вярно.
father = [i for i in range(5)]
child = father.copy()
father.insert(2, 99)
print("Father: ", father) # [0, 1, 99, 2, 3, 4]
print("Child: ", child) # [0, 1, 2, 3, 4]
Родителят и детето са два различни обекта:
print(id(child) != id(father) # True
Копиране на масив, съдържащ съставни обекти (масив от mutable objects) чрез използване на метод copy(). Промени в съставен обект на бащата водят до промени и в детето.
from copy import copy
inner_list = [4, 5]
father = [1, 2, 3, inner_list]
child = copy(father)
father[3].append(6) # equals inner_list.append(6)
print("Father: ", father) # [1, 2, 3, [4, 5, 6]]
print("Child: ", child) # [1, 2, 3, [4, 5, 6]]
Детето и бащата са два различни обекта, но съставните части си остават същите при използването на copy().
print(id(child) != id(father)) # True
print(id(child[3]) == id(father[3])) # True
Копиране на масив, съдържащ съставни обекти (масив от mutable objects) чрез използване на метод deepcopy(). Промени в съставен обект на бащата не водят до промени в детето.
from copy import deepcopy
inner_list = [4, 5]
father = [1, 2, 3, inner_list]
child = deepcopy(father)
father[3].append(6)
print("Father: ", father) # [1, 2, 3, [4, 5, 6]]
print("Child: ", child) # [1, 2, 3, [4, 5]
Детето и бащата са два различни обекта, и съставните части са различни обекти при използването на deepcopy().
print(id(child) != id(father)) # True
print(id(child[3]) == id(father[3])) # True
Filter() се нуждае от аргумент функция, по която да се извърши филтрирането. (В Python функциите притежават способността да бъдат подавани, като аргумент на функция - First-class citizen)
Това може да е наша дефинирана функция:
arr = [i for i in range(10)]
def check_if_even(x):
return x % 2 == 0
even_arr = list(filter(check_if_even, arr))
print(even_arr) # [0, 2, 4, 6, 8]
Или може да е анонимна функция, така наречените ламбда изрази:
arr = [i for i in range(10)]
even_arr = list(filter(lambda x: x % 2 == 0, arr))
print(even_arr) # [0, 2, 4, 6, 8]
- константа - O(1)
- логаритмична - О(logN)
- линейна - O(N)
- енлог - O(NlogN)
- квадратична - O(N2)
- кубична - O(N3)
- експоненциална - O(cN)
Ред на нарастване на времето:
1 < logN < sqrtN < N < NlogN < N2 < N3 < 2N < 3N < N! < NN
-
константите, които са множители не влияят.
O(100N) = O(N)
-
влияе само най-бързо растящата функция, при сбор от множители.
О(N2 + NlogN + N + 1) = O(N2)
-
при произведение от множители, влияят всички функции.
*O(N2 log3N) не може да бъде опростено.
-
основата на логаритъма не влияе.
O(log2N) = O(log3N)
Изписва се просто O(logN). Това не важи за степента на логаритмична функция (горния пример).
Примери:
- О(2N) = O(N) = O(10000N)
- O(10000000) < O(logN)
- O(N + M)
При невъзможност да се определи коя функция е по-голяма N или М, изразът O(N + M) не може да бъде опростен.
Нека имаме 2 компютъра. Компютър А е най-бързият за времето си със производителност 10 милиарда операции в секунда. Компютър Б е обикновен компютър и изчислява 10 милиона операции в секунда.
Задачата на компютрите е да сортират масив с 10 милиона елемента.
Машина А използва Insertion sort със сложност 2N2. Машина Б използва Мerge sort със сложност 50NlogN.
За колко време всяка машина ще се справи със задачата?
Суперкомпютър А:
- S1 = 2N2 стъпки, за N = 107
- V1 = 1010 стъпки/ сек
- => t1 = 20000 сек. = ~5.5 ч.
Компютър Б:
- S2 = 50NlogN стъпки, за N = 107
- V2 = 107 стъпки/ сек
- => t2 = ~1163 сек. = ~20 мин.
Въпреки разликата в производителността и константите в алгоритмите (2 и 50), резултатите са коренно различни.
Още по-съществена разлика се наблюдава при увеличаване на големината на масива 10 пъти. При N = 100 милиона числа, компютър А отнема 23 дни, а компютър Б - 4 часа.
O(1) - връщането на константа:
def get_Pi():
return 3.14
O(1) - връщането на елемент от масив:
def get_5th_element(arr):
return arr[5]
O(N) - еднократно обхождане на масив с големина N.
for el in arr:
print(el)
O(N2) - обхождане на масив с големина N N пъти (чрез вложен цикъл). Обхождане на матрица с размер NxN.
for i in range(N):
for j in range(N):
print(i, j)
O(2N) - Намиране на N-тото число от редицата на Фибоначи. Всяко извикване на функцията, създава 2 нови деца, всяко от които 2 свои деца и т.н.
def fibonacci(N):
if N <= 1:
return N
return fibonacci(N-1) + fibonacci(N-2)
O(logN) - Намиране на колко двойки се съдържат в числото N. На всяко рекурсивно извикване, числото намаля 2 пъти.
def count_deuces(N, count = 0):
if N <= 1:
return count
count += 1
return count_deuces(N // 2, count)
O(N + M) - обхождане на масив с големина N и масив с големина M.
def brothers(N, M):
for i in range(N):
print(i)
for j in range(M):
print(j)
Задачи за упражнение (в LeetCode)
Решения на задачите: тук