Algorytmy sortujące i wyszukujące Zadaniem algorytmów sortujących jest ułożenie elementów danego zbioru w ściśle określonej kolejności. Najczęściej wykorzystywany jest porządek numeryczny lub leksykograficzny. Posortowane elementy wykorzystywane są w bardziej zaawansowanych algorytmach na przykład wyszukujących. Przedstawione poniżej sposoby sortowania układają elementy w porządku rosnącym. Sortowanie przez wybieranie Sortowanie przez wybieranie polega na wybraniu z wektora elementu najmniejszego i zamianie go z elementem bieżącym. Załóżmy, że chcemy posortować n-elementowy wektor dane[] o indeksach od 0 do n-1. for (int i = 0; i < n-1; i++ ) { - przypisz zmiennej j indeks najmniejszego elementu spośród dane[i]...dane[n-1] - dokonaj zamiany elementów dane[i] z dane[j] Przykład, chcemy posortować następujący wektor dane[]. 4 2 5 3 1 Poniżej wszystkie kroki, które zostaną wykonane przy sortowaniu. Zmienna i to kolejny numer kroku, a j indeks najmniejszego elementu z zakresu od i do n-1 (n w naszym przykładzie wynosi 5). Sortowanie przez wstawianie i wektor wejściowy j wektor wyjściowy 0 4 2 5 3 1 4 1 2 5 3 4 1 1 2 5 3 4 1 1 2 5 3 4 2 1 2 5 3 4 3 1 2 3 5 4 3 1 2 3 5 4 4 W sortowaniu przez wstawianie wektor zostaje podzielony na dwie części, część posortowaną oraz część nieposortowaną. Istotą algorytmu jest pobieranie kolejnych elementów z części nieposortowanej i wstawianie ich na odpowiednie miejsce w części posortowanej (tak samo jak układanie kart w dłoni). Podział wektora ustalamy zakładając, że w części posortowanej znajduje się jego pierszy element, a cała reszta to część nieposortowana. Załóżmy, że chcemy posortować n-elementowy wektor dane[] o indeksach od 0 do n-1. 13
for (int i = 1; i < n; i++) { - pobierz pierwszy element z części nieposortowanej, e = dane[i] - wstaw ten element w odpowiedne miejsce w części posortowanej dane[0]...dane[i] Przykład, chcemy posortować następujący wektor dane[]. 4 2 5 3 1 Poniżej wszystkie kroki, które zostaną wykonane przy sortowaniu. Zmienna i to kolejny numer kroku, a e wartość pierwszego elementu z części nieposortowanej. Część posortowana zaznaczona jest kolorem niebieskim. i wektor wejściowy e = dane[i] wektor wyjściowy 1 4 2 5 3 1 2 2 4 5 3 1 2 2 4 5 3 1 5 2 4 5 3 1 3 2 4 5 3 1 3 2 3 4 5 1 4 2 3 4 5 1 1 Stosując algorytm sortowania przez wstawianie możemy sortować na bieżąco dane wejściowe (np. podczas pobierania ich od użytkownika z konsoli czy też odczytu z pliku). Sortowanie bąbelkowe Ten sposób sortowania polega na porównywaniu ze sobą par sąsiadujących elementów i zamianie ich kolejności jeśli nie spełniają warunku porządkowania. Załóżmy, że chcemy posortować n-elementowy wektor dane[] o indeksach od 0 do n-1. for (int i = 1; i < n; i++ ) { - przeglądaj kolejno pary elementów: (dane[n-1], dane[n-2]) (dane[n-2], dane[n-3])... (dane[i-1], dane[i]) - jeżeli elementy tworzące parę nie są prawidłowo uporządkowane, zamień je Przykład, chcemy posortować następujący wektor dane[]. 4 2 5 3 1 14
Poniżej poszczególne kroki, które zostaną wykonane przy sortowaniu. Zmienna i to kolejny numer kroku. Wektor brany pod uwagę jest uzależniony od wartości i od dane[n-1] do dane[i-1]. Elementy porównywane zaznaczone są kolorem niebieskim. i wektor wejściowy porównuję uzyskuję wektor wyjściowy 1 4 2 5 3 1 4 2 5 3 1 4 2 5 1 3 4 2 1 5 3 4 1 2 5 3 2 1 4 2 5 3 1 4 2 5 3 3 4 2 5 1 3 4 2 1 5 3 4 1 2 5 3 1 4 2 5 3 1 4 2 5 3 4 Sortowanie bąbelkowe jest mało wydajne dla większych zbiorów. Sortowanie szybkie Idea sortowania szybkiego (ang. quicksort) polega na podziale wektora na dwie części, jedna z nich zawiera elementy mniejsze od elementu dzielącego, druga większe. Każda z części jest znowu sortowana za pomocą algorytmu sortowania szybkiego (wywołania rekurencyjne). - wybierz jeden element w wektorze (oznaczmy go jako p) - dokonaj przemieszczenia elementów tak aby uzyskać elementy < p p elementy >= p - powtórz algorytm dla lewej (elementy mniejsze) i prawej (elementy większe) części wektora Element podziału może być pierwszym, ostatnim, środkowym czy też losowym elementem naszego wektora (często ma on wartość mediany). Przykład, chcemy posortować następujący wektor dane[]. Jako element dzielący wybieramy ostatni element wektora wejściowego. 3 4 1 7 2 8 5 6 Poniżej wszystkie kroki, które zostaną wykonane przy sortowaniu. W metodzie dokonującej przemieszczenia elementów wykorzystujemy dwa indeksy i, j umożliwiające nam wyszukiwanie elementów większych i mniejszych od elementu dzielącego (oznaczonego jako p). Początkowa wartość i to indeks pierwszego elementu wektora wejściowego, a j indeks przedostatniego elementu 15
wektora wejściowego. Na ostatnim indeksie wektora wejściowego mamy element dzielący (zauważ, że wektor wejściowy może być częścią wektora początkowego). Sama metoda realizująca podział może wyglądać następująco. i - indeks pierwszego elementu j - indeks przedostatniego elementu p - wartość ostatniego elementu (zapamiętujemy jego indeks w zmiennej k) // dopóki indeks i jest mniejszy od j while (i < j) { while (wektor[i] < p) i++; // szukamy od początku elementu mniejszego od p while (wektor[j] > p) j--; // szukamy od końca elementu większego od p jeśli nie minęliśmy się indeksami (i<j) to zamieniamy wektor[i] z wektor[j] zamieniamy miejscami elementy wektor[i] z wektor[k], dzięki temu element dzielący znajdzie się na właściwej pozycji zwracamy indeks i (czyli indeks podziału wektora na części) Przykład algorytmu przemieszczającego elementy w naszym wektorze dane[]. Element dzielący p został zaznaczony na niebiesko (jego indeks k wynosi 7). i j wektor wejściowy akcja wektor wyjściowy 0 6 3 4 1 7 2 8 5 6 3 6 3 4 1 7 2 8 5 6 zamień dane[i] z dane[j] 3 4 1 5 2 8 7 6 5 4 3 4 1 5 2 8 7 6 zamień dane[i] z dane[k] 3 4 1 5 2 6 7 8 Metoda zwróci nam indeks i czyli wartość 5. Ta informacja jest nam potrzebna do kolejnego wywołania algorytmu (dla lewej i prawej części wektora). Wszystkie wywołania zostały przedstawione poniżej. 3 4 1 5 2 6 7 8 1 2 3 5 4 7 8 1 3 4 5 7 3 5 6 7 8 16
Sortowanie szybkie jest przykładem algorytmu typu dziel i zwyciężaj. W skrócie zadanie jest dzielone na podzadania, które są przetwarzane na mniejszej ilości danych. Rozwiązania podzadań są składane w jedną całośći stanowią rozwiązanie dla zadania głównego. Wyszukiwanie wartości w wektorze Mając dany zbiór elementów zazwyczaj chcemy znaleźć konkretną wartość. Do przeszukiwania wektora stosuje się zazwyczaj wyszukiwanie sekwencyjne bądź też binarne. Wyszukiwanie sekwencyjne polega na sprawdzeniu kolejnych wartości (przeglądamy elementy od pierwszego do ostatniego indeksu). Wyszukiwanie binarne (ang. binary search) odbywa się na uporządkowanym ciągu elementów. Polega na połowieniu ciągu i ponownym szukaniu w tej połowie, w której może występować poszukiwana wartość (analogia do szukania danych w książce telefonicznej czy też w encyklopedii). Wyszukiwanie binarne najczęściej jest realizowane za pomocą rekurencji. Zadania do wykonania 1. Napisz program, który utworzy n-elementowy wektor, wygeneruje n pseudolosowych liczb całkowitych z przedziału [5, 67] (wartość n ma być określana w trakcie działania programu), uzupełni nimi wektor, a następnie dokona posortowania wektora oraz wyświetli jego zawartość pzed i po sortowaniu. Zastosuj algorytm sortowania przez wybieranie. Samo sortowanie ma się dokonywać za pomocą metody statycznej. Sortujemy w porządku rosnącym. 2. Dopisz do kodu zadania pierwszego metodę statyczną realizującą sortowanie za pomocą algorytmu sortowania przez wstawianie. 3. Dopisz do kodu zadania pierwszego metodę statyczną realizującą sortowanie za pomocą algorytmu sortowania bąbelkowego. 4. Dopisz do kodu zadania pierwszego metodę statyczną realizującą sortowanie za pomocą algorytmu sortowania szybkiego. 5. Napisz program, który pozwoli na wczytanie n ciągów znaków (wartość n ma być określana w trakcie działania programu), a następnie uporządkuje je alfabetycznie i w takiej postaci wyświetli na ekranie monitora. Przyjmij, że ciągi znaków składają się wyłącznie z liter alfabetu łacińskiego. W trakcie sortowania program nie powinien rozróżniać małych i dużych liter. Zastosuj algorytm sortowania przez wybieranie. Utwórz odpowiedni wektor oraz przypomnij sobie metody porównujące łańcuchy tekstowe (klasa String). 6. Napisz program pozwalający na wczytanie n liczb. Wprowadzane liczby mają być od razu umieszczane w wektorze w porządku rosnącym. Wyświetl utworzony wektor. 17
7. Napisz program pozwalający na wczytanie do wektora n wartości całkowitych i wyszukujący podaną przez użytkownika wartość. Zastosuj wyszukiwanie liniowe. 8. Napisz program pozwalający na wprowadzenie do wektora n wartości całkowitych. Każdą wprowadzaną liczbę należy umieścić w wektorze od razu w porządku rosnącym. Następnie program ma znaleźć wśród wartości znajdujących się w wektorze zadaną przez użytkownika liczbę. Zastosuj algorytm wyszukiwania binarnego Wykorzystaj rozwiązanie zadania nr 7. 18