English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

nu

gość
1 / ?
powrót do lekcji

Co Sprawia, że Lista Jest Użyteczna

Listy: Twoja Pierwsza Struktura Danych

Lista (zwana tablicą w niektórych językach) przechowuje elementy w określonym porządku. Możesz:

- Dostęp do dowolnego elementu po jego pozycji: names[0] pobiera pierwszy element. O(1) — natychmiast.

- Dodawanie elementów na koniec: names.append("Zoe"). O(1) — natychmiast.

- Przejść przez każdy element w porządku: for name in names. O(N) — odwiedza każdy element raz.

Listy zachowują porządek, w którym umieszczasz elementy. Pierwszy element pozostaje pierwszy. Ostatni zostaje ostatni.

# A list of pets
pets = ["cat", "dog", "cat", "fish", "dog"]

print(pets[0])   # "cat"  — O(1) instant
print(pets[3])   # "fish" — O(1) instant
print(len(pets)) # 5 — duplicates kept

Zwróć uwagę: "cat" pojawia się dwa razy. "dog" pojawia się dwa razy. Listy pozwalają na duplikaty. Przechowują dokładnie to, co im dajesz, w porządku, w którym je dałeś.

Ale listy mają słabość. Zobacz, co się dzieje, gdy zapytasz: czy "fish" jest na tej liście?

# Membership check — is "fish" in the list?
if "fish" in pets:    # scans ["cat", "dog", "cat", "fish"...] one by one
    print("found!")   # had to check 4 items before finding it

Komputer sprawdza "cat" — nie. "dog" — nie. "cat" — nie. "fish" — tak! Przeskanował 4 elementy, aby znaleźć odpowiedź. Z 1000 elementami może przeskanować wszystkie 1000. To sprawdzenie przynależności kosztuje O(N).

Przewiduj Koszt Skanowania

Masz listę 500 nazwisk uczniów w losowym porządku.

Chcesz sprawdzić, czy "Zara" pojawia się na liście.

students = ["Alex", "Maria", ..., "Zara", ...]  # 500 names
if "Zara" in students:
    print("enrolled")
Jaka jest najlepsza, najgorsza & średnia liczba nazwisk, które komputer sprawdza, aby znaleźć "Zara"? Jaka klasa Big O opisuje tę operację? Wyjaśnij swoje rozumowanie.

Jak Zbiory Działają Inaczej

Zbiory: Zbudowane dla Pytań o Przynależność

Zbiór przechowuje unikalne elementy przy użyciu techniki zwanej haszowaniem. Gdy dodajesz element, komputer oblicza hasz (numerowy odcisk palca), który mówi mu dokładnie, gdzie przechowywać ten element.

Gdy sprawdzasz przynależność, komputer oblicza ten sam hasz & przeskakuje bezpośrednio do właściwego miejsca. Brak skanowania.

List vs Set: how data lives in memory — list scans, set jumps

# A set of pets
pets = {"cat", "dog", "cat", "fish", "dog"}

print(pets)      # {"cat", "dog", "fish"} — duplicates removed
print(len(pets)) # 3 — only unique items

Zwróć uwagę na trzy różnice od list:

1. Brak duplikatów. "cat" pojawił się dwa razy na wejściu, ale zbiór zachowuje tylko jedną kopię.

2. Brak gwarantowanego porządku. Zbiór może wydrukować elementy w dowolnym układzie.

3. Brak dostępu do indeksu. Nie możesz napisać pets[0] — zbiory nie mają pozycji.

Kompromis: tracisz porządek & duplikaty. Zyskujesz O(1) testowanie przynależności — sprawdzenie, czy element istnieje, zajmuje tyle samo czasu, czy zbiór ma 5 elementów, czy 5 milionów.

# Membership check — is "fish" in the set?
if "fish" in pets:    # hash("fish") → jump to bucket → found
    print("found!")   # checked exactly 1 spot, not 4

Porównanie Zbioru i Listy

Masz 500 nazwisk uczniów przechowywanych w zbiorze zamiast listy.

students = {"Alex", "Maria", ..., "Zara", ...}  # 500 names
if "Zara" in students:
    print("enrolled")
Ile elementów komputer sprawdza, aby określić, czy "Zara" istnieje w zbiorze 500 nazwisk? Jaka klasa Big O opisuje to? Dlaczego różni się od wersji listy?

Porównanie Obok Siebie

Arkusz Ściągania Operacji

Operation costs: list vs set — membership, add, index, dedup

Używaj listy, gdy potrzebujesz:

- Elementy w określonym porządku (lista odtwarzająca, przepis, kolejka)

- Dostęp po pozycji (items[3])

- Duplikaty zachowane ("cat" pojawia się 3 razy = 3 koty)

Używaj zbioru, gdy potrzebujesz:

- Szybkie sprawdzenia przynależności ("czy już to widziałem?")

- Automatyczne usuwanie duplikatów

- Brak trosk o porządek

Używaj obu, gdy potrzebujesz porządku I szybkiego wyszukiwania:

seen = set()     # O(1) membership checks
result = []      # preserves insertion order
for item in data:
    if item not in seen:  # O(1) check
        seen.add(item)    # O(1) add
        result.append(item)  # O(1) append
# Total: O(N) — one pass, each step O(1)

Ten wzorzec "zbiór + lista" daje Ci najlepsze z obu: uporządkowane wyniki z szybkim wykrywaniem duplikatów.

Wybierz Właściwą Strukturę

Trzy problemy. Dla każdego, zdecyduj: lista, zbiór, czy oba?

1. Musisz przechowywać playlistę piosenek w porządku, w którym użytkownik je dodał.

2. Musisz szybko sprawdzić, czy nazwa użytkownika została już zajęta.

3. Musisz usunąć duplikaty wiadomości e-mail z listy mailingowej, ale zachować je w porządku rejestracji.

Dla każdego z trzech problemów, którą strukturę danych powinieneś użyć (lista, zbiór, czy oba)? Wyjaśnij dlaczego dla każdego.

Problem Deduplikacji

Problem: Usuń Duplikaty, Zachowaj Porządek

Otrzymujesz listę rejestracji wiadomości e-mail. Niektóre osoby zarejestrowały się dwa razy. Musisz wysłać jedną wiadomość e-mail na osobę w porządku, w którym najpierw się zarejestrowali.

Próba 1: tylko lista (powolne)

signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
unique = []
for email in signups:
    if email not in unique:  # O(N) scan of unique list
        unique.append(email)
# Works! But: O(N) check × N items = O(N²)

Ze 100 rejestracjami to robi ~5000 sprawdzeń. Z 10 000 rejestracjami: ~50 000 000 sprawdzeń.

Próba 2: zbiór + lista (szybko)

signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
seen = set()
unique = []
for email in signups:
    if email not in seen:   # O(1) hash lookup
        seen.add(email)     # O(1) add to set
        unique.append(email) # O(1) append to list
# O(1) check × N items = O(N)

Ten sam wynik. Ten sam porządek. Ale: 10 000 rejestracji teraz wymaga ~10 000 sprawdzeń zamiast ~50 000 000.

Oblicz Przyspieszenie

Wersja tylko dla list działa przy O(N²). Wersja zbiór+lista działa przy O(N).

Masz 10 000 rejestracji wiadomości e-mail do deduplikacji.

Ile przybliżonych sprawdzeń wykonuje każda wersja przy N=10 000? Ile razy szybsza jest wersja zbiór+lista? Pokaż swoją kalkulację.

Znalezienie Wspólnych Elementów

Problem: Znajdź Elementy na Obu Listach

Masz dwie listy uczniów klas. Musisz znaleźć uczniów zapisanych na obie zajęcia.

Wolne podejście: zagnieżdżone pętle O(N × M)

class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b = ["Bob", "Diana", "Eve", "Frank"]
both = []
for student in class_a:        # N iterations
    if student in class_b:     # M scans each time
        both.append(student)
# O(N × M) — each student scanned against all of class_b

Szybkie podejście: konwertuj jedną listę na zbiór O(N + M)

class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b_set = set(class_b)     # O(M) to build
both = []
for student in class_a:        # N iterations
    if student in class_b_set: # O(1) each time
        both.append(student)
# O(N + M) — build the set once, check N times at O(1) each

Lub jeszcze prościej używając wbudowanego przecięcia zbiorów Pythona:

both = set(class_a) & set(class_b)  # O(N + M)

Operator & znajduje wszystkie elementy, które pojawiają się w obu zbiorach.

Przepisz na Szybciej

Szkoła ma dwa koła zainteresowań po 200 członków każde. Ten kod znajduje uczniów będących w obu kołach:

chess_club = [...]   # 200 names
art_club = [...]     # 200 names
both_clubs = []
for student in chess_club:
    if student in art_club:
        both_clubs.append(student)
Jaka jest klasa Big O tego kodu? Przepisz go, aby działał szybciej, używając zbioru. Jaka jest klasa Big O Twojej ulepszonej wersji? Wyjaśnij różnicę.

Twoja Rama Decyzyjna

Twoja Rama Decyzyjna do Struktury Danych

Za każdym razem, gdy piszesz kod, który przechowuje kolekcję elementów, zadaj:

1. Czy potrzebuję elementów w porządku? → lista

2. Czy potrzebuję szybkich sprawdzeń przynależności? → zbiór

3. Czy potrzebuję zarówno porządku, jak i szybkich sprawdzeń? → zbiór + lista razem

4. Czy potrzebuję duplikatów? → lista (zbiory usuwają duplikaty)

---

Kluczowy wgląd z tej lekcji: ten sam program, rozwiązujący ten sam problem, może działać 1 000× do 1 000 000× szybciej tylko poprzez wybór właściwej struktury danych. Bez sprytnych sztuczek. Bez skomplikowanej matematyki. Po prostu pytając: jakie operacje wykonuje mój kod najczęściej? Następnie wybierając strukturę, w której te operacje kosztują O(1) zamiast O(N).

Ta lekcja obejmowała listy & zbiory. Istnieją inne struktury danych (słowniki, drzewa, kopce), które rozwiązują inne problemy. Rama decyzyjna pozostaje taka sama: zrozumiej swoje operacje, potem wybierz strukturę, która je robi tanio.

---

Chcesz pójść głębiej? Nasza lekcja Big O obejmuje tempa wzrostu & obliczenia skalowania szczegółowo. Czytaj Big O: Jak Szybko Jest Wystarczająco Szybko?. Nasz kurs unhamming obejmuje rzeczywisty defekt (MOAD-0001), gdzie dokładnie ta naprawa listy-na-zbiór wyprodukowała przyspieszenie 1 000× w oprogramowaniu produkcyjnym. Czytaj Brakujący Rozdział: Złożoność Algorytmiczna.

Debuguj Ten Kod

Uczeń napisał ten kod do zliczania, ile unikalnych słów pojawia się w książce:

words = book.split()           # list of all words
unique_count = 0
checked = []
for word in words:             # N words
    if word not in checked:    # scans checked list
        checked.append(word)
        unique_count += 1
print(unique_count)

Książka ma 100 000 słów.

Jaka jest klasa Big O tego kodu? Dlaczego działa powoli na dużej książce? Przepisz go, aby rozwiązać ten sam problem w O(N). Potem wyjaśnij: czy możesz rozwiązać to w jednej linii za pomocą zbioru? Jak byłoby to wyglądać?