vyhľadávanie A triedenie
O(1), O(log n), O(n), O(n²)Keď píšeme programy, nestačí, aby „fungovali“. Musia byť aj efektívne.
Koľko prostriedkov a zdrojov je potrebné na vyriešenie úlohy:
empirické meranie času potrebného na vykonanie konkrétnej úlohy na konkrétnej hardvérovej konfigurácii.
"lepší" hardvér - potrebuje menej času
Benchmark hovorí o výkone hardvéru, ale nehovorí o efektivite implementácie úlohy
Ako hodnotíme "Výkonnosť" algoritmu?
Výkonnosť algoritmu môžeme benchmarkovať:
Potrebujeme:
Výsledky sú závislé na konkrétnej imlementácii, na konkrétnych dátach a na konkrétnom hardvéri
Výsledky znázorníme ako krivku závislosti veľkosti dát a potrebného času alebo potrebnej pamäte
T (s)
^
| *
| *
| *
| *
| *
| *
+---------------------------> N
Ktorá krivka je lepšia?
Príklady na krivky:
T (s)
^
| x *
| x *
| ..... x .... * ............
| x *
| x *
| x*
+---------------------------> N
Pre získanie všeobecného porovnania je potrebná teoretická analýza:
Potom nemusíme nič programovať a spúšťať, stačí porozmýšlač
Popisuje, ako sa mení počet teoretických operácií v závislosti od veľkosti vstupu n.
Používame najmä:
👉 Ignorujeme konštanty a menej významné členy.
Operácia trvá rovnako dlho bez ohľadu na veľkosť vstupu.
def get_first(arr):
return arr[0]
✔ Vždy 1 operácia ✔ Nezáleží na veľkosti poľa
Čas rastie priamo úmerne s n.
def sum_array(arr):
total = 0
for x in arr:
total += x
return total
✔ Pre každý prvok robíme jednu operáciu ✔ Pri dvojnásobnom vstupe → dvojnásobný čas
Typicky vnorené cykly.
def print_pairs(arr):
for i in arr:
for j in arr:
print(i, j)
✔ Počet operácií ≈ n × n ✔ Veľmi rýchlo rastie
Zmenšujeme problém na polovicu (napr. binárne vyhľadávanie).
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
✔ Každým krokom zmenšíme problém na polovicu ✔ Veľmi efektívne aj pre veľké dáta
Niektoré algoritmy majú viac častí:
def example(arr):
# O(n)
for x in arr:
print(x)
# O(n²)
for i in arr:
for j in arr:
print(i, j)
👉 Výsledná zložitosť:
O(n + n²) = O(n²)
✔ Berieme najdominantnejší člen
Niektoré algoritmy sa správajú rôzne:
def find(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i
return -1
def f1(arr):
for x in arr:
print(x)
def f2(arr):
for x in arr:
print(x)
print(x)
print(x)
👉 Konštanta (3) nie je dôležitá pri veľkých n.
Od najlepšieho po najhorší:
O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ)
Vyhľadávanie znamená nájsť prvok v kolekcii.
Typické otázky:
Zložitosť závisí od toho, či sú dáta usporiadané.
Prechádzame prvky jeden po druhom.
def linear_search(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i
return -1
Zložitosť:
Lineárne vyhľadávanie je vhodné, keď:
Nevýhoda:
pri veľkom n môže byť pomalé
Binárne vyhľadávanie funguje iba na zoradených dátach.
Myšlienka:
Každým krokom zmenšíme problém približne na polovicu.
def binary_search(arr, target):
left = 0
right = len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
if arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
Zložitosť: O(log n)
Hľadáme 23:
[2, 5, 8, 12, 16, 23, 38, 56]
↑
12
23 je väčšie ako 12, ľavú polovicu zahodíme.
[16, 23, 38, 56]
↑
23
Hodnota bola nájdená.
| Vlastnosť | Lineárne | Binárne |
|---|---|---|
| Vyžaduje zoradené dáta | nie | áno |
| Najhorší prípad | O(n) | O(log n) |
| Funguje na ľubovoľnom zozname | áno | nie |
| Vhodné pre malé dáta | áno | áno |
| Vhodné pre veľké zoradené dáta | horšie | veľmi dobré |
Ak dáta ešte nie sú zoradené, binárne vyhľadávanie vyžaduje triedenie.
arr = sorted(arr) # O(n log n)
index = binary_search(arr, target) # O(log n)
Pre jedno vyhľadanie sa to nemusí oplatiť.
Pre veľa vyhľadávaní nad rovnakými dátami sa triedenie môže vyplatiť.
Ak nás zaujíma iba existencia prvku, často je vhodná množina.
items = {10, 20, 30, 40}
print(30 in items)
Priemerná zložitosť:
O(1)
Množina používa hash tabuľku.
Príklad analýzy asymptotickej zložitosti
def has_duplicates(arr):
for i in range(len(arr)):
for j in range(i + 1, len(arr)):
if arr[i] == arr[j]:
return True
return False
def has_duplicates(arr):
seen = set()
for x in arr:
if x in seen:
return True
seen.add(x)
return False
✔ Použitie množiny (set) dramaticky zlepší výkon
Triedenie je základ pre:
Jednoduchý, ale neefektívny algoritmus.
Opakovane prechádzame pole a vymieňame susedné prvky, ak sú v zlom poradí.
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
👉 Veľmi pomalý pre veľké dáta
V každom kroku nájdeme najmenší prvok a dáme ho na správne miesto.
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_idx = i
for j in range(i + 1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
👉 Jednoduchý, ale neefektívny
Budujeme zoradenú časť poľa postupne.
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
👉 V praxi veľmi dobrý pre malé alebo skoro zoradené polia
Efektívny algoritmus založený na divide & conquer.
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] < right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
👉 Stabilný a spoľahlivý algoritmus
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
👉 Veľmi rýchly v praxi, ale závisí od pivotu
arr.sort()
# alebo
sorted_arr = sorted(arr)
👉 Kombinácia:
✔ Optimalizovaný pre reálne dáta ✔ Používa sa v praxi
| Algoritmus | Najhorší prípad | Priemerný | Najlepší |
|---|---|---|---|
| Bubble Sort | O(n²) | O(n²) | O(n) |
| Selection Sort | O(n²) | O(n²) | O(n²) |
| Insertion Sort | O(n²) | O(n²) | O(n) |
| Merge Sort | O(n log n) | O(n log n) | O(n log n) |
| Quick Sort | O(n²) | O(n log n) | O(n log n) |
import random
import time
arr = [random.randint(0, 10000) for _ in range(1000)]
start = time.time()
sorted(arr) # Timsort
print("Built-in:", time.time() - start)
start = time.time()
bubble_sort(arr.copy())
print("Bubble:", time.time() - start)
👉 Rozdiel môže byť stokrát až tisíckrát
Skús určiť zložitosť:
def mystery(n):
i = 1
while i < n:
print(i)
i *= 2
Analýza asymptotickej zložitosti pomocou abstraktných strojov:
📚 Referencie: EMU Studio
sorted(arr)