Sortowanie przez wstawianie, wyszukiwanie binarne, algorytmy na liczbach.
Algorytm buduje posortowaną część listy element po elemencie — każdy nowy element jest wstawiany w odpowiednie miejsce w już posortowanej części. Działa jak sortowanie kart w ręce.
def insertion_sort(lista):
for i in range(1, len(lista)):
klucz = lista[i] # element do wstawienia
j = i - 1
# Przesuń elementy większe od klucza w prawo
while j >= 0 and lista[j] > klucz:
lista[j + 1] = lista[j]
j -= 1
lista[j + 1] = klucz # wstaw klucz na miejsce
# Wersja z debugiem — pokazuje każde wstawienie
def insertion_sort_debug(lista):
for i in range(1, len(lista)):
klucz = lista[i]
j = i - 1
while j >= 0 and lista[j] > klucz:
lista[j + 1] = lista[j]
j -= 1
lista[j + 1] = klucz
print(f"Wstawiam {klucz:2}: {lista}")
# Test
dane = [12, 11, 13, 5, 6]
print("Przed:", dane)
insertion_sort_debug(dane)
print("Po: ", dane)
# Wstawiam 11: [11, 12, 13, 5, 6]
# Wstawiam 13: [11, 12, 13, 5, 6]
# Wstawiam 5: [ 5, 11, 12, 13, 6]
# Wstawiam 6: [ 5, 6, 11, 12, 13]
Algorytm dla posortowanych list — za każdym razem sprawdza środkowy element i eliminuje połowę zakresu poszukiwań. Znacznie szybszy od liniowego dla dużych danych.
lewy, prawy, srodeklewy > prawy (element nie istnieje)srodek = (lewy + prawy) // 2 — dzielenie całkowite. Dla lewy=0, prawy=8: środek=4. Jeśli szukana < lista[4] — szukamy w [0..3]. Jeśli większa — w [5..8].
def wyszukiwanie_binarne(lista, szukana):
"""Zwraca indeks elementu lub -1. Lista musi być posortowana."""
lewy = 0
prawy = len(lista) - 1
kroki = 0
while lewy <= prawy:
kroki += 1
srodek = (lewy + prawy) // 2
if lista[srodek] == szukana:
print(f"Znaleziono po {kroki} krokach.")
return srodek
elif lista[srodek] < szukana:
lewy = srodek + 1 # szukamy w prawej połowie
else:
prawy = srodek - 1 # szukamy w lewej połowie
print(f"Nie znaleziono po {kroki} krokach.")
return -1
# Test
posortowane = [2, 5, 8, 12, 16, 23, 38, 45, 56, 72, 91]
idx = wyszukiwanie_binarne(posortowane, 23)
print(f"23 → indeks: {idx}") # indeks: 5
idx = wyszukiwanie_binarne(posortowane, 50)
print(f"50 → indeks: {idx}") # -1
# Porównanie z liniowym — dla 11 elementów:
# Liniowe max 11 porównań
# Binarne max 4 porównania (log2(11) ≈ 3.5)
Wyszukiwanie binarne można elegancko zapisać rekurencyjnie — problem dzieli się na mniejszy podproblem za każdym wywołaniem.
lewy > prawy — nie znalezionolista[srodek] == szukana — znalezionodef binarne_rek(lista, szukana, lewy, prawy):
"""Rekurencyjne wyszukiwanie binarne."""
# Warunek bazowy — nie znaleziono
if lewy > prawy:
return -1
srodek = (lewy + prawy) // 2
# Warunek bazowy — znaleziono
if lista[srodek] == szukana:
return srodek
# Szukaj w prawej połowie
elif lista[srodek] < szukana:
return binarne_rek(lista, szukana, srodek + 1, prawy)
# Szukaj w lewej połowie
else:
return binarne_rek(lista, szukana, lewy, srodek - 1)
# Wygodny wrapper
def wyszukaj(lista, szukana):
return binarne_rek(lista, szukana, 0, len(lista) - 1)
# Test
dane = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
print(wyszukaj(dane, 7)) # 3
print(wyszukaj(dane, 13)) # 6
print(wyszukaj(dane, 4)) # -1
# Wizualizacja kroków rekurencji dla szukanej=7:
# binarne_rek([1..19], 7, 0, 9) → srodek=4, lista[4]=9 > 7
# binarne_rek([1..19], 7, 0, 3) → srodek=1, lista[1]=3 < 7
# binarne_rek([1..19], 7, 2, 3) → srodek=2, lista[2]=5 < 7
# binarne_rek([1..19], 7, 3, 3) → srodek=3, lista[3]=7 == 7 ✓
Klasyczne algorytmy matematyczne często pojawiają się na egzaminie INF.04 — musisz je znać i implementować zarówno iteracyjnie jak i rekurencyjnie.
nwd(a,b) = nwd(b, a%b)nww(a,b) = a*b // nwd(a,b)# NWD — iteracyjnie (Euklides)
def nwd(a, b):
while b != 0:
a, b = b, a % b
return a
# NWW ze wzoru
def nww(a, b):
return a * b // nwd(a, b)
print(f"NWD(48, 18) = {nwd(48, 18)}") # 6
print(f"NWW(4, 6) = {nww(4, 6)}") # 12
# Dzielniki liczby
def dzielniki(n):
wynik = []
for i in range(1, int(n**0.5) + 1):
if n % i == 0:
wynik.append(i)
if i != n // i:
wynik.append(n // i)
return sorted(wynik)
print(dzielniki(36)) # [1, 2, 3, 4, 6, 9, 12, 18, 36]
# Rozkład na czynniki pierwsze
def czynniki_pierwsze(n):
wynik = []
d = 2
while d * d <= n:
while n % d == 0:
wynik.append(d)
n //= d
d += 1
if n > 1:
wynik.append(n)
return wynik
print(czynniki_pierwsze(360)) # [2, 2, 2, 3, 3, 5]
Algorytmy tekstowe — sprawdzanie właściwości słów i zdań. Częste tematy egzaminacyjne.
s[0] = 'X' → TypeError. Buduj nowy string: przez konkatenację, przez listę znaków → ''.join(lista), lub przez replace().
# Palindrom — bez slice
def czy_palindrom(s):
s = s.lower().replace(" ", "")
n = len(s)
for i in range(n // 2):
if s[i] != s[n - 1 - i]:
return False
return True
print(czy_palindrom("kajak")) # True
print(czy_palindrom("Kobyła ma mały bok")) # True
print(czy_palindrom("Python")) # False
# Anagram — przez sortowanie
def czy_anagram(s1, s2):
s1 = s1.lower().replace(" ", "")
s2 = s2.lower().replace(" ", "")
return sorted(s1) == sorted(s2)
print(czy_anagram("listen", "silent")) # True
print(czy_anagram("hello", "world")) # False
# Zliczanie liter (bez Counter)
def zlicz_litery(tekst):
licznik = {}
for znak in tekst.lower():
if znak.isalpha():
licznik[znak] = licznik.get(znak, 0) + 1
return dict(sorted(licznik.items()))
print(zlicz_litery("Python"))
# {'h': 1, 'n': 1, 'o': 1, 'p': 1, 't': 1, 'y': 1}
Algorytm Euklidesa to jeden z najstarszych algorytmów — pochodzi z ok. 300 p.n.e. Na egzaminie INF.04 pojawia się bardzo często w różnych wariantach.
nwd(a, b) = nwd(b, a mod b)def nwd_debug(a, b):
"""NWD z wypisaniem każdego kroku."""
krok = 0
print(f"nwd({a}, {b})")
while b != 0:
krok += 1
r = a % b
print(f" krok {krok}: nwd({b}, {a}%{b}={r})")
a, b = b, r
print(f" wynik: {a}")
return a
nwd_debug(48, 18)
# nwd(48, 18)
# krok 1: nwd(18, 48%18=12)
# krok 2: nwd(12, 18%12=6)
# krok 3: nwd(6, 12%6=0)
# wynik: 6
# NWD wielu liczb — przez reduce
from functools import reduce
def nwd_wielu(*liczby):
return reduce(nwd, liczby)
print(nwd_wielu(12, 18, 24)) # 6
print(nwd_wielu(100, 75, 50)) # 25
# NWW wielu liczb
def nww_wielu(*liczby):
return reduce(nww, liczby)
print(nww_wielu(4, 6, 10)) # 60
Posortuj listę [9, 3, 7, 1, 8, 4] przez wstawianie. Dla każdego wstawianego elementu wypisz: który element wstawiasz, ile przesunięć wykonałeś i stan listy po wstawieniu.
lista = [9, 3, 7, 1, 8, 4]
print(f"Start: {lista}")
lacznie_przesuniec = 0
for i in range(1, len(lista)):
klucz = lista[i]
j = i - 1
przesuniecia = 0
while j >= 0 and lista[j] > klucz:
lista[j + 1] = lista[j]
j -= 1
przesuniecia += 1
lista[j + 1] = klucz
lacznie_przesuniec += przesuniecia
print(f"Wstawiam {klucz}: "
f"przesunięć={przesuniecia}, "
f"lista={lista}")
print(f"\nWynik: {lista}")
print(f"Łącznie przesunięć: {lacznie_przesuniec}")
lista[j+1] = klucz — po wyjściu z pętli j wskazuje na ostatni element nie większy od klucza (lub j=-1 gdy klucz jest mniejszy od wszystkich). j+1 to właściwa pozycja.
Napisz program który symuluje wyszukiwanie binarne krok po kroku — dla każdej iteracji wypisz: lewy i prawy zakres, środkowy indeks i element, oraz decyzję (szukam w lewej/prawej/znalazłem). Na końcu podsumuj ile kroków zajęło.
def binarne_symulacja(lista, szukana):
lewy = 0
prawy = len(lista) - 1
krok = 0
print(f"Szukam {szukana} w: {lista}")
print("-" * 50)
while lewy <= prawy:
krok += 1
srodek = (lewy + prawy) // 2
elem = lista[srodek]
print(f"Krok {krok}: zakres [{lewy}..{prawy}], "
f"środek={srodek}, wartość={elem}")
if elem == szukana:
print(f"✓ Znaleziono na indeksie {srodek}!")
return srodek
elif elem < szukana:
print(f" {elem} < {szukana} → szukam w prawej")
lewy = srodek + 1
else:
print(f" {elem} > {szukana} → szukam w lewej")
prawy = srodek - 1
print(f"✗ Nie znaleziono. Kroków: {krok}")
return -1
dane = list(range(1, 22, 2)) # [1,3,5,7,9,11,13,15,17,19,21]
binarne_symulacja(dane, 13)
lewy = srodek — środkowy element był już sprawdzony i nie jest szukanym. Musimy go wykluczyć dodając lub odejmując 1.
<, pominęlibyśmy przypadek gdy zakres ma jeden element.
Wczytaj dwie liczby naturalne. Oblicz i wypisz ich NWD i NWW. Wypisz wizualizację kroków algorytmu Euklidesa. Sprawdź czy podane liczby są względnie pierwsze (NWD = 1).
def nwd_z_krokami(a, b):
kroki = []
oryg_a, oryg_b = a, b
while b != 0:
r = a % b
kroki.append(f" nwd({a}, {b}): {a} = {b}×{a//b} + {r}")
a, b = b, r
kroki.append(f" nwd({a}, 0) = {a} ← wynik")
return a, kroki
a = int(input("Pierwsza liczba: "))
b = int(input("Druga liczba: "))
wynik_nwd, kroki = nwd_z_krokami(a, b)
wynik_nww = a * b // wynik_nwd
print(f"\nAlgorytm Euklidesa dla nwd({a}, {b}):")
for k in kroki:
print(k)
print(f"\nNWD({a}, {b}) = {wynik_nwd}")
print(f"NWW({a}, {b}) = {wynik_nww}")
if wynik_nwd == 1:
print(f"\n{a} i {b} są względnie pierwsze!")
else:
print(f"\n{a} i {b} nie są względnie pierwsze.")
print(f"Ułamek {a}/{b} = {a//wynik_nwd}/{b//wynik_nwd}")
a * b // wynik_nwd — mnożymy oryginalne wartości (nie te zmodyfikowane przez algorytm Euklidesa). Dlatego zapamiętujemy oryginalne a i b.
Algorytmy z tego kafelka to fundament egzaminu — ćwicz pisanie ich z pamięci bez zaglądania do notatek.
Insertion sort malejąco
Zmodyfikuj insertion sort żeby sortował malejąco. Przetestuj na liście 8 losowych liczb. Wypisz stan listy po każdym wstawieniu.
> na < w pętli whileLiczby względnie pierwsze
Wypisz wszystkie pary liczb (a, b) z zakresu 1–20 dla których NWD = 1 (są względnie pierwsze). Policz ile takich par jest.
nwd(a, b) == 1Binarne — pierwsza i ostatnia pozycja
Na posortowanej liście z duplikatami znajdź binarnie pierwszą i ostatnią pozycję szukanej wartości. Np. w [1,2,2,2,3,4] dla szukanej=2: pierwsza=1, ostatnia=3.
Rozkład na czynniki
Wczytaj liczbę naturalną. Wypisz jej rozkład na czynniki pierwsze w postaci: 360 = 2³ × 3² × 5¹. Użyj słownika do zliczania potęg.
czynniki_pierwsze() → zlicz powtórzenia → formatuj z ³ ² ¹Anagramy w liście
Masz listę słów. Pogrupuj je w pary/grupy anagramów. Wypisz każdą grupę osobno. Np. ["listen","silent","hello","world","enlist"] → dwie grupy.
tuple(sorted(slowo))Sortowanie przez scalanie — merge sort
Zaimplementuj merge sort (sortowanie przez scalanie) — podziel listę na pół rekurencyjnie, posortuj każdą połowę, scal wyniki. Złożoność O(n log n).
len(lista) <= 1, scal dwie posortowane listy porównując kolejne elementy