Wyszukiwanie i sortowanie — krok po kroku
W lekcji 24 poznałeś Array.Sort i Array.BinarySearch. Teraz zajrzymy do środka: jak te algorytmy działają, dlaczego jeden jest szybszy od drugiego i jak je napisać samemu. Na egzaminie zwykle chodzi właśnie o to — o pokazanie algorytmu, nie o wywołanie gotowej metody.
Czego się dziś nauczysz
- Napiszesz wyszukiwanie liniowe i binarne
- Wyjaśnisz, dlaczego binarne wymaga posortowanych danych
- Zaimplementujesz trzy klasyczne algorytmy sortowania
- Prześledzisz działanie algorytmu krok po kroku na kartce
- Oszacujesz, ile porównań wykona algorytm dla
nelementów
Wyszukiwanie liniowe
Najprostszy możliwy algorytm: sprawdzamy element po elemencie, aż znajdziemy szukaną wartość albo skończą się dane.
int[] dane = { 42, 17, 93, 8, 55, 23 };
int szukana = 55;
int znaleziono = -1;
int porownan = 0;
for (int i = 0; i < dane.Length; i++)
{
porownan++;
if (dane[i] == szukana)
{
znaleziono = i;
break; // dalej szukac nie ma po co
}
}
if (znaleziono >= 0)
{
Console.WriteLine($"Znaleziono na indeksie {znaleziono} po {porownan} porownaniach.");
}
else
{
Console.WriteLine($"Nie znaleziono. Sprawdzono {porownan} elementow.");
}
Znaleziono na indeksie 4 po 5 porownaniach.
| Przypadek | Liczba porównań | Kiedy |
|---|---|---|
| optymistyczny | 1 | szukana wartość jest pierwsza |
| średni | około n / 2 | wartość gdzieś w środku |
| pesymistyczny | n | wartość jest ostatnia albo nie ma jej wcale |
Zaleta: działa zawsze
Wyszukiwanie liniowe nie wymaga posortowania danych — tablica może być w dowolnej kolejności. To jego jedyna, ale bardzo istotna przewaga nad wyszukiwaniem binarnym. Dokładnie tak działa Array.IndexOf.
Wyszukiwanie binarne
Znasz ten pomysł z gry w zgadywanie liczby z lekcji 16: pytamy o środek, a odpowiedź „za mało” albo „za dużo” pozwala odrzucić połowę możliwości. Ten sam trick działa na posortowanej tablicy.
int[] dane = { 3, 8, 12, 17, 23, 31, 42, 56, 68, 74, 89, 95 }; // POSORTOWANE
int szukana = 56;
int lewy = 0;
int prawy = dane.Length - 1;
int znaleziono = -1;
int krok = 0;
while (lewy <= prawy)
{
krok++;
int srodek = (lewy + prawy) / 2;
Console.WriteLine($"krok {krok}: lewy={lewy} srodek={srodek} prawy={prawy} " +
$"-> dane[{srodek}]={dane[srodek]}");
if (dane[srodek] == szukana)
{
znaleziono = srodek;
break;
}
else if (dane[srodek] < szukana)
{
lewy = srodek + 1; // szukana jest w prawej polowie
}
else
{
prawy = srodek - 1; // szukana jest w lewej polowie
}
}
Console.WriteLine();
Console.WriteLine(znaleziono >= 0
? $"Znaleziono na indeksie {znaleziono} w {krok} krokach."
: $"Nie znaleziono ({krok} krokow).");
krok 1: lewy=0 srodek=5 prawy=11 -> dane[5]=31
krok 2: lewy=6 srodek=8 prawy=11 -> dane[8]=68
krok 3: lewy=6 srodek=6 prawy=7 -> dane[6]=42
krok 4: lewy=7 srodek=7 prawy=7 -> dane[7]=56
Znaleziono na indeksie 7 w 4 krokach.
krok 1: lewy=0 srodek=5 prawy=11 -> dane[5]=31
krok 2: lewy=0 srodek=2 prawy=4 -> dane[2]=12
krok 3: lewy=0 srodek=0 prawy=1 -> dane[0]=3
krok 4: lewy=1 srodek=1 prawy=1 -> dane[1]=8
Nie znaleziono (4 krokow).
Trzy rzeczy, na które trzeba uważać
| Element | Dlaczego akurat tak |
|---|---|
while (lewy <= prawy) |
Znak musi być <=. Przy < pominięty zostałby przypadek, gdy do sprawdzenia został jeden element — a wtedy lewy równa się prawy. Zobacz krok 4 w obu przebiegach powyżej. |
srodek + 1 i srodek - 1 |
Środek został właśnie sprawdzony i nie pasuje, więc wykluczamy go z dalszych poszukiwań. Zapis lewy = srodek; to pętla nieskończona — przy dwóch elementach zakres przestałby się zmniejszać. |
| dane muszą być posortowane | Cały pomysł opiera się na tym, że „w lewo są mniejsze”. Na nieposortowanej tablicy algorytm odrzuci połowę, w której szukana wartość akurat leżała — i nie znajdzie jej, choć tam jest. |
Dlaczego to jest tak szybko
Każdy krok zmniejsza zakres o połowę. Dla 12 elementów wystarczą 4 kroki, dla 1000 — 10, dla miliona — 20, dla miliarda — 30. Wyszukiwanie liniowe potrzebowałoby w tym ostatnim przypadku średnio pół miliarda porównań. To najlepsza ilustracja tego, że wybór algorytmu bywa ważniejszy niż szybkość komputera.
Kiedy binarne się nie opłaca
Posortowanie tablicy kosztuje więcej niż jedno przeszukanie liniowe. Jeśli szukasz raz, po prostu przejdź tablicę. Binarne wygrywa dopiero wtedy, gdy w tych samych danych szukasz wielokrotnie — wtedy koszt sortowania rozkłada się na wiele wyszukiwań.
Zamiana dwóch wartości
Każdy algorytm sortowania na tym stoi, więc zaczynamy od tej jednej operacji. Najpierw wykonamy ją z trzecią zmienną; język pozwala też na zapis (a, b) = (b, a):
int[] t = { 10, 20 };
// TAK NIE - obie komorki beda mialy 20
// t[0] = t[1];
// t[1] = t[0];
// poprawnie: przez zmienna pomocnicza
int pomocnicza = t[0];
t[0] = t[1];
t[1] = pomocnicza;
Console.WriteLine(string.Join(", ", t));
// krocej, od C# 7 - krotka
(t[0], t[1]) = (t[1], t[0]);
Console.WriteLine(string.Join(", ", t));
20, 10
10, 20
Dlaczego dwa proste przypisania nie wystarczą
Po pierwszym przypisaniu t[0] = t[1] stara wartość t[0] przestaje istnieć — została nadpisana. Drugie przypisanie skopiowałoby więc dwudziestkę samą na siebie. Trzecia zmienna to schowek na wartość, którą za chwilę stracimy. W kursie będziemy pisać wersję z pomocniczą, bo taka pojawia się w zadaniach egzaminacyjnych.
Sortowanie bąbelkowe
Pomysł: przechodzimy tablicę i porównujemy sąsiednie pary. Gdy stoją w złej kolejności, zamieniamy je miejscami. Po jednym przejściu największy element „wypływa” na sam koniec — jak bąbelek powietrza w wodzie. Powtarzamy, aż nic nie trzeba już zamieniać.
int[] t = { 5, 2, 9, 1, 7, 3 };
int n = t.Length;
for (int i = 0; i < n - 1; i++)
{
int zamian = 0;
for (int j = 0; j < n - 1 - i; j++) // uwaga na "- i"
{
if (t[j] > t[j + 1])
{
int pom = t[j];
t[j] = t[j + 1];
t[j + 1] = pom;
zamian++;
}
}
Console.WriteLine($"przebieg {i + 1}: {string.Join(", ", t)} (zamian: {zamian})");
if (zamian == 0)
{
break; // juz posortowane - nie ma po co isc dalej
}
}
przebieg 1: 2, 5, 1, 7, 3, 9 (zamian: 4)
przebieg 2: 2, 1, 5, 3, 7, 9 (zamian: 2)
przebieg 3: 1, 2, 3, 5, 7, 9 (zamian: 2)
przebieg 4: 1, 2, 3, 5, 7, 9 (zamian: 0)
| Fragment | Znaczenie |
|---|---|
j < n - 1 - i |
Najważniejszy szczegół. Po pierwszym przejściu ostatni element jest już na swoim miejscu, po drugim — dwa ostatnie. Odejmowanie i sprawia, że nie sprawdzamy ich ponownie. Bez tego algorytm działa poprawnie, ale robi więcej porównań; w pełnym przebiegu około dwa razy tyle. |
n - 1 w obu pętlach |
Porównujemy t[j] z t[j + 1], więc j nie może dojść do ostatniego indeksu — wtedy j + 1 wyszłoby poza tablicę. |
zamian == 0 → break |
Jeśli w całym przejściu nic nie trzeba było zamienić, tablica jest już posortowana. Dzięki temu dla danych prawie uporządkowanych algorytm kończy się bardzo szybko. To jedyna optymalizacja, która czyni sortowanie bąbelkowe znośnym. |
Prześledź przebieg pierwszy na kartce
Start: 5, 2, 9, 1, 7, 3. Porównujemy 5 z 2 → zamiana (2, 5, 9, 1, 7, 3). Potem 5 z 9 → zostaje. Potem 9 z 1 → zamiana (2, 5, 1, 9, 7, 3). Potem 9 z 7 → zamiana (2, 5, 1, 7, 9, 3). Na koniec 9 z 3 → zamiana (2, 5, 1, 7, 3, 9). Cztery zamiany, a dziewiątka dopłynęła na sam koniec.
Sortowanie przez wybór
Pomysł bliższy temu, jak człowiek porządkuje karty w ręku: znajdź najmniejszy element w nieposortowanej części i przenieś go na początek. Potem to samo dla reszty.
int[] t = { 5, 2, 9, 1, 7, 3 };
int n = t.Length;
for (int i = 0; i < n - 1; i++)
{
// szukamy INDEKSU najmniejszego w czesci od i do konca
int indeksMin = i;
for (int j = i + 1; j < n; j++)
{
if (t[j] < t[indeksMin])
{
indeksMin = j;
}
}
// zamiana - tylko jesli trzeba
if (indeksMin != i)
{
int pom = t[i];
t[i] = t[indeksMin];
t[indeksMin] = pom;
}
Console.WriteLine($"krok {i + 1}: {string.Join(", ", t)}");
}
krok 1: 1, 2, 9, 5, 7, 3
krok 2: 1, 2, 9, 5, 7, 3
krok 3: 1, 2, 3, 5, 7, 9
krok 4: 1, 2, 3, 5, 7, 9
krok 5: 1, 2, 3, 5, 7, 9
Zapamiętujemy indeks, nie wartość
Zmienna nazywa się indeksMin, bo przechowuje numer, a nie liczbę. Znając samą najmniejszą wartość, nie wiedzielibyśmy, skąd ją zabrać przy zamianie. To ta sama zasada, co przy szukaniu najcieplejszego dnia w lekcji 23.
Dlaczego niektóre kroki niczego nie zmieniają
W kroku 2 najmniejszym elementem reszty jest dwójka, która już stoi na swoim miejscu — indeksMin równa się i, więc warunek indeksMin != i nie wpuszcza nas do zamiany. Podobnie w krokach 4 i 5. Ten warunek nie jest konieczny (zamiana elementu z samym sobą jest nieszkodliwa), ale oszczędza trzy przypisania.
Zaleta: Mała liczba zamian
Sortowanie przez wybór wykonuje co najwyżej n − 1 zamian — po jednej na krok. Bąbelkowe potrafi ich zrobić setki. Ma to znaczenie, gdy przestawiane elementy są „ciężkie” (długie napisy, duże obiekty), ale w tablicy napisów lub obiektów klas zamieniane są referencje, a nie cała treść; koszt porównania i przenoszenia zależy od typu danych.
Sortowanie przez wstawianie
Tak układa się karty, biorąc je po jednej: bierzemy kolejny element i wsuwamy go we właściwe miejsce wśród już uporządkowanych, przesuwając większe o jedno w prawo.
int[] t = { 5, 2, 9, 1, 7, 3 };
int n = t.Length;
for (int i = 1; i < n; i++) // start od 1: element 0 to juz "posortowana czesc"
{
int klucz = t[i]; // element, ktory wstawiamy
int j = i - 1;
// przesuwamy w prawo wszystko, co jest wieksze od klucza
while (j >= 0 && t[j] > klucz)
{
t[j + 1] = t[j];
j--;
}
t[j + 1] = klucz; // tu jest jego miejsce
Console.WriteLine($"krok {i}: {string.Join(", ", t)}");
}
krok 1: 2, 5, 9, 1, 7, 3
krok 2: 2, 5, 9, 1, 7, 3
krok 3: 1, 2, 5, 9, 7, 3
krok 4: 1, 2, 5, 7, 9, 3
krok 5: 1, 2, 3, 5, 7, 9
| Fragment | Znaczenie |
|---|---|
int klucz = t[i]; |
Odkładamy element na bok, bo za chwilę jego miejsce zostanie nadpisane przesuwanymi wartościami. To ta sama rola, co zmienna pomocnicza przy zamianie. |
j >= 0 && t[j] > klucz |
Dwa warunki naraz: nie wyjdź poza początek tablicy i przesuwaj, dopóki napotykasz większe. Kolejność jest ważna — dzięki skróconemu obliczaniu z lekcji 10 przy j równym −1 drugi warunek nie jest w ogóle sprawdzany, więc nie ma odwołania do t[-1]. |
t[j + 1] = klucz; |
Po zakończeniu pętli j wskazuje element mniejszy lub równy kluczowi (albo −1), więc klucz trafia o jedno miejsce dalej. |
Najlepszy z tej trójki
Dla danych prawie posortowanych wstawianie jest bardzo szybkie — pętla while prawie się nie wykonuje. Dlatego właśnie tego algorytmu używa .NET wewnątrz Array.Sort dla małych fragmentów tablicy. Jest też stabilny: elementy o równych wartościach zachowują pierwotną kolejność.
Ile to kosztuje
Wszystkie trzy poznane algorytmy sortowania mają dwie pętle zagnieżdżone, a to znaczy, że liczba porównań rośnie z kwadratem liczby elementów. Dwa razy więcej danych — cztery razy dłużej.
| Elementów | Porównań (około n²/2) | Czas przy milionie porównań na sekundę |
|---|---|---|
| 10 | 50 | natychmiast |
| 100 | 5 000 | natychmiast |
| 1 000 | 500 000 | pół sekundy |
| 10 000 | 50 000 000 | około minuty |
| 100 000 | 5 000 000 000 | ponad godzinę |
Dlatego istnieją algorytmy lepsze
Array.Sort używa algorytmów, w których liczba porównań rośnie jak n · log n, a nie n². Dla stu tysięcy elementów to około 1,7 miliona porównań zamiast pięciu miliardów — czyli sekunda zamiast godziny. Jednym z takich algorytmów jest sortowanie przez scalanie, którego serce poznałeś już w zadaniu z lekcji 24.
| Algorytm | Porównań | Zamian | Uwagi |
|---|---|---|---|
| bąbelkowe | do n²/2 | dużo | najprostsze do napisania, najwolniejsze; z wykrywaniem braku zamian szybkie dla danych prawie uporządkowanych |
| przez wybór | zawsze n²/2 | do n − 1 | liczba porównań nie zależy od danych; najmniej zamian |
| przez wstawianie | od n do n²/2 | zależnie od danych | bardzo szybkie dla danych prawie posortowanych; stabilne |
Array.Sort | około n · log n | — | dobry domyślny wybór do sortowania tablic; nie gwarantuje stabilności |
Kiedy pisać sortowanie samemu
W typowym programie użytkowym zacznij od Array.Sort. Własną implementację piszemy, gdy chodzi o naukę, o zadanie egzaminacyjne albo o sortowanie według reguły, której gotowa metoda nie zna. Ostatni przypadek w praktyce rozwiązuje się jednak przez Array.Sort z własnym porównywaniem albo przez LINQ.
Użyte w tej lekcji elementy
| Nazwa | Zwraca | Działanie |
|---|---|---|
(a, b) = (b, a) | — | Krotka — zamiana dwóch wartości bez zmiennej pomocniczej. Prawa strona liczy się w całości, zanim cokolwiek zostanie przypisane. |
Random.Next(a, b) | int |
Liczba losowa z zakresu od a do b — bez b. Do generowania danych testowych. |
Stopwatch | — | Klasa z System.Diagnostics do mierzenia czasu: Stopwatch.StartNew(), potem .Stop() i .ElapsedMilliseconds. |
Array.Sort(t) | nic | Gotowe sortowanie z lekcji 24 punkt odniesienia dla własnych implementacji. |
Array.BinarySearch(t, x) | int |
Gotowe wyszukiwanie binarne. Wymaga posortowanej tablicy. |
t[..] | tablica | Kopia tablicy — konieczna, gdy chcesz porównać kilka algorytmów na tych samych danych. |
Math.Log2(x) | double |
Logarytm przy podstawie 2 — mówi, ile razy trzeba podzielić liczbę na pół. Opisuje tempo wzrostu liczby kroków, nie dokładny wynik każdego wyszukiwania. Dla n ≥ 1 maksymalnie potrzeba floor(log₂ n) + 1 sprawdzeń środka. |
Porównanie algorytmów na tych samych danych
Program losuje tablicę, kopiuje ją trzy razy i sortuje każdą kopię innym algorytmem, licząc porównania i zamiany. Dzięki temu porównanie jest uczciwe — każdy algorytm dostaje identyczne dane.
const int Ile = 20;
Random los = new Random(42); // stale ziarno - te same dane przy kazdym uruchomieniu
int[] oryginal = new int[Ile];
for (int i = 0; i < Ile; i++)
{
oryginal[i] = los.Next(1, 100);
}
Console.WriteLine($"Dane: {string.Join(", ", oryginal)}");
Console.WriteLine();
Console.WriteLine($"{"Algorytm",-24}{"Porownan",10}{"Zamian",10}");
Console.WriteLine(new string('-', 44));
// --- 1. babelkowe ---
int[] a = oryginal[..];
int por = 0, zam = 0;
for (int i = 0; i < a.Length - 1; i++)
{
bool bylaZamiana = false;
for (int j = 0; j < a.Length - 1 - i; j++)
{
por++;
if (a[j] > a[j + 1])
{
int pom = a[j];
a[j] = a[j + 1];
a[j + 1] = pom;
zam++;
bylaZamiana = true;
}
}
if (!bylaZamiana)
{
break;
}
}
Console.WriteLine($"{"babelkowe",-24}{por,10}{zam,10}");
// --- 2. przez wybor ---
int[] b = oryginal[..];
por = 0;
zam = 0;
for (int i = 0; i < b.Length - 1; i++)
{
int indeksMin = i;
for (int j = i + 1; j < b.Length; j++)
{
por++;
if (b[j] < b[indeksMin])
{
indeksMin = j;
}
}
if (indeksMin != i)
{
int pom = b[i];
b[i] = b[indeksMin];
b[indeksMin] = pom;
zam++;
}
}
Console.WriteLine($"{"przez wybor",-24}{por,10}{zam,10}");
// --- 3. przez wstawianie ---
int[] c = oryginal[..];
por = 0;
zam = 0;
for (int i = 1; i < c.Length; i++)
{
int klucz = c[i];
int j = i - 1;
while (j >= 0)
{
por++;
if (c[j] <= klucz)
{
break;
}
c[j + 1] = c[j];
zam++;
j--;
}
c[j + 1] = klucz;
}
Console.WriteLine($"{"przez wstawianie",-24}{por,10}{zam,10}");
Console.WriteLine(new string('-', 44));
// --- sprawdzenie, czy wszystkie daly ten sam wynik ---
Console.WriteLine();
Console.WriteLine($"Wynik: {string.Join(", ", a)}");
Console.WriteLine($"Zgodne: {a.SequenceEqual(b) && b.SequenceEqual(c)}");
Dlaczego new Random(42)
Liczba w nawiasie to ziarno generatora. Przy tym samym ziarnie ciąg liczb losowych jest zawsze identyczny — więc program przy każdym uruchomieniu da ten sam wynik i możesz go porównać z kolegą albo z poprzednim uruchomieniem. Bez ziarna (new Random()) dane byłyby za każdym razem inne, co przy porównywaniu algorytmów utrudnia analizę. Konkretny ciąg zależy od wersji .NET, więc twoje liczby mogą się różnić od tych w podręczniku — ważne, że w jednym uruchomieniu wszystkie trzy algorytmy dostają to samo.
Czego się z tego dowiadujemy
| Obserwacja | Wyjaśnienie |
|---|---|
| wybór zawsze robi tyle samo porównań | Dla n elementów dokładnie n(n−1)/2 — przy dwudziestu to 190, niezależnie od danych. Wewnętrzna pętla zawsze przechodzi całą resztę tablicy. |
| bąbelkowe robi najwięcej zamian | Zamienia tylko sąsiadów, więc element z końca tablicy musi „przepłynąć” przez wszystkie pozycje po jednej. |
| wybór robi najmniej zamian | Najwyżej jedną na krok, bo od razu przenosi element na docelowe miejsce. |
| wstawianie zależy od danych | Na danych losowych wypada podobnie do bąbelkowego. Na prawie posortowanych — wielokrotnie lepiej. Spróbuj uruchomić program na tablicy już posortowanej i zobacz różnicę. |
SequenceEqual na końcu |
Sprawdzenie, że wszystkie trzy algorytmy dały identyczny wynik. To najprostszy test poprawności własnej implementacji — jeśli wypisze False, gdzieś jest błąd. |
Eksperyment do samodzielnego wykonania
Uruchom program trzy razy: na danych losowych, na tablicy już posortowanej rosnąco i na posortowanej malejąco. Zapisz wyniki w tabelce. Zobaczysz, że dane posortowane malejąco to najgorszy możliwy przypadek dla wszystkich trzech algorytmów, a posortowane rosnąco — najlepszy dla bąbelkowego i wstawiania, ale bez znaczenia dla wyboru.
Na czym się najczęściej potykamy
| Błąd | Co się dzieje i jak poprawić |
|---|---|
| zamiana bez zmiennej pomocniczej | Obie komórki dostają tę samą wartość, druga przepada. Potrzebna trzecia zmienna albo krotka (a, b) = (b, a). |
j < n w bąbelkowym |
Odwołanie do t[j + 1] wyjdzie poza tablicę. Warunek to j < n - 1 - i. |
brak - i w bąbelkowym |
Algorytm nadal sortuje poprawnie, ale porównuje elementy, o których już wiadomo, że są na miejscu — więcej porównań; w pełnym przebiegu około dwa razy tyle. |
| zapamiętywanie wartości zamiast indeksu | W sortowaniu przez wybór trzeba wiedzieć, skąd zabrać najmniejszy element. Sama wartość tego nie mówi. |
while (lewy < prawy) w binarnym |
Pomija przypadek jednego elementu do sprawdzenia. Musi być <=. |
lewy = srodek; zamiast srodek + 1 |
Zakres przestaje się zmniejszać — pętla nieskończona. |
| binarne na nieposortowanej tablicy | Wynik jest bezsensowny, a program nie zgłasza błędu. Zawsze sortuj wcześniej. |
t[j] > klucz przed sprawdzeniem j >= 0 |
Odwołanie do t[-1] i przerwanie programu. Warunek zakresu musi być pierwszy — skrócone obliczanie zrobi resztę. |
| porównywanie algorytmów na różnych danych | Porównanie wpływu algorytmu jest wtedy zaburzone. Każdy algorytm musi dostać kopię tych samych danych. |
| sortowanie 100 000 elementów bąbelkowym | Program może długo liczyć; rzeczywisty czas zależy od implementacji, danych i sprzętu. Przed uruchomieniem oszacuj n². |
Ile to naprawdę kosztuje
Umiesz już sortować i wyszukiwać. W lekcji 28 nauczysz się porównywać te algorytmy liczbą operacji i poznasz dwa szybsze: sortowanie przez scalanie i sortowanie szybkie.
Zadania
Przerób sortowanie bąbelkowe tak, żeby porządkowało tablicę malejąco. Zmienić trzeba dokładnie jeden znak.
Policz, ile porównań wykonuje wyszukiwanie liniowe, a ile binarne, szukając tej samej wartości w posortowanej tablicy stu elementów.
Posortuj tablicę imion alfabetycznie własnym algorytmem (nie używając Array.Sort). Do porównania napisów użyj metody CompareTo.
Policz medianę tablicy liczb: wartość środkową po posortowaniu, a przy parzystej liczbie elementów — średnią dwóch środkowych. Oryginalna kolejność ma zostać nienaruszona.
Posortuj uczniów według punktów malejąco — własnym algorytmem, przestawiając jednocześnie obie tablice. To ręczna wersja metody Array.Sort(klucze, wartości) — napisz ją samodzielnie, bez korzystania z gotowej.
Napisz wyszukiwanie binarne, które przy braku szukanej wartości podaje, między którymi elementami powinna się ona znaleźć. Wypisz też każdy krok wraz z rozmiarem pozostałego zakresu.
Co trzeba zapamiętać
- Wyszukiwanie liniowe nie wymaga posortowania i w najgorszym przypadku sprawdza n elementów.
- Wyszukiwanie binarne wymaga zgodnego porządku danych; dla n >= 1 potrzebuje najwyżej floor(log2(n)) + 1 sprawdzeń środka.
- Liczba iteracji to nie zawsze liczba wszystkich wykonanych operatorów porównania.
- Zamianę wartości można wykonać zmienną pomocniczą albo przypisaniem krotki.
- Bąbelkowe, wybór i wstawianie mają kwadratowy najgorszy przypadek. Sama liczba pętli tego nie dowodzi.
- Sortowanie przez wybór z zamianą nie jest stabilne. Wstawianie z przesuwaniem tylko wartości większych zachowuje stabilność.
- Array.Sort jest dobrym domyślnym narzędziem, ale nie gwarantuje stabilności.
- Czas wykonania mierz dla tych samych danych. Liczniki operacji i oszacowania czasu nie zastępują pomiaru.