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 |
©2024 mgr Jerzy Wałaszek |
ProblemDaną listę dwukierunkową należy posortować rosnąco
algorytmem
|
Wynalazcą algorytmu
Wybieramy na liście jeden element, który nazwiemy piwotem (ang. pivot – element zwrotny). Następnie tak przekształcamy listę, aby elementy większe lub równe piwotowi znalazły się po prawej stronie piwota, a elementy mniejsze po stronie lewej:
Piwot dzieli listę na dwie części, które będziemy nazywać lewą partycją (elementy mniejsze) i prawą partycją (elementy równe lub większe). Zwróć uwagę, że po tej operacji piwot zajął na liście swoją końcową pozycję. Teraz w ten sam sposób sortujemy lewą i prawą partycję. Operacje wykonujemy rekurencyjnie dotąd, aż dojdziemy do partycji pustych lub jednoelementowych. Ponieważ fizycznie nie musimy rozłączać listy, to po wykonaniu tej procedury stanie się ona posortowana, ponieważ każdy podział partycji częściowo porządkuje listę.
Należy rozwiązać kilka problemów technicznych. Po pierwsze, jak wybrać piwot? Najlepszym rozwiązaniem byłoby wybieranie go w sposób losowy wewnątrz listy. Możemy też ułatwić sobie sprawę i jako piwot wybierać pierwszy lub ostatni element listy. Jeśli lista jest nieuporządkowana, to taki sposób wyboru piwota jest zupełnie wystarczający.
Jeśli wybierzemy piwot na początku listy, to podział na partycje uzyskamy przechodząc przez elementy następne aż do końca listy, a następnie porównując je z piwotem. Jeśli dany element jest mniejszy od pivota, to odłączamy go od listy i dołączamy przed pivota. W efekcie w prawej partycji pozostaną tylko elementy większe lub równe piwotowi. Elementy mniejsze zostaną przeniesione do partycji lewej.
Drugim problemem jest podział listy na dwie partycje. Osiągniemy to dodając na początku i na końcu listy wartowników.
Po wybraniu pivota elementy mniejsze dopisujemy przed piwotem. Listę przeszukujemy aż do natrafienia na wartownika prawego. Dalsze podziały listy na partycje nie wymagają już dodawania nowych wartowników – staną się nimi piwoty i poprzedni wartownicy:
Partycja jest pusta, jeśli następnikiem lewego wartownika jest wartownik prawy. Partycja jest jednoelementowa, jeśli następnikiem piwota będzie prawy wartownik. Te spostrzeżenia pozwolą nam zakończyć rekurencję.
Po posortowaniu listy usuwamy z niej pierwszy i ostatni element, czyli dodanych na początku wartowników.
K01: JeśliL .count < 2, ; listy pustej i jednoelementowej nie sortuj to zakończ K02: l_push_front(L , 0) ; dodaj lewego wartownika K03: l_push_back(L , 0) ; dodaj prawego wartownika K04: l_partition(L .head ,L .tail ) ; dziel na partycje, ; w efekcie sortując listę K05: l_pop_front(L ) ; usuń lewego wartownika K06: l_pop_back(L ) ; usuń prawego wartownika K07: Zakończ
K01:p ←lg →next ; ustawiamy piwot na pierwszy element listy K02: Jeślip =rg , ; partycja jest pusta, kończymy to zakończ K03:i ←p →next ; i rozpoczyna jako następnik piwota K04: Jeślii =rg , ; partycja jest jednoelementowa, kończymy to zakończ K05:j ←i ; j jest badanym elementem K06i ←i →next ; i jest zawsze następnikiem K07: Jeślij →data ≥p →data , ; szukamy elementu mniejszego od piwota, to idź do kroku K14 ; pomijając elementy większe lub równe K08:j →prev →next ←j →next ; gdy go znajdziemy, to odłączamy od listy K09:j →next →prev ←j →prev K10:j →next ←p ; i dołączamy go przed piwotem p K11:j →prev ←p →prev K12:p →prev ←j K13:j →prev →next ←j K14: Jeślii ≠rg , ; jeśli nie przetworzyliśmy całej partycji, to idź do kroku K05 ; to wracamy na początek pętli K15: Jeślilg →next ≠p , ; rekurencyjnie dzielimy lewą partycję to l_partitionlg ,p ) K16: Jeślip →next ≠rg , ; i prawą partycje to l_partition(p ,rg ) K17: Zakończ
Uwaga: podany algorytm sortowania szybkiego jest jednym z wielu możliwych. Zmiany dotyczą głównie sposobów wyboru piwota oraz przeszukiwania partycji.
Uwaga: Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz sposób korzystania z nich. |
Program tworzy listę 200 pseudolosowych liczb całkowitych, po czym sortuje ją algorytmem Quick Sort. Program wykorzystuje obiekt listy dwukierunkowej, w którym typ danych zmieniliśmy ze znakowego na całkowity. W obiekcie pozostawiliśmy jedynie niezbędne metody. |
Pascal// Sortowanie szybkie listy dwukierunkowej // Data: 08.07.2012 // (C)2020 mgr Jerzy Wałaszek //---------------------------------------- program dlist_qsort; // Definicje typów //---------------- type PDLel = ^DLel; // wskaźnik do elementu listy // Element listy //-------------- DLel = record next : PDLel; // następnik prev : PDLel; // poprzednik data : integer; end; // Definicja obiektu listy dwukierunkowej //--------------------------------------- DLvar = object public head : PDLel; // początek listy tail : PDLel; // koniec listy count : cardinal; // licznik elementów constructor init; destructor destroy; procedure l_print; procedure l_push_front(v : integer); procedure l_push_back(v : integer); procedure l_remove(e : PDLel); procedure l_pop_front; procedure l_pop_back; procedure l_quicksort; procedure l_partition(lg, rg : PDLel); end; //--------------------------------------------- // Definicje metod obiektu listy dwukierunkowej //--------------------------------------------- // Inicjuje pola zmiennej listy //----------------------------- constructor DLvar.init; begin head := nil; tail := nil; count := 0; end; // Usuwa elementy listy //--------------------- destructor DLvar.destroy; begin while count > 0 do l_pop_front; end; // Procedura wyświetla zawartość elementów listy //---------------------------------------------- procedure DLvar.l_print; var p : PDLel; begin p := head; while p <> NIL do begin write(p^.data:4); p := p^.next; end; writeln; end; // Procedura dodaje nowy element na początek listy //------------------------------------------------ procedure DLvar.l_push_front(v : integer); var p : PDLel; begin new(p); // tworzymy nowy element p^.data := v; p^.prev := nil; p^.next := head; head := p; inc(count); if p^.next <> nil then p^.next^.prev := p else tail := p; end; // Procedura dodaje nowy element na koniec listy //---------------------------------------------- procedure DLvar.l_push_back(v : integer); var p : PDLel; begin new(p); // tworzymy nowy element p^.data := v; p^.next := nil; p^.prev := tail; tail := p; inc(count); if p^.prev <> nil then p^.prev^.next := p else head := p; end; // Procedura usuwa wybrany element z listy //---------------------------------------- procedure DLvar.l_remove(e : PDLel); begin dec(count); if e^.prev <> nil then e^.prev^.next := e^.next else head := e^.next; if e^.next <> nil then e^.next^.prev := e^.prev else tail := e^.prev; dispose(e); end; // Procedura usuwa element z początku listy //----------------------------------------- procedure DLvar.l_pop_front; begin if count > 0 then l_remove(head); end; // Procedura usuwa element z końca listy //-------------------------------------- procedure DLvar.l_pop_back; begin if count > 0 then l_remove(tail); end; // Procedura szybkiego sortowania //------------------------------- procedure DLvar.l_quicksort; begin if count > 1 then begin l_push_front(0); // dodajemy lewego wartownika l_push_back(0); // dodajemy prawego wartownika l_partition(head, tail); // tworzymy partycje l_pop_back; // usuwamy prawego wartownika l_pop_front; // usuwamy lewego wartownika end; end; // Procedura rekurencyjnie dzieli na partycje //------------------------------------------- procedure DLvar.l_partition(lg, rg : PDLel); var p, i, j : PDLel; begin p := lg^.next; // piwot i := p^.next; if (p <> rg) and (i <> rg) then begin repeat // szukamy elementów mniejszych od piwota j := i; i := i^.next; if(j^.data) < (p^.data) then begin j^.prev^.next := j^.next; // element wycinamy j^.next^.prev := j^.prev; // z listy j^.next := p; // i wstawiamy go przed piwot j^.prev := p^.prev; p^.prev := j; j^.prev^.next := j; end; until i = rg; if lg^.next <> p then l_partition(lg, p); if p^.next <> rg then l_partition(p, rg); end; end; //--------------- // Program główny //--------------- var L : DLvar; begin randomize; L.init; while L.count < 200 do L.l_push_back(random(101)-50); L.l_print; L.l_quicksort; writeln; L.l_print; L.destroy; end. |
// Sortowanie szybkie listy dwukierunkowej // Data: 08.07.2012 // (C)2020 mgr Jerzy Wałaszek //---------------------------------------- #include <iostream> #include <iomanip> #include <ctime> #include <cstdlib> using namespace std; // Element listy //-------------- struct DLel { DLel * next; // następnik DLel * prev; // poprzednik int data; }; // Definicja obiektu listy dwukierunkowej //--------------------------------------- class DLvar { public: DLel * head; // początek listy DLel * tail; // koniec listy unsigned count; // licznik elementów DLvar(); // konstruktor ~DLvar(); // destruktor void l_print(); void l_push_front(int v); void l_push_back(int v); void l_remove(DLel * e); void l_quicksort(); void l_partition(DLel * lg, DLel * rg); void l_pop_front(); void l_pop_back(); }; //------------------------------------ // Metody obiektu listy dwukierunkowej //------------------------------------ // Inicjuje pola zmiennej listy //----------------------------- DLvar::DLvar() { head = tail = NULL; count = 0; } // Usuwa listę z pamięci //---------------------- DLvar::~DLvar() { while(count) l_pop_front(); } // Wyświetla zawartość elementów listy //---------------------------------------------- void DLvar::l_print() { DLel * p; p = head; while(p) { cout << setw(4) << p->data; p = p->next; } cout << endl; } // Dodaje nowy element na początek listy //------------------------------------------------ void DLvar::l_push_front(int v) { DLel * p; p = new DLel; p->data = v; p->prev = NULL; p->next = head; head = p; count++; if(p->next) p->next->prev = p; else tail = p; } // Dodaje nowy element na koniec listy //---------------------------------------------- void DLvar::l_push_back(int v) { DLel * p; p = new DLel; p->data = v; p->next = NULL; p->prev = tail; tail = p; count++; if(p->prev) p->prev->next = p; else head = p; } // Usuwa wybrany element z listy //------------------------------ void DLvar::l_remove(DLel * e) { count--; if(e->prev) e->prev->next = e->next; else head = e->next; if(e->next) e->next->prev = e->prev; else tail = e->prev; delete e; } // Usuwa element z początku listy //------------------------------- void DLvar::l_pop_front() { if(count) l_remove(head); } // Usuwa element z końca listy //---------------------------- void DLvar::l_pop_back() { if(count) l_remove(tail); } // Procedura szybkiego sortowania //------------------------------- void DLvar::l_quicksort() { if(count > 1) { l_push_front(0); // dodajemy lewego wartownika l_push_back(0); // dodajemy prawego wartownika l_partition(head, tail); // tworzymy partycje l_pop_back(); // usuwamy prawego wartownika l_pop_front(); // usuwamy lewego wartownika } } // Rekurencyjny podział na partycje //--------------------------------- void DLvar::l_partition(DLel * lg, DLel * rg) { DLel * p, * i, * j; p = lg->next; // piwot i = p->next; if((p != rg) && (i != rg)) { do // szukamy elementów mniejszych od piwota { j = i; i = i->next; if(j->data < p->data) { j->prev->next = j->next; // element wycinamy j->next->prev = j->prev; // z listy j->next = p; // i wklejamy go przed piwot j->prev = p->prev; p->prev = j; j->prev->next = j; } } while(i != rg); if(lg->next != p) l_partition(lg, p); if(p->next != rg) l_partition(p, rg); } } //--------------- // Program główny //--------------- int main() { DLvar L; srand(time(NULL)); while(L.count < 200) L.l_push_back((rand()%101)-50); L.l_print(); L.l_quicksort(); cout << endl; L.l_print(); return 0; } |
Basic' Sortowanie szybkie listy dwukierunkowej ' Data: 08.07.2012 ' (C)2020 mgr Jerzy Wałaszek '---------------------------------------- ' Element listy '-------------- Type DLel next As DLel Ptr ' następnik prev As DLel Ptr ' poprzednik data As Integer End Type ' Typ obiektowy listy dwukierunkowej '----------------------------------- Type DLvar head As DLel Ptr ' początek listy tail As DLel Ptr ' koniec listy count As UInteger ' licznik elementów Declare Constructor() Declare Destructor() Declare Sub l_print() Declare Sub l_push_front(v As Integer) Declare Sub l_push_back(v As Integer) Declare Sub l_remove(e As DLel Ptr) Declare Sub l_pop_front() Declare Sub l_pop_back() Declare Sub l_quicksort() Declare Sub l_partition(ByVal lg As DLel Ptr, _ ByVal rg As DLel Ptr) End Type ' Konstruktor listy '------------------ Constructor DLvar() head = 0 tail = 0 count = 0 End Constructor ' Usuwa listę z pamięci '---------------------- Destructor DLvar() While count > 0 l_pop_front() Wend End Destructor ' Wyświetla zawartość elementów listy '------------------------------------ Sub DLvar.l_print() Dim p As DLel Ptr p = head while p Print Using "####";p->Data; p = p->next Wend Print End Sub ' Dodaje nowy element na początek listy '-------------------------------------- Sub DLvar.l_push_front(v As Integer) Dim p As DLel Ptr p = New DLel p->data = v p->prev = 0 p->next = head head = p count += 1 If p->next Then p->next->prev = p Else tail = p End If End Sub ' Dodaje nowy element na koniec listy '------------------------------------ Sub DLvar.l_push_back(v As Integer) Dim p As DLel Ptr p = New DLel p->data = v p->next = 0 p->prev = tail tail = p count += 1 If p->prev Then p->prev->next = p Else head = p End If End Sub ' Usuwa wybrany element z listy '------------------------------ Sub DLvar.l_remove(e As DLel Ptr) count -= 1 If e->prev Then e->prev->next = e->next Else head = e->next End If If e->next Then e->next->prev = e->prev Else tail = e->prev End If Delete e End Sub ' Usuwa element z początku listy '------------------------------- Sub DLvar.l_pop_front() If count > 0 Then _ l_remove(head) End Sub ' Usuwa element z końca listy '---------------------------- Sub DLvar.l_pop_back() If count > 0 Then _ l_remove(tail) End Sub ' Procedura szybkiego sortowania '------------------------------- sub DLvar.l_quicksort() If count > 1 Then l_push_front(0) ' lewy wartownik l_push_back(0) ' prawy wartownik l_partition(head, tail) ' tworzymy partycje l_pop_back() ' usuwamy prawego wartownika l_pop_front() ' usuwamy lewego wartownika End If End Sub ' Dokonuje rekurencyjnego podziału na partycje '--------------------------------------------- sub DLvar.l_partition(ByVal lg As DLel Ptr, _ ByVal rg As DLel Ptr) Dim As DLel Ptr p, i, j p = lg->next ' piwot i = p->Next If (p <> rg) AndAlso (i <> rg) Then Do ' szukamy elementów mniejszych od piwota j = i i = i->Next if j->data < p->Data Then j->prev->next = j->next ' element wycinamy j->next->prev = j->prev j->next = p ' i wklejamy go przed piwotem j->prev = p->prev p->prev = j j->prev->next = j End If Loop Until i = rg If lg->next <> p then _ l_partition(lg, p) If p->next <> rg Then _ l_partition(p, rg) End If End Sub '--------------- ' Program główny '--------------- Dim L As DLvar Randomize while L.count < 200 L.l_push_back(Int(Rnd*101)-50) Wend L.l_print() L.l_quicksort() Print L.l_print() End |
Python (dodatek) |
# Sortowanie szybkie listy dwukierunkowej # Data: 30.05.2024 # (C)2024 mgr Jerzy Wałaszek # ---------------------------------------- # klasa elementu listy # --------------------- class DLel: def __init__(self, data): self.next = None self.prev = None self.data = data # klasa listy dwukierunkowej # --------------------------- class DLvar: # Konstruktor def __init__(self): self.head = None self.tail = None self.count = 0 # Destructor def __del__(self): while self.count: self.l_pop_front() # Wyświetla zawartość elementów listy def l_print(self): p = self.head while p: print("%4d" % p.data, end="") p = p.next print() # Dodaje nowy element na początek def l_push_front(self, v): p = DLel(v) p.next = self.head self.head = p self.count += 1 if p.next: p.next.prev = p else: self.tail = p # Dodaje nowy element na koniec def l_push_back(self, v): p = DLel(v) p.prev = self.tail self.tail = p self.count += 1 if p.prev: p.prev.next = p else: self.head = p # Usuwa wybrany element z listy def l_remove(self, e): self.count -= 1 if e.prev: e.prev.next = e.next else: self.head = e.next if e.next: e.next.prev = e.prev else: self.tail = e.prev # Usuwa element z początku def l_pop_front(self): if self.count: self.l_remove(self.head) # Usuwa element z końca def l_pop_back(self): if self.count: self.l_remove(self.tail) # Szybkie sortowanie def l_quicksort(self): if self.count > 1: self.l_push_front(0) self.l_push_back(0) self.l_partition(self.head, self.tail) self.l_pop_back() self.l_pop_front() # Rekurencyjny podział na partycje def l_partition(self, lg, rg): p = lg.next i = p.next if (p is not rg) and (i is not rg): while True: j = i i = i.next if j.data < p.data: j.prev.next = j.next j.next.prev = j.prev j.next = p j.prev = p.prev p.prev = j j.prev.next = j if i is rg: break if lg.next is not p: self.l_partition(lg, p) if p.next is not rg: self.l_partition(p, rg) # --------------- # Program główny # --------------- import random dl = DLvar() while dl.count < 200: dl.l_push_back(random.randrange(-50, 51)) dl.l_print() dl.l_quicksort() print() dl.l_print() |
Wynik: |
10 -5 28 18 42 -46 -8 43 19 28 30 -30 7 6 1 38 -50 -10 43 13 -26 20 14 45 43 42 -42 -21 26 43 -43 -19 -10 36 40 19 18 -10 -5 49 16 -1 -1 -34 -26 -50 -30 -13 -20 -31 31 -30 -3 10 48 -31 -20 43 -3 -38 22 48 49 -32 16 -32 -50 -39 10 24 -46 -38 -33 -3 18 13 -47 -36 -47 12 39 -8 -14 0 44 -38 -20 34 -20 0 44 -5 -44 -14 5 45 1 -32 2 -34 18 -39 42 -6 -31 -44 47 -14 -32 -20 -13 -26 9 -6 -31 17 24 25 3 -29 -21 -4 -8 31 13 -40 -28 -25 -27 -38 5 -29 -45 -28 -38 -29 -21 -17 -24 -33 -28 -32 48 -27 -17 -31 42 17 -22 -6 18 31 44 21 -25 -11 35 -16 15 26 -15 -1 27 -13 30 -28 -39 -14 27 -31 -3 -18 20 16 -42 -44 49 47 23 -2 -13 -33 -24 42 -46 -7 44 23 44 14 -33 -10 37 -18 43 46 -36 36 15 -11 -50 -50 -50 -47 -47 -46 -46 -46 -45 -44 -44 -44 -43 -42 -42 -40 -39 -39 -39 -38 -38 -38 -38 -38 -36 -36 -34 -34 -33 -33 -33 -33 -32 -32 -32 -32 -32 -31 -31 -31 -31 -31 -31 -30 -30 -30 -29 -29 -29 -28 -28 -28 -28 -27 -27 -26 -26 -26 -25 -25 -24 -24 -22 -21 -21 -21 -20 -20 -20 -20 -20 -19 -18 -18 -17 -17 -16 -15 -14 -14 -14 -14 -13 -13 -13 -13 -11 -11 -10 -10 -10 -10 -8 -8 -8 -7 -6 -6 -6 -5 -5 -5 -4 -3 -3 -3 -3 -2 -1 -1 -1 0 0 1 1 2 3 5 5 6 7 9 10 10 10 12 13 13 13 14 14 15 15 16 16 16 17 17 18 18 18 18 18 19 19 20 20 21 22 23 23 24 24 25 26 26 27 27 28 28 30 30 31 31 31 34 35 36 36 37 38 39 40 42 42 42 42 42 43 43 43 43 43 43 44 44 44 44 44 45 45 46 47 47 48 48 48 49 49 49 |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2024 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.