|
Serwis Edukacyjny w I-LO w Tarnowie
Materiały dla uczniów liceum |
Wyjście Spis treści Wstecz Dalej
Autor artykułu: mgr Jerzy Wałaszek |
©2026 mgr Jerzy Wałaszek
|
| Wejście: | ||
| n | – | liczba elementów tablicy |
| T | – | tablica n-elementowa do przeszukania |
| p | – | indeks elementu, od którego rozpocznie się przeszukiwanie |
| w | – | poszukiwana wartość |
| Wyjście: |
| Pozycja elementu o wartości w lub -1, jeśli element nie występuje w tablicy. |
W algorytmie powyższym sprawdzana jest równość T[i] = w. W rzeczywistości może to być dowolne kryterium (np. T[i] > w; T[i] < T[0]...). Wtedy otrzymamy ogólny algorytm wyszukiwania liniowego:
Poniższy program generuje tablicę 500-elementową i wypełnia ją liczbami pseudolosowymi o wartościach od 10 do 99. Następnie wyszukuje w niej wszystkie komórki zawierające liczbę 50 i zamienia ją na 1. Na koniec zawartość tablicy zostaje wyświetlona z zaznaczonymi komórkami o zawartości równej 1 – tutaj też stosowany jest podobny algorytm do wyszukiwania liniowego.
Przeanalizuj dokładnie program, w szczególności fragment wykorzystujący wynik wyszukiwania.
Na podstawie powyższego programu napisz program, który:
Przeanalizujmy algorytm wyszukiwania liniowego. W tym celu rozbijmy go na kroki równoważne:
Policzmy czas wykonania. Oznaczmy czas wykonania kroku przez tnumer kroku. Załóżmy, iż w przeszukiwanym zbiorze żaden element nie spełnia kryterium wyszukiwawczego, to jest przypadek pesymistyczny. Wtedy pętla wykona się n-razy.
| Krok | Czas | Ilość wykonań |
| K1 | t1 | 1 |
| K2 | t2 | n + 1 |
| K3 | t3 | 1 |
| K4 | t4 | n |
| K5 | t5 | n |
| K6 | t6 | n |
Czas wykonania będzie równy w przypadku pesymistycznym:
Przy dużych n możemy przyjąć, iż:
n + 1 ≈ n
Również czasy t1 i t3 możemy pominąć, gdyż są to czasy jednostkowe i przy dużych n nie wpływają istotnie na czas wykonania całości algorytmu. Otrzymamy zatem przybliżony wzór na czas wykonania algorytmu w przypadku pesymistycznym:
Dalsze uproszczenie będzie polegało na zastąpieniu czasów t2, t4, t5 i t6 czasem wykonania pętli tp:
tp = t2 + t4 + t5 + t6 t ≈ n · tp
Zatem czas wykonania algorytmu jest proporcjonalny do n pomnożonego przez czas wykonania pętli. Jeśli uda nam się przyspieszyć wykonanie pętli, to algorytm ulegnie przyspieszeniu. Jak można przyspieszyć wykonanie pętli? Zwróć uwagę, iż pętla zawiera dwie instrukcje warunkowe, oznaczone tu kolorem czerwonym:
Instrukcja w kroku K2 jest wykonywana w celu sprawdzenia, czy indeks i nie wykroczył poza indeks ostatniego elementu tablicy. Jeśli poszukiwany element jest w przeszukiwanym zakresie, to algorytm dojdzie do jego indeksu i krok K4 zakończy wykonywanie algorytmu. Instrukcja warunkowa w kroku K2 nie będzie wykonana, jest zatem zbędna. Ale skąd mamy wiedzieć, czy poszukiwany element jest w zakresie poszukiwań? Zrobimy coś, co się może wydać szalone: sami wstawiamy poszukiwany element do tablicy, ale na jej końcu, poza jej elementami, czyli w komórce T[n] - wynika stąd, iż tablica musi posiadać o jedną komórkę więcej. Co nam to daje? Otóż dużo - przede wszystkim, jeśli pominiemy krok K2, to w przypadku braku poszukiwanego elementu w zakresie od T[0] do T[n - 1], algorytm znajdzie element dodany w komórce T[n]. Teraz wystarczy sprawdzić, czy indeks znalezionego elementu jest mniejszy od n. Jeśli tak, to jest to element rzeczywisty. Jeśli nie, to jest to element dodany, a zatem w zakresie podstawowym nie było poszukiwanego elementu. Dodany element nazywamy wartownikiem (ang. sentinel). Wartownik pilnuje, aby algorytm nie wyszedł poza zakres elementów tablicy. Dzięki niemu krok K2 staje się zbędny i pętla wykonywana jest szybciej. Zmodyfikowany algorytm wygląda następująco:
| Wejście: | ||
| n | – | liczba elementów tablicy |
| T | – | tablica (n + 1)-elementowa do przeszukania |
| p | – | indeks elementu, od którego rozpocznie się przeszukiwanie |
| w | – | poszukiwana wartość |
| Wyjście: |
| Pozycja elementu o wartości w lub -1, jeśli element nie występuje w tablicy. |
Zwróć uwagę, iż algorytm jest dłuższy, jednak pętla K3, K4 i K5 zawiera tylko jedną instrukcję warunkową, a nie dwie jak poprzednio. Pozostałe instrukcje wykonują się tylko jeden raz, zatem nie wpływają istotnie na czas wykonania algorytmu.
Algorytm wyszukiwania liniowego w przypadku pesymistycznym ma klasę złożoności obliczeniowej O(n), czyli liniową. Wyszukiwanie z wartownikiem jest szybsze od zwykłego wyszukiwania, chociaż algorytm jest nieco bardziej skomplikowany.
W poprzednim programie zamień funkcję lsearch( ) na:
Rozmiar tablicy ustaw na T[501].
| Wejście: | ||
| n | – | liczba elementów tablicy |
| T | – | tablica n-elementowa do przeszukania |
| Wyjście: | ||
| w | – | wartość elementu największego (najmniejszego) w tablicy. |
| p | – | pozycja elementu największego (najmniejszego) w tablicy |
Poniższy program tworzy tablicę 100 elementową i wypełnia ją liczbami pseudolosowymi od 10 do 99. Następnie wyszukuje w niej element największy.
Przerób program tak, aby wyszukiwał element minimalny.
| Wejście: | ||
| n | – | liczba elementów tablicy |
| T | – | tablica n-elementowa do przeszukania |
| Wyjście: | ||
| wmin | – | wartość elementu najmniejszego w tablicy. |
| pmin | – | pozycja elementu najmniejszego w tablicy. |
| wmax | – | wartość elementu największego w tablicy. |
| pmax | – | pozycja elementu największego w tablicy. |
W przykładowym programie tworzona jest tablica 15 elementowa, która zostaje wypełniona liczbami pseudolosowymi rzeczywistymi od -2 do 2. Następnie program wyszukuje wartość MIN i MAX.
Przeanalizujmy teraz zaprezentowany wyżej algorytm. W pętli przeglądającej tablicę są dwie operacje porównań:
K01: |
wmin ← T[0] |
K02: |
pmin ← 0 |
K03: |
wmax ← T[0] |
K04: |
pmax ← 0 |
K05: |
Dla i = 1,2,...,n-1: wykonuj K06...K7 |
K06: |
Jeśli T[i] > wmax, to
wmax ← T[i]
pmax ← i
|
K07: |
Jeśli T[i] < wmin, to
wmin ← T[i]
pmin ← i
|
K05: |
Zakończ |
Pętla przegląda tablicę od elementu o indeksie 1 do ostatniego, zatem jeśli tablica ma n-elementów, to pętla wykona się n - 1 razy. W każdym obiegu mamy 2 operacje porównań, zatem liczba wszystkich porównań dla całej tablicy wyniesie:
Jeśli uda się nam zmniejszyć liczbę porównań, to przyspieszymy algorytm.
Wyobraźmy sobie, iż liczba elementów tablicy jest parzysta – jeśli nie jest parzysta, to na końcu dublujemy ostatni element. Podzielmy teraz wszystkie elementy na pary: pierwszy z drugim, trzeci z czwartym, piąty z szóstym, itd. Par będzie dwa razy mniej niż tworzących je elementów, czyli n / 2. Bierzemy parę i porównujemy ze sobą zawarte w niej elementy:
(a,b): jeśli a > b, to a nie może być elementem najmniejszym, zatem a porównujemy dalej z zapamiętanym wcześniej elementem MAX, a b z zapamiętanym wcześniej elementem MIN.
(a,b): jeśli nie zachodzi a > b, to a nie może być elementem największym, zatem a porównujemy dalej z zapamiętanym wcześniej elementem MIN, a b z zapamiętanym wcześniej elementem MAX.
Co nam to dało? Porównań elementów w parach jest tyle, ile jest par, zatem n / 2. Porównanie takie rozdziela parę na element a i element b. Każdy z tych elementów jest następnie porównywany z MIN lub z MAX. Porównań tych jest po n / 2. Zatem sumarycznie mamy porównań:
Zmniejszyliśmy ilość porównań mniej więcej o 1/4, a zatem tak skonstruowany algorytm będzie szybszy. Zapiszmy go:
| Wejście: | ||
| n | – | liczba elementów tablicy |
| T | – | tablica n-elementowa do przeszukania |
| Wyjście: | ||
| wmin | – | wartość elementu najmniejszego w tablicy. |
| pmin | – | pozycja elementu najmniejszego w tablicy. |
| wmax | – | wartość elementu największego w tablicy. |
| pmax | – | pozycja elementu największego w tablicy. |
Poniżej mamy poprzedni program ze zmodyfikowanym algorytmem wyszukiwania MIN i MAX:
Podany algorytm wykorzystuje zasadę dziel i zwyciężaj (ang. divide and conquer). Polega ona na podzieleniu problemu na problemy mniejsze, rozwiązaniu problemów mniejszych i z tych rozwiązań stworzenia rozwiązania problemu głównego.
Naszym zadaniem jest znalezienie w zbiorze wartości, która powtarza się najczęściej. Na przykład mamy zbiór:
Wartość 5 powtarza się 3 razy i jest wartością najczęstszą.
Problem można rozwiązać na wiele sposobów. Zacznijmy od najprostszego.
Klasa złożoności obliczeniowej tak zdefiniowanego algorytmu jest równa O(n2). Jeśli elementów jest niewiele (do około 5000) i szybkość działania nie gra istotnej roli, to rozwiązanie bezpośrednie jest do przyjęcia – tak właśnie było w przypadku zadania maturalnego.
| Wejście: | ||
| n | – | liczba elementów tablicy |
| T | – | tablica n-elementowa do przeszukania |
| Wyjście: | ||
| maxw | – | wartość najczęstszego elementu w tablicy. |
| maxc | – | liczba powtórzeń najczęstszej wartości |
Poniższy program tworzy tablicę 100 elementową i wypełnia ją liczbami pseudolosowymi od 1 do 25. Następnie wyszukuje element, który powtarza się najczęściej. Przeanalizuj dokładnie program.
Pierwszy algorytm jest prosty, lecz bardzo prymitywny i wykonuje wiele niepotrzebnych operacji. Co możemy poprawić?
Po tych modyfikacjach otrzymujemy:
| Wejście: | ||
| n | – | liczba elementów tablicy |
| T | – | tablica n-elementowa do przeszukania |
| Wyjście: | ||
| maxw | – | wartość najczęstszego elementu w tablicy. |
| maxc | – | liczba powtórzeń najczęstszej wartości |
Uaktualniony program
Jeśli elementy zbioru tworzą spójny przedział wartości i przedział ten nie jest specjalnie duży, to do wyszukania wartości najczęstszej można zastosować następującą metodę.
Tworzymy tablicę L[ ], której elementy będą pełniły rolę liczników wystąpień wartości elementów z tablicy T[ ]. Zakres indeksów w L[ ] możemy znaleźć wyszukując w tablicy T[ ] wartość minimalną i maksymalną (można też z góry przydzielić odpowiednią liczbę liczników, gdy znamy zakres wartości elementów w przeszukiwanej tablicy T[ ]). Zerujemy wszystkie liczniki w L[ ] i przeglądamy tablicę T[ ] zwiększając o 1 liczniki w L[ ] , które odpowiadają wartościom przeglądanych elementów. W efekcie po zakończeniu przeglądania tablicy T[ ] w L[ ] będziemy mieli informację o liczbie wystąpień każdej z wartości. Wyznaczamy w L[ ] element maksymalny. Wynikiem jest indeks, który określa wartość występującą najczęściej w T[ ] oraz zawartość elementu L[ ] o tym indeksie, która określa ile razy dana wartość wystąpiła w T[ ].
Tak określony algorytm wyszukiwania najczęstszego elementu posiada liniową klasę złożoności obliczeniowej O(m + n), gdzie m jest liczbą wartości elementów przeszukiwanego zbioru, a n jest liczbą jego elementów.
W języku C++ indeksy tablicy rozpoczynają się od wartości 0. Jeśli indeksy mają odpowiadać wartościom elementów tablicy, to wartości te należy odpowiednio przeliczyć. Na przykład mamy przedział wartości <-2, 3>. W przedziale tym są liczby:
-2 -1 0 1 2 3
Jeśli mamy zliczać wystąpienia tych wartości, to musimy przygotować tablicę liczników tak, aby ich indeksy odpowiadały zliczanym wartościom:
Jeśli mamy przedział całkowity <a,b>, to w przedziale tym jest b - a + 1 różnych wartości. Wyrażenie to pozwoli wyliczyć rozmiar tablicy liczników, jeśli znamy zakres wartości. Zakres a...b poznamy wyznaczając MIN i MAX liczb w przeszukiwanej tablicy. Wzory przeliczeniowe pomiędzy indeksami liczników, a zliczanymi przez nie wartościami są następujące:
indeks = wartość - MIN wartość = indeks + MIN
Zatem pełny algorytm wygląda następująco:
| Wejście: | ||
| n | – | liczba elementów tablicy |
| T | – | tablica n-elementowa do przeszukania |
| Wyjście: | ||
| maxw | – | wartość najczęstszego elementu w tablicy. |
| maxc | – | liczba powtórzeń najczęstszej wartości |
Zmodyfikowany program:
Taki algorytm jest efektywny tylko wtedy, gdy zbiór danych posiada ograniczoną liczbę wartości, lecz sam jest duży. Na maturze raczej takich problemów nie zadaje się uczniom z uwagi na ograniczony czas trwania egzaminu. Dlatego algorytm nr 2 powinien być wystarczający.
Zbiór uporządkowany możemy utworzyć w prosty sposób przy pomocy liczb pseudolosowych:
Poniższy program tworzy cztery zbiory o różnych porządkach.
Wyszukiwanie binarne (ang. binary search) odnosi się do zbiorów uporządkowanych, ponieważ korzysta ono z porządku w zbiorze. Postępujemy następująco:
Załóżmy, że zbiór jest uporządkowany rosnąco lub niemalejąco (przy zbiorze uporządkowanym malejąco lub nierosnąco postępujemy podobnie, lecz uwzględniamy fakt odwrotnego uporządkowania).
Wyznaczymy element leżący w środku zbioru. Sprawdzimy, czy jest on poszukiwanym elementem. Jeśli tak, to element został znaleziony i możemy zakończyć poszukiwania. Jeśli nie, to poszukiwany element jest albo mniejszy od elementu środkowego, albo większy. Ponieważ zbiór jest uporządkowany, to elementy mniejsze od środkowego będą leżały w pierwszej połówce zbioru, a elementy większe w drugiej połówce (przy uporządkowaniu odwrotnym jest na odwrót). Zatem w następnym obiegu zbiór możemy zredukować do pierwszej lub drugiej połówki – jest w nich o połowę mniej elementów. Mając nowy zbiór postępujemy w sposób identyczny – znów wyznaczamy element środkowy, sprawdzamy, czy jest poszukiwanym elementem. Jeśli nie, to zbiór dzielimy znów na dwie połowy – elementy mniejsze od środkowego i elementy większe od środkowego. Poszukiwania kontynuujemy w odpowiedniej połówce zbioru aż znajdziemy poszukiwany element lub do chwili, gdy po podziale połówka zbioru nie zawiera dalszych elementów.
Ponieważ w każdym obiegu pętli algorytm redukuje liczbę elementów o połowę, to jego klasa złożoności obliczeniowej jest równa O(log n). Jest to bardzo korzystna złożoność. Na przykład w algorytmie wyszukiwania liniowego przy 1000000 elementów należało wykonać aż 1000000 porównań, aby stwierdzić, iż elementu poszukiwanego nie ma w zbiorze. W naszym algorytmie wystarczy tylko 20 porównań.
Algorytm wyszukiwania liniowego może być zastosowany dla dowolnego zbioru. Algorytm wyszukiwania binarnego można stosować tylko i wyłącznie w zbiorze uporządkowanym.
Musimy uściślić sposób podziału zbioru na coraz mniejsze fragmenty. Zbiór odwzorowujemy w tablicy T[ ] składającej się z n elementów ponumerowanych kolejno od 0 do n - 1. Określmy dwa indeksy:
ip – indeks pierwszego elementu
zbioru
ik – indeks końcowego elementu zbioru
| indeksy elementów tablicy T[ ] | 0 | 1 | 2 | ... | n-2 | n-1 | |
| indeksy początku i końca | ip | ik |
Indeksy ip i ik określają obszar zbioru w tablicy T[ ]. Początkowo będą oczywiście równe: ip = 0 oraz ik = n - 1. Na podstawie tych dwóch indeksów obliczamy indeks elementu środkowego isr:

Jeśli zbiór jest uporządkowany rosnąco, to:
| T[0] | T[1] | ... | T[isr-1] | T[isr] | T[isr+1] | ... | T[n-2] | T[n-1] |
| ip | ← | isr | → | ik | ||||
| elementy < T[isr] | elementy > T[isr] | |||||||
Zatem w przypadku, gdy poszukiwana wartość w < T[isr], to przechodzimy do pierwszej połówki, w której indeksy przebiegają od ip do isr-1. Element T[isr] pomijamy, gdyż wiadomo, że o niego nam nie chodzi.
Jeśli w > T[isr], to przechodzimy do drugiej połówki o indeksach od isr + 1 do ik.
Jeśli w wyniku redukcji indeks ip stanie się większy od ik, to wyszukiwanie kończymy, ponieważ taka połówka nie zawiera żadnego elementu, zatem poszukiwanego elementu w zbiorze nie ma.
W przypadku porządku słabego tablica może posiadać wielkości równe w kilku kolejnych komórkach. W takim przypadku algorytm wyszukiwania binarnego wskaże jedną z nich.
| n | – | liczba komórek w tablicy |
| T | – | tablica do wyszukania elementu. |
| w | – | wartość poszukiwanego elementu |
p = indeks elementu o wartości w lub
p = -1, jeśli nie odnaleziono w tablicy elementu o takiej wartości.
Poniższy program tworzy tablicę 208 elementową i umieszcza w niej wartości uporządkowane w kolejności rosnącej. Następnie wybiera jedną z tych wartości i dodaje liczbę pseudolosową 0 lub 1, po czym tak zmodyfikowaną wartość wyszukuje w tablicy.
Lider (ang. leader) jest wartością występującą w ponad połowie elementów zbioru. Na przykład w zbiorze:
{ 1 4 7 4 3 4 4 8 4 4 }
liderem jest wartość 4, ponieważ pojawia się w zbiorze 6 razy, a zbiór liczy 10 elementów, czyli 4 występuje w więcej niż w połowie elementów.
Lidera można znaleźć przedstawionym wcześniej algorytmem wyszukiwania najczęstszej wartości (jak to zrobić?). Jednakże istnieje prostszy i szybszy sposób. Wykorzystujemy tu twierdzenie:
Jeśli tablica T[ ] posiada lidera, to usunięcie z niego pary elementów różnych daje zbiór z tym samym liderem.
Dowód tego twierdzenia jest bardzo prosty. Oznaczmy przez nL liczbę elementów będących liderami. Tablica T[ ] posiada n elementów, zatem liczba pozostałych elementów wynosi n - nL. Zachodzi nierówność:
nL > n - nL
Przypadek 1
Z tablicy T[ ] usuwamy dwa elementy, które nie są liderami. W tym przypadku nL nie zmniejsza się, lecz zmniejsza się o 2 liczba elementów n. Otrzymamy zatem:
nL > (n - 2) - nL nL > (n - nL) - 2
Jeśli pierwsza nierówność była prawdziwa (a była z założenia, iż nL jest liczebnością liderów ), to tym bardziej będzie prawdziwa druga nierówność. Wynika z tego, iż usunięcie dwóch elementów nie będących liderami nie wpływa na występowanie lidera.
Przypadek 2
Z tablicy T[ ] usuwamy jeden element lidera oraz jeden element nie będący liderem. Zmniejszeniu o 1 ulega zatem liczba liderów, a liczba elementów maleje o 2. Otrzymujemy:
Otrzymaliśmy nierówność wyjściową, w której od obu stron odjęto tę samą liczbę -1. Zatem nierówność jest wciąż prawdziwa, z czego wynika, iż usunięcie z tablicy T[ ] jednego lidera i jeden element nie będący liderem nie wpływa na występowanie lidera.
Innych przypadków nie ma, zatem dowiedliśmy prawdziwości twierdzenia.
Z powyższego twierdzenia bezpośrednio wynikają dwie dalsze własności:
| Jeśli zbiór posiada lidera, to
usunięcie z niego wszystkich par elementów różnych daje zbiór
zawierający tylko elementy będące liderem. Jeśli w wyniku takiej operacji otrzymujemy jednak zbiór pusty, to lidera w zbiorze wejściowym nie było. |
Zamiast faktycznie wyrzucać z tablicy elementy różne – co jest dosyć trudne, możemy wykonać operację odwrotną. Będziemy zliczali elementy o równych wartościach – wystarczy wtedy zapamiętać wartość elementu oraz ilość jej wystąpień. Algorytm pracuje w sposób następujący:
Licznik elementów równych L stawiamy na zero. Rozpoczynamy przeglądanie elementów zbioru od pierwszego do ostatniego. Jeśli licznik elementów równych L jest równy 0, to kolejny element zbioru zapamiętujemy, zwiększamy licznik L o 1 i wykonujemy kolejny obieg pętli dla następnego elementu. Jeśli licznik L nie jest równy zero, to sprawdzamy, czy bieżący element jest równy zapamiętanemu. Jeśli tak, to mamy dwa elementy równe – zwiększamy licznik L o 1. W przeciwnym razie licznik L zmniejszamy o 1 – odpowiada to wyrzuceniu z tablicy dwóch elementów różnych. W obu przypadkach wykonujemy kolejny obieg pętli.
Jeśli tablica posiadała lidera, to liczba elementów równych jest większa od liczby elementów różnych. W takim przypadku zapamiętana wartość jest liderem. Aby to potwierdzić, zliczamy liczbę wystąpień tej wartości w tablicy. Jeśli jest ona większa od liczby połowy elementów, to mamy lidera.
Algorytm posiada liniową klasę złożoności obliczeniowej O(n), jest zatem bardziej efektywny od opisanych w wcześniej algorytmów wyszukiwania najczęstszej wartości.
| n | – | liczba elementów w tablicy T[ ] |
| T[ ] | – | tablica do wyszukania najczęstszego elementu. Elementy są liczbami całkowitymi. Indeksy od 0 do n - 1. |
w = element będący liderem, L = liczba jego wystąpień w T[ ] lub informacja o braku lidera.
Poniższy program tworzy tablicę o 200 elementach i wypełnia ją liczbami pseudolosowymi od 0 do 99 w taki sposób, aby zaistniała szansa pojawienia się lidera. Następnie lider jest wyszukiwany naszym algorytmem.
![]() |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2026 mgr Jerzy Wałaszek |
Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone pod warunkiem podania źródła oraz niepobierania za to pieniędzy.
Pytania proszę przesyłać na adres email:
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.
Informacje dodatkowe.