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 |
ProblemNależy zaprojektować listę, w której dostęp do elementów wyszukiwanych najczęściej jest najkrótszy. |
Tego typu lista nosi nazwę listy samoorganizującej się (ang. self organizing list). Samoorganizujące się listy posiadają wiele praktycznych zastosowań. Ich zaletą jest fakt, że czas dostępu do elementów często używanych jest krótszy niż dla pozostałych elementów. Dzięki takie cesze listy samoorganizujące się mogą być bardziej efektywne dla niektórych algorytmów, np. dla baz danych. Przykładem jest lista odwiedzanych stron w przeglądarce. Gdy ją rozwiniemy, to na samym początku znajdą się najczęściej odwiedzane strony. W ten sposób szybko możemy przejść do ulubionej strony WWW. Podobnie działa lista skrótów do ostatnio używanych programów w menu Start w systemie Windows – na samej górze listy system umieszcza skróty do najczęściej uruchamianych aplikacji. Z badań wynika, że 80% odczytów z listy dotyczy jedynie 20% jej elementów. Pozostałe są wykorzystywane rzadko. Istnieje kilka strategii rozwiązania tego problemu. Polegają one na takim uporządkowaniu listy, aby elementy wyszukiwane najczęściej znalazły się na jej początku. Dzięki temu algorytm przeszukujący listę natrafi na nie szybciej. |
W metodzie tej każdy znaleziony element jest umieszczany na początku listy. Można ją w prosty sposób zaimplementować na listach jedno i dwukierunkowych. Nie wymaga dodatkowej pamięci. Szybko przebudowuje listę w miarę wyszukiwania w niej elementów, jednakże niektóre elementy mogą zostać ocenione zbyt wysoko przez tę metodę, tzn. element rzadko wykorzystywany może być przeniesiony do początku listy.
K01: p ← L.head ; rozpoczynamy wyszukiwanie ; od pierwszego elementu listy K02: Jeśli p = nil, ; jeśli koniec listy, to idź do kroku K09 ; to kończymy K03: Jeśli p→data ≠ v, ; sprawdzamy, czy natrafiliśmy to idź do kroku K07 ; na poszukiwany element K04: Odłącz element p od listy K05: Wstaw element p na początek listy K06: Idź do kroku K09 ; wychodzimy z pętli K07: p ← p→next ; następny element na liście K08: Idź do kroku K02 ; i kontynuujemy pętlę K09: Zakończ z wynikiem p
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 uporządkowaną listę
dwukierunkową 10 liczb całkowitych |
Pascal// Samoorganizujące się listy // Przesuwanie na początek listy // Data: 04.08.2012 // (C)2020 mgr Jerzy Wałaszek //------------------------------ program move_to_front; // Typ elementów listy //-------------------- type PDLel = ^DLel; DLel = record next : PDLel; prev : PDLel; data : integer; end; // Definicja typu obiektowego DLvar //------------------------------------ DLvar = object public head : PDLel; // początek listy tail : PDLel; // koniec listy constructor init; destructor destroy; procedure l_print; procedure l_push_front(v : integer); procedure l_pop_front; function l_find(v : integer) : PDLel; end; //------------------------ // Metody obiektu DLvar //------------------------ // Konstruktor-inicjuje pole head //--------------------------------- constructor DLvar.init; begin head := nil; tail := nil; end; // Destruktor-usuwa listę z pamięci //----------------------------------- destructor DLvar.destroy; begin while head <> nil do l_pop_front; end; // Procedura wyświetla zawartość listy //------------------------------------ procedure DLvar.l_print; var p : PDLel; begin p := head; while p <> nil do begin if p = head then write(' (', p^.data, ')') else write(' ', p^.data, ' '); p := p^.next; end; writeln; end; // Procedura dołączania 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; if p^.next <> nil then p^.next^.prev := p else tail := p; end; // Procedura usuwa pierwszy element //--------------------------------- procedure DLvar.l_pop_front; var p : PDLel; begin p := head; if p <> nil then begin head := p^.next; if head = nil then tail := nil else head^.prev := nil; dispose(p); end; end; // Funkcja wyszukuje element, a jeśli go // znajdzie, to umieszcza go na początku // listy. Zwraca adres znalezionego elementu // lub nil //------------------------------------------ function DLvar.l_find(v : integer) : PDLel; var p : PDLel; begin p := head; // rozpoczynamy od początku while p <> nil do // przeszukujemy listę begin if p^.data = v then // element znaleziony? begin // odłączamy element od listy if p^.prev <> nil then p^.prev^.next := p^.next else head := p^.next; if p^.next <> nil then p^.next^.prev := p^.prev else tail := p^.prev; // umieszczamy go na początku listy p^.next := head; p^.prev := nil; head := p; if p^.next <> nil then p^.next^.prev := p else tail := p; break; // opuszczamy pętlę end; p := p^.next; // do następnego elementu end; // kontynuujemy pętlę l_find := p; // zwracamy wynik p end; //--------------- // Program główny //--------------- var L : DLvar; // obiekt listy i, v : integer; begin randomize; L.init; // inicjujemy obiekt for i := 9 downto 0 do L.l_push_front(i);// tworzymy listę for i := 1 to 20 do // przeszukujemy listę begin v := random(10);// losujemy element write(v, ': '); // wyświetlamy go L.l_find(v); // szukamy elementu L.l_print; // wyświetlamy zawartość end; L.destroy; // usuwamy listę z pamięci end. |
// Samoorganizujące się listy // Przesuwanie na początek listy // Data: 04.08.2012 // (C)2020 mgr Jerzy Wałaszek //------------------------------ #include <iostream> #include <cstdlib> #include <ctime> 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 DLvar(); // konstruktor ~DLvar(); // destruktor void l_print(); void l_push_front(int v); void l_pop_front(); DLel * l_find(int v); }; //------------------------------------ // Metody obiektu listy dwukierunkowej //------------------------------------ // Inicjuje pola zmiennej listy //----------------------------- DLvar::DLvar() { head = tail = NULL; } // Usuwa listę z pamięci //---------------------- DLvar::~DLvar() { while(head) l_pop_front(); } // Wyświetla zawartość elementów listy //------------------------------------ void DLvar::l_print() { for(DLel * p = head; p; p = p->next) if(p == head) cout << " (" << p->data << ")"; else cout << " " << p->data << " "; cout << endl; } // Dołącza na początek listy //-------------------------- void DLvar::l_push_front(int v) { DLel * p = new DLel; p->data = v; p->prev = NULL; p->next = head; head = p; if(p->next) p->next->prev = p; else tail = p; } // Usuwa pierwszy element //----------------------- void DLvar::l_pop_front() { if(DLel * p = head) { if(!(head = p->next)) tail = NULL; else head->prev = NULL; delete p; } } // Wyszukuje element, a jeśli go znajdzie, // to umieszcza go na początku listy. Zwraca // adres znalezionego elementu lub nil //------------------------------------------- DLel * DLvar::l_find (int v) { DLel * p; for(p = head; p; p = p->next) if(p->data == v) // element znaleziony? { // odłączamy element od listy if(p->prev) p->prev->next = p->next; else head = p->next; if(p->next) p->next->prev = p->prev; else tail = p->prev; // umieszczamy go na początku listy p->next = head; p->prev = NULL; head = p; if(p->next) p->next->prev = p; else tail = p; break; // opuszczamy pętlę } return p; // zwracamy wynik p } //--------------- // Program główny //--------------- int main() { DLvar L; // obiekt listy int i, v; srand(time(NULL)); for(i = 9; i >= 0; i--) L.l_push_front(i); // tworzymy listę for(i = 0; i < 20; i++) // przeszukujemy listę { v = rand()%10; // losujemy element cout << v << ": "; // wyświetlamy go L.l_find(v); // szukamy elementu L.l_print(); // wyświetlamy zawartość listy } return 0; } |
Basic' Samoorganizujące się listy ' Przesuwanie na początek listy ' Data: 04.08.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 ' Definicja obiektu listy dwukierunkowej '--------------------------------------- Type DLvar head As DLel Ptr ' początek listy tail As DLel Ptr ' koniec listy Declare Constructor() Declare Destructor() Declare Sub l_print() Declare Sub l_push_front(v As Integer) Declare Sub l_pop_front() Declare Function l_find(v As Integer)_ As DLel Ptr End Type '--------------- ' Program główny '--------------- Dim L As DLvar Dim As Integer i, v Randomize ' inicjujemy liczby pseudolosowe For i = 9 To 0 Step -1 L.l_push_front(i) ' tworzymy listę Next For i = 1 To 20 ' przeszukujemy listę v = Int(Rnd()*10) ' losujemy element Print Using "#: ";v; ' wyświetlamy go L.l_find(v) ' szukamy elementu L.l_print() ' wyświetlamy zawartość listy Next End '------------------------------------ ' Metody obiektu listy dwukierunkowej '------------------------------------ ' Konstruktor listy '------------------ Constructor DLvar() head = 0 tail = 0 End Constructor ' Usuwa listę z pamięci Destructor DLvar() While head 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 If p = head Then Print Using " (#)";p->data; Else Print Using " # ";p->data; End If p = p->Next Wend Print End Sub ' Dołącza 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 If p->Next Then p->next->prev = p Else tail = p End If End Sub ' Usuwa pierwszy element '----------------------- Sub DLvar.l_pop_front() Dim p As DLel Ptr p = head If p Then head = p->Next If head = 0 Then tail = 0 Else head->prev = 0 End If Delete p End If End Sub ' Wyszukuje element, a jeśli go znajdzie, ' to umieszcza go na początku listy. Zwraca ' adres znalezionego elementu lub nil '------------------------------------------------ Function DLvar.l_find(v As integer)_ As DLel Ptr Dim p As DLel Ptr p = head While p ' w pętli przeszukujemy listę If p->data = v Then ' element znaleziony? ' odłączamy element od listy If p->prev Then p->prev->next = p->next Else head = p->Next End If If p->next Then p->next->prev = p->prev Else tail = p->prev End If ' umieszczamy go na początku listy p->next = head p->prev = 0 head = p if p->Next Then p->next->prev = p Else tail = p End If Exit While ' opuszczamy pętlę End If p = p->Next ' idziemy do następnego elementu Wend l_find = p ' zwracamy wynik p End function |
Python (dodatek) |
# Samoorganizujące się listy # Przesuwanie na początek listy # Data: 31.05.2024 # (C)2024 mgr Jerzy Wałaszek # ------------------------------ import random # 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 # Destructor def __del__(self): while self.head: self.l_pop_front() # Wyświetla zawartość elementów listy def l_print(self): p = self.head while p: if p is self.head: print("(", p.data, ")", sep="", end="") else: print(" ", p.data, " ", sep="", 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 if p.next: p.next.prev = p else: self.tail = p # Usuwa pierwszy element def l_pop_front(self): p = self.head if p: self.head = p.next if not self.head: self.tail = None else: self.head.prev = None # Wyszukuje element, a jeśli go znajdzie, # to umieszcza go na początku listy. Zwraca # adres znalezionego elementu lub nil # ----------------------------------------- def l_find(self, v): p = self.head while p: if p.data == v: # element znaleziony? # odłączamy element od listy if p.prev: p.prev.next = p.next else: self.head = p.next if p.next: p.next.prev = p.prev else: self.tail = p.prev # umieszczamy go na początku listy p.next = self.head p.prev = None self.head = p if p.next: p.next.prev = p else: self.tail = p break # opuszczamy pętlę p = p.next return p # zwracamy wynik p # --------------- # Program główny # --------------- dl = DLvar() # obiekt listy for i in reversed(range(10)): # tworzymy listę dl.l_push_front(i) for i in range(20): # przeszukujemy listę # losujemy element v = random.randrange(10) # wyświetlamy go print(v, ": ", sep="", end="") # szukamy elementu dl.l_find(v) # wyświetlamy zawartość listy dl.l_print() |
Wynik: |
4: (4) 0 1 2 3 5 6 7 8 9 6: (6) 4 0 1 2 3 5 7 8 9 7: (7) 6 4 0 1 2 3 5 8 9 5: (5) 7 6 4 0 1 2 3 8 9 0: (0) 5 7 6 4 1 2 3 8 9 6: (6) 0 5 7 4 1 2 3 8 9 7: (7) 6 0 5 4 1 2 3 8 9 7: (7) 6 0 5 4 1 2 3 8 9 2: (2) 7 6 0 5 4 1 3 8 9 0: (0) 2 7 6 5 4 1 3 8 9 4: (4) 0 2 7 6 5 1 3 8 9 5: (5) 4 0 2 7 6 1 3 8 9 5: (5) 4 0 2 7 6 1 3 8 9 6: (6) 5 4 0 2 7 1 3 8 9 4: (4) 6 5 0 2 7 1 3 8 9 5: (5) 4 6 0 2 7 1 3 8 9 0: (0) 5 4 6 2 7 1 3 8 9 6: (6) 0 5 4 2 7 1 3 8 9 8: (8) 6 0 5 4 2 7 1 3 9 8: (8) 6 0 5 4 2 7 1 3 9 |
W tej metodzie każdy element listy posiada dodatkowe pole licznika. Po znalezieniu elementu jego licznik zostaje zwiększony, a następnie lista zostaje uporządkowana względem malejącej wartości liczników. W ten sposób elementy wykorzystywane najczęściej zajmują początkowe pozycje listy. Ta metoda jest lepsza od poprzedniej, ponieważ rozkład elementów lepiej odpowiada częstości ich wykorzystywania. Wadą jest zwiększone zapotrzebowanie na pamięć, ponieważ musimy z każdym elementem dodatkowo przechowywać jego licznik. Musimy również pamiętać, że liczniki posiadają górny zakres, np. 4 miliardów. Jeśli program przekroczy tę wartość, to liczniki się przewiną i element najczęściej używany trafi na koniec listy (w takim przypadku można zastosować różne strategie normalizacyjne – np. umawiamy się, że jeśli wartość licznika przekroczy, powiedzmy, 4 miliardy, to wszystkie liczniki zmniejszamy o 2 miliardy, a te, które po zmniejszeniu mają stan ujemny, zerujemy – pozwoli to zachować względną kolejność elementów na liście).
Metoda wymaga nowego typu elementów:
Pascaltype PDLel = ^DLel; DLel = record next : PDLel; prev : PDLel; count : Cardinal data : typ_danych; end; |
struct DLel { DLel * next, * prev; unsigned int count; typ_danych data; }; |
BasicType DLel next As DLel Ptr prev As DLel Ptr count As UInteger data As typ_danych End Type |
Python (dodatek) |
# klasa elementu listy #--------------------- class DLel: def __init__(self, data): self.next = None self.prev = None self.count = 0 self.data = data |
K01: p ← L.head ; rozpoczynamy od pierwszego elementu K02: Jeśli p = nil, ; jeśli koniec listy, to kończymy to idź do kroku K14 K03: Jeśli p→data ≠ v, ; element znaleziony? to idź do kroku K09 K04: p→count ← p→count+1 ; zwiększamy licznik użycia K05: r ← p→prev ; zapamiętaj poprzedni element w r K06: Odłącz element p od listy K07: Jeśli r = nil, ; jeśli brak poprzednika, to p idź do kroku K11 ; jest pierwszym elementem K08: Jeśli r→count > p→count, ; szukamy częstszego to idź do kroku K13 ; elementu poprzedzającego K09: r ← r→prev ; cofamy się wstecz o jeden element K10: Idź do kroku K07 ; i kontynuujemy wyszukiwanie K11: Wstaw p na początek listy K12: Idź do kroku K14 K13: Wstaw p za r K14: Zakończ z wynikiem p
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 uporządkowaną listę dwukierunkową 10 liczb całkowitych od 0 do 9. Następnie dokonuje 1000 wyszukiwań liczb losowych od 0 do 9. Na końcu wypisuje zawartość listy wraz ze stanem licznika każdego elementu. W programie wykorzystujemy zmodyfikowany obiekt listy dwukierunkowej. |
Pascal// Samoorganizujące się listy // Zliczanie z sortowaniem // Data: 04.08.2012 // (C)2020 mgr Jerzy Wałaszek //------------------------------ program count_use; // Typ elementów listy //-------------------- type PDLel = ^DLel; DLel = record next : PDLel; prev : PDLel; count : Cardinal; data : integer; end; // Definicja typu obiektowego DLvar //------------------------------------ DLvar = object public head : PDLel; // początek listy tail : PDLel; // koniec listy constructor init; destructor destroy; procedure l_print; procedure l_push_front(v : integer); procedure l_pop_front; function l_find(v : integer) : PDLel; end; //--------------------- // Metody obiektu dlist //--------------------- // Konstruktor-inicjuje pole head //--------------------------------- constructor DLvar.init; begin head := nil; tail := nil; end; // Destruktor-usuwa listę z pamięci //----------------------------------- destructor DLvar.destroy; begin while head <> nil 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 writeln(p^.data, ' : ', p^.count:4); p := p^.next; end; end; // Procedura dołączania na początek listy //--------------------------------------- procedure DLvar.l_push_front(v : integer); var p : PDLel; begin new(p); p^.data := v; p^.count := 0; p^.prev := nil; p^.next := head; head := p; if p^.next <> nil then p^.next^.prev := p else tail := p; end; // Procedura usuwa pierwszy element //--------------------------------- procedure DLvar.l_pop_front; var p : PDLel; begin p := head; if p <> nil then begin head := p^.next; if head = nil then tail := nil else head^.prev := nil; dispose(p); end; end; // Funkcja wyszukuje element, zwiększa jego licznik // i umieszcza na takim miejscu listy, że poprzednik // posiada licznik większy //------------------------------------------------ function DLvar.l_find(v : integer) : PDLel; var p, r : PDLel; begin p := head; // rozpoczynamy od początku listy while p <> nil do // w pętli przeszukujemy listę begin if p^.data = v then // element znaleziony? begin inc(p^.count); // zwiększamy licznik użycia r := p^.prev; // w r zapamiętujemy poprzedni element // odłączamy element od listy if r <> nil then r^.next := p^.next else head := p^.next; if p^.next <> nil then p^.next^.prev := p^.prev else tail := p^.prev; while true do begin if r = nil then begin // umieszczamy go na początku listy p^.next := head; p^.prev := nil; head := p; if p^.next <> nil then p^.next^.prev := p else tail := p; Exit(p); // wychodzimy z funkcji end; if r^.count > p^.count then begin // umieszczamy go za r p^.next := r^.next; r^.next := p; p^.prev := r; if p^.next <> nil then p^.next^.prev := p else tail := p; Exit(p); // wychodzimy z funkcji end; r := r^.prev; // idziemy do poprzedniego elementu end; end; p := p^.next; // przechodzimy do następnego elementu end; // kontynuujemy pętlę l_find := p; // zwracamy wynik p end; //--------------- // Program główny //--------------- var L : DLvar; // obiekt listy i : integer; begin randomize; L.init; // inicjujemy obiekt listy for i := 9 downto 0 do L.l_push_front(i); // tworzymy listę for i := 1 to 1000 do // przeszukujemy listę 1000 razy L.l_find(random(10)); // szukamy elementów od 0 do 9 L.l_print; // wyświetlamy wyniki L.destroy; end. |
// Samoorganizujące się listy // Zliczanie z sortowaniem // Data: 04.08.2012 // (C)2020 mgr Jerzy Wałaszek //------------------------------ #include <iostream> #include <iomanip> #include <cstdlib> #include <ctime> using namespace std; // Element listy //-------------- struct DLel { DLel * next; // następnik DLel * prev; // poprzednik unsigned int count; // licznik int data; }; // Definicja obiektu listy dwukierunkowej //--------------------------------------- class DLvar { public: DLel * head; // początek listy DLel * tail; // koniec listy DLvar(); // konstruktor ~DLvar(); // destruktor void l_print(); void l_push_front(int v); void l_pop_front(); DLel * l_find(int v); }; //------------------------------------ // Metody obiektu listy dwukierunkowej //------------------------------------ // Inicjuje pola zmiennej listy //----------------------------- DLvar::DLvar() { head = tail = NULL; } // Usuwa listę z pamięci //---------------------- DLvar::~DLvar() { while(head) l_pop_front(); } // Procedura wyświetla zawartość listy //------------------------------------ void DLvar::l_print() { for(DLel * p = head; p; p = p->next) cout << p->data << ": " << setw(4) << p->count << endl; } // Procedura dołączania na początek listy //--------------------------------------- void DLvar::l_push_front(int v) { DLel * p = new DLel; p->data = v; p->count = 0; p->prev = NULL; p->next = head; head = p; if(p->next) p->next->prev = p; else tail = p; } // Procedura usuwa pierwszy element //--------------------------------- void DLvar::l_pop_front() { if(DLel * p = head) { if(! (head = p->next)) tail = NULL; else head->prev = NULL; delete p; } } // Funkcja wyszukuje element, zwiększa jego licznik // i umieszcza na takim miejscu listy, iż poprzednik // posiada licznik o stanie większym //------------------------------------------------ DLel * DLvar::l_find(int v) { DLel *p, *r; for(p = head; p; p = p->next) if(p->data == v) // element znaleziony? { p->count++; // zwiększamy licznik użycia r = p->prev; // w r poprzedni element // i odłączamy element od listy if(r) r->next = p->next; else head = p->next; if(p->next) p->next->prev = p->prev; else tail = p->prev; while(true) { if(!r) { // umieszczamy go na początku listy p->next = head; p->prev = NULL; head = p; if(p->next) p->next->prev = p; else tail = p; return p; // wychodzimy z funkcji } if(r->count > p->count) { // umieszczamy go za r p->next = r->next; r->next = p; p->prev = r; if(p->next) p->next->prev = p; else tail = p; return p; // wychodzimy z funkcji } r = r->prev; // idziemy do poprzedniego elementu } } return p; // zwracamy wynik p } //--------------- // Program główny //--------------- int main() { DLvar L; // obiekt listy int i; srand(time(NULL)); for(i = 9; i >= 0; i--) L.l_push_front(i); // tworzymy listę for(i = 0; i < 1000; i++) // przeszukujemy listę 1000 razy, L.l_find(rand() % 10); // porządkując jej elementy L.l_print(); // wyświetlamy zawartość listy return 0; } |
Basic' Samoorganizujące się listy ' Zliczanie z sortowaniem ' Data: 04.08.2012 ' (C)2020 mgr Jerzy Wałaszek '--------------------------- ' Element listy '-------------- Type DLel next As DLel Ptr ' następnik prev As DLel Ptr ' poprzednik count As UInteger ' licznik data As Integer End Type ' Definicja obiektu listy dwukierunkowej '--------------------------------------- Type DLvar head As DLel Ptr ' początek listy tail As DLel Ptr ' koniec listy Declare Constructor() Declare Destructor() Declare Sub l_print() Declare Sub l_push_front(v As Integer) Declare Sub l_pop_front() Declare Function l_find(v As Integer) As DLel Ptr End Type '--------------- ' Program główny '--------------- Dim L As DLvar Dim As Integer i Randomize For i = 9 To 0 Step -1 L.l_push_front(i) ' tworzymy listę Next For i = 1 To 1000 ' przeszukujemy listę 1000 L.l_find(Int(Rnd()*10)) ' szukamy elementów od 0 do 9 Next L.l_print() ' wyświetlamy zawartość listy End '------------------------------------ ' Metody obiektu listy dwukierunkowej '------------------------------------ ' Konstruktor listy Constructor DLvar() head = 0 tail = 0 End Constructor ' Usuwa listę z pamięci Destructor DLvar() While head l_pop_front() Wend End Destructor ' Procedura wyświetla zawartość listy Sub DLvar.l_print() Dim p As DLel Ptr p = head While p Print Using "#: ####";p->Data;p->count p = p->Next Wend Print End Sub ' Procedura dołączania na początek listy Sub DLvar.l_push_front(v As Integer) Dim p As DLel Ptr p = new DLel p->data = v p->count = 0 p->prev = 0 p->next = head head = p If p->Next Then p->next->prev = p Else tail = p End If End Sub ' Procedura usuwa pierwszy element Sub DLvar.l_pop_front() Dim p As DLel Ptr p = head If p Then head = p->Next If head = 0 Then tail = 0 Else head->prev = 0 End If Delete p End If End Sub ' Funkcja wyszukuje element, zwiększa jego licznik ' i umieszcza na takim miejscu listy, iż poprzednik ' posiada licznik o większej wartości '-------------------------------------------------- Function DLvar.l_find(v As integer) As DLel Ptr Dim As DLel Ptr p, r p = head While p ' w pętli przeszukujemy listę if p->data = v Then ' element znaleziony? p->count += 1 ' zwiększamy licznik użycia r = p->prev ' w r zapamiętujemy poprzedni element ' odłączamy element od listy If r then r->next = p->next Else head = p->Next End If If p->Next Then p->next->prev = p->prev Else tail = p->prev End If While 1 If r = 0 Then ' umieszczamy go na początku listy p->next = head p->prev = 0 head = p If p->Next Then p->next->prev = p Else tail = p End If return p ' wychodzimy z funkcji End If If r->count > p->count Then ' umieszczamy go za r p->next = r->Next r->next = p p->prev = r If p->next Then p->next->prev = p Else tail = p End If return p ' wychodzimy z funkcji End If r = r->prev ' idziemy do poprzedniego elementu Wend End If p = p->Next Wend return p ' zwracamy wynik p End function |
Python (dodatek) |
# Samoorganizujące się listy # Zliczanie z sortowaniem # Data: 02.06.2024 # (C)2024 mgr Jerzy Wałaszek # --------------------------- import random # klasa elementu listy # --------------------- class DLel: def __init__(self, data): self.next = None self.prev = None self.count = 0 self.data = data # klasa listy dwukierunkowej # --------------------------- class DLvar: # Konstruktor def __init__(self): self.head = None self.tail = None # Destructor def __del__(self): while self.head: self.l_pop_front() # Wyświetla zawartość elementów listy def l_print(self): p = self.head while p: print(p.data, ": ", "%4d" % p.count, sep="") p = p.next # Dodaje nowy element na początek def l_push_front(self, v): p = DLel(v) p.next = self.head self.head = p if p.next: p.next.prev = p else: self.tail = p # Usuwa pierwszy element def l_pop_front(self): p = self.head if p: self.head = p.next if not self.head: self.tail = None else: self.head.prev = None # Wyszukuje element, zwiększa jego licznik # i umieszcza na takim miejscu listy, iż # poprzednik posiada licznik o stanie większym def l_find(self, v): p = self.head while p: # element znaleziony? if p.data == v: # zwiększamy licznik użycia p.count += 1 # w r poprzedni element r = p.prev # odłączamy element od listy if r: r.next = p.next else: self.head = p.next if p.next: p.next.prev = p.prev else: self.tail = p.prev while True: if not r: # umieszczamy go # na początku listy p.next = self.head p.prev = None self.head = p if p.next: p.next.prev = p else: self.tail = p # wychodzimy z funkcji return p if r.count > p.count: # umieszczamy go za r p.next = r.next r.next = p p.prev = r if p.next: p.next.prev = p else: self.tail = p # wychodzimy z funkcji return p # idziemy do poprzedniego elementu r = r.prev p = p.next return p # zwracamy wynik p # --------------- # Program główny # --------------- dl = DLvar() # obiekt listy for i in reversed(range(10)): # tworzymy listę dl.l_push_front(i) # przeszukujemy listę 1000 razy, # porządkując jej elementy for i in range(1000): dl.l_find(random.randrange(10)) # wyświetlamy zawartość listy dl.l_print() |
Wynik: |
8 : 113 5 : 111 7 : 105 9 : 103 0 : 102 3 : 99 2 : 98 4 : 96 6 : 91 1 : 82 |
W metodzie przemieszczania znaleziony element zostaje wymieniony z elementem go poprzedzającym. W efekcie elementy często używane ciągle są przemieszczane w kierunku początku listy. Zaletą tej metody jest brak konieczności utrzymywania liczników, z drugiej strony elementy znajdujące się początkowo daleko na liście będą wymagały wielu operacji przemieszczenia, zanim znajdą się na początku listy.
K01: p ← L.head ; przeszukiwanie rozpoczynamy ; od pierwszego elementu na liście K02: Jeśli p = nil, to idź do kroku K09 K03: Jeśli p→data ≠ v, ; sprawdzamy, czy p to idź do kroku K07 ; wskazuje poszukiwany element K04: Odłącz p od listy ; jeśli tak, to zamieniamy ; go z poprzedzającym K05: Wstaw p przed jego poprzednik K06: Idź do kroku K09 K07: p ← p→next ; przechodzimy do następnego elementu K08: Idź do kroku K02 ; i kontynuujemy pętlę K09: Zakończ z wynikiem p
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 uporządkowaną listę dwukierunkową 10 liczb całkowitych od 0 do 9. Następnie dokonuje 40 wyszukiwań liczb losowych od 0 do 9, wypisując za każdym razem stan listy. W programie wykorzystujemy zmodyfikowany obiekt listy dwukierunkowej. |
Pascal// Samoorganizujące się listy // Wyszukiwanie z przemieszczaniem // Data: 04.08.2012 // (C)2020 mgr Jerzy Wałaszek //-------------------------------- program transpose; // Typ elementów listy //-------------------- type PDLel = ^DLel; DLel = record next : PDLel; prev : PDLel; data : integer; end; // Definicja typu obiektowego DLvar //------------------------------------ DLvar = object public head : PDLel; // początek listy tail : PDLel; // koniec listy constructor init; destructor destroy; procedure l_print(v : integer); procedure l_push_front(v : integer); procedure l_pop_front; function l_find(v : integer) : PDLel; end; //------------------------ // Metody obiektu DLvar //------------------------ // Konstruktor-inicjuje pole head //--------------------------------- constructor DLvar.init; begin head := nil; tail := nil; end; // Destruktor-usuwa listę z pamięci //----------------------------------- destructor DLvar.destroy; begin while head <> nil do l_pop_front; end; // Procedura wyświetla zawartość elementów listy //---------------------------------------------- procedure DLvar.l_print(v : integer); var p : PDLel; begin p := head; while p <> nil do begin if p^.data = v then write ('(', p^.data, ')') else write (' ', p^.data, ' '); p := p^.next; end; writeln; end; // Procedura dołączania 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; if p^.next <> nil then p^.next^.prev := p else tail := p; end; // Procedura usuwa pierwszy element //--------------------------------- procedure DLvar.l_pop_front; var p : PDLel; begin p := head; if p <> nil then begin head := p^.next; if head = nil then tail := nil else head^.prev := nil; dispose(p); end; end; // Funkcja wyszukuje element i zamienia go z poprzednikiem //-------------------------------------------------------- function DLvar.l_find(v : integer) : PDLel; var p : PDLel; begin p := head; // rozpoczynamy od początku listy while p <> nil do // w pętli przeszukujemy listę begin if p^.data = v then // element znaleziony? begin // odłączamy element od listy if p^.prev <> nil then p^.prev^.next := p^.next else break; if p^.next <> nil then p^.next^.prev := p^.prev else tail := p^.prev; // umieszczamy go przed poprzednikiem p^.next := p^.prev; p^.prev := p^.next^.prev; p^.next^.prev := p; if p^.prev <> nil then p^.prev^.next := p else head := p; break; end; p := p^.next; // przechodzimy do następnego elementu end; // kontynuujemy pętlę l_find := p; // zwracamy wynik p end; //--------------- // Program główny //--------------- var L : DLvar; // obiekt listy i, v : integer; begin Randomize; // inicjujemy liczby pseudolosowe L.init; // inicjujemy obiekt for i := 9 downto 0 do L.l_push_front(i); // tworzymy listę write(' '); L.l_print(10); // lista początkowa for i := 1 to 40 do // 40 razy wyszukujemy element begin v := random(10); // losujemy elementy 0…9 write(v, ': '); // wyświetlamy wylosowany element L.l_find(v); // wyszukujemy element L.l_print(v); // wyświetlamy listę end; L.destroy; end. |
// Samoorganizujące się listy // Wyszukiwanie z przemieszczaniem // Data: 04.08.2012 // (C)2020 mgr Jerzy Wałaszek //-------------------------------- #include <iostream> #include <cstdlib> #include <ctime> 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 DLvar(); // konstruktor ~DLvar(); // destruktor void l_print(int v); void l_push_front(int v); void l_pop_front(); DLel * l_find(int v); }; //------------------------------------ // Metody obiektu listy dwukierunkowej //------------------------------------ // Inicjuje pola zmiennej listy //----------------------------- DLvar::DLvar() { head = tail = NULL; } // Usuwa listę z pamięci //---------------------- DLvar::~DLvar() { while(head) l_pop_front(); } // Procedura wyświetla zawartość listy //------------------------------------ void DLvar::l_print(int v) { for(DLel * p = head; p; p = p->next) if(p->data == v) cout << "(" << p->data << ")"; else cout << " " << p->data << " "; cout << endl; } // Procedura dołączania na początek listy //--------------------------------------- void DLvar::l_push_front(int v) { DLel * p = new DLel; // tworzymy nowy element p->data = v; p->prev = NULL; p->next = head; head = p; if(p->next) p->next->prev = p; else tail = p; } // Procedura usuwa pierwszy element //--------------------------------- void DLvar::l_pop_front() { if(DLel * p = head) { if(!(head = p->next)) tail = NULL; else head->prev = NULL; delete p; } } // Funkcja wyszukuje element // i zamienia go z poprzednikiem //------------------------------ DLel * DLvar::l_find(int v) { DLel *p; for(p = head; p; p = p->next) // w pętli przeszukujemy listę if(p->data == v) // element znaleziony? { // odłączamy element od listy if(p->prev) p->prev->next = p->next; else break; if(p->next) p->next->prev = p->prev; else tail = p->prev; // umieszczamy go przed poprzednikiem p->next = p->prev; p->prev = p->next->prev; p->next->prev = p; if(p->prev) p->prev->next = p; else head = p; break; } return p; // zwracamy wynik p } //--------------- // Program główny //--------------- int main() { DLvar L; // obiekt listy int i, v; srand (time(NULL)); // inicjujemy liczby pseudolosowe for(i = 9; i >= 0; i--) L.l_push_front(i); // tworzymy listę cout << " "; L.l_print(10); // lista początkowa for(i = 0; i < 40; i++) // 40 razy wyszukujemy element { v = rand()%10; // losujemy elementy 0…9 cout << v << ": "; // wyświetlamy wylosowany element L.l_find(v); // wyszukujemy element L.l_print(v); // wyświetlamy listę } return 0; } |
Basic' Samoorganizujące się listy ' Wyszukiwanie z przemieszczaniem ' Data: 04.08.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 ' Definicja obiektu listy dwukierunkowej '--------------------------------------- Type DLvar head As DLel Ptr ' początek listy tail As DLel Ptr ' koniec listy Declare Constructor() Declare Destructor() Declare Sub l_print(v As Integer) Declare Sub l_push_front(v As Integer) Declare Sub l_pop_front() Declare Function l_find(v As Integer)_ As DLel Ptr End Type '--------------- ' Program główny '--------------- Dim L As DLvar Dim As Integer i, v Randomize For i = 9 To 0 Step -1 L.l_push_front(i) ' tworzymy listę Next Print " "; L.l_print(10) ' lista początkowa For i = 1 to 40 ' 40 razy wyszukujemy element v = Int(Rnd()*10) ' losujemy elementy 0…9 Print Using "#: ";v; ' wyświetlamy wylosowany element L.l_find(v) ' wyszukujemy element L.l_print(v) ' wyświetlamy listę Next End '------------------------------------ ' Metody obiektu listy dwukierunkowej '------------------------------------ ' Konstruktor listy '------------------ Constructor DLvar() head = 0 tail = 0 End Constructor ' Usuwa listę z pamięci Destructor DLvar() While head l_pop_front() Wend End Destructor ' Procedura wyświetla zawartość listy '------------------------------------ Sub DLvar.l_print(v As Integer) Dim p As DLel Ptr p = head While p if p->data = v Then Print Using "(#)";p->data; Else Print Using " # ";p->data; End If p = p->Next Wend Print End Sub ' Procedura dołączania 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 If p->next Then p->next->prev = p Else tail = p End If End Sub ' Procedura usuwa pierwszy element '--------------------------------- Sub DLvar.l_pop_front() Dim p As DLel Ptr p = head If p Then head = p->next If head = 0 Then tail = 0 Else head->prev = 0 End If Delete p End If End Sub ' Funkcja wyszukuje element ' i zamienia go z poprzednikiem '------------------------------ Function DLvar.l_find(v As integer)_ As DLel Ptr Dim As DLel Ptr p p = head While p ' w pętli przeszukujemy listę If p->data = v Then ' element znaleziony? ' odłączamy element od listy If p->prev Then p->prev->next = p->Next Else Exit While End If If p->next Then p->next->prev = p->prev Else tail = p->prev End If ' umieszczamy go przed poprzednikiem p->next = p->prev p->prev = p->next->prev p->next->prev = p If p->prev Then p->prev->next = p Else head = p End If Exit While End If p = p->next Wend Return p ' zwracamy wynik p End Function |
Python (dodatek) |
# Samoorganizujące się listy # Wyszukiwanie z przemieszczaniem # Data: 02.06.2024 # (C)2024 mgr Jerzy Wałaszek # -------------------------------- import random # 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 # Destructor def __del__(self): while self.head: self.l_pop_front() # Wyświetla zawartość elementów listy def l_print(self, v): p = self.head while p: if p.data == v: print("(%1d)" % p.data, end="") else: print(" %1d " % 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 if p.next: p.next.prev = p else: self.tail = p # Usuwa pierwszy element def l_pop_front(self): p = self.head if p: self.head = p.next if not self.head: self.tail = None else: self.head.prev = None p = None # Funkcja wyszukuje element # i zamienia go z poprzednikiem # ------------------------------ def l_find(self, v): p = self.head while p: # w pętli przeszukujemy listę if p.data == v: # element znaleziony? # odłączamy element od listy if p.prev: p.prev.next = p.next else: break if p.next: p.next.prev = p.prev else: self.tail = p.prev # umieszczamy go przed poprzednikiem p.next = p.prev p.prev = p.next.prev p.next.prev = p if p.prev: p.prev.next = p else: self.head = p break p = p.next # --------------- # Program główny # --------------- dl = DLvar() for i in reversed(range(10)): # tworzymy listę dl.l_push_front(i) print(" ", end="") # lista początkowa dl.l_print(10) # 40 razy wyszukujemy element for i in range(40): # losujemy elementy 0…9 v = random.randrange(10) # wyświetlamy wylosowany element print("%1d: " % v, end="") # wyszukujemy element dl.l_find(v) # wyświetlamy listę dl.l_print(v) |
Wynik: |
0 1 2 3 4 5 6 7 8 9 5: 0 1 2 3 (5) 4 6 7 8 9 8: 0 1 2 3 5 4 6 (8) 7 9 6: 0 1 2 3 5 (6) 4 8 7 9 6: 0 1 2 3 (6) 5 4 8 7 9 2: 0 (2) 1 3 6 5 4 8 7 9 6: 0 2 1 (6) 3 5 4 8 7 9 2: (2) 0 1 6 3 5 4 8 7 9 6: 2 0 (6) 1 3 5 4 8 7 9 5: 2 0 6 1 (5) 3 4 8 7 9 4: 2 0 6 1 5 (4) 3 8 7 9 9: 2 0 6 1 5 4 3 8 (9) 7 8: 2 0 6 1 5 4 (8) 3 9 7 0: (0) 2 6 1 5 4 8 3 9 7 0: (0) 2 6 1 5 4 8 3 9 7 8: 0 2 6 1 5 (8) 4 3 9 7 5: 0 2 6 (5) 1 8 4 3 9 7 8: 0 2 6 5 (8) 1 4 3 9 7 9: 0 2 6 5 8 1 4 (9) 3 7 4: 0 2 6 5 8 (4) 1 9 3 7 4: 0 2 6 5 (4) 8 1 9 3 7 5: 0 2 (5) 6 4 8 1 9 3 7 2: (2) 0 5 6 4 8 1 9 3 7 0: (0) 2 5 6 4 8 1 9 3 7 2: (2) 0 5 6 4 8 1 9 3 7 3: 2 0 5 6 4 8 1 (3) 9 7 7: 2 0 5 6 4 8 1 3 (7) 9 8: 2 0 5 6 (8) 4 1 3 7 9 4: 2 0 5 6 (4) 8 1 3 7 9 3: 2 0 5 6 4 8 (3) 1 7 9 5: 2 (5) 0 6 4 8 3 1 7 9 7: 2 5 0 6 4 8 3 (7) 1 9 7: 2 5 0 6 4 8 (7) 3 1 9 1: 2 5 0 6 4 8 7 (1) 3 9 6: 2 5 (6) 0 4 8 7 1 3 9 4: 2 5 6 (4) 0 8 7 1 3 9 8: 2 5 6 4 (8) 0 7 1 3 9 9: 2 5 6 4 8 0 7 1 (9) 3 3: 2 5 6 4 8 0 7 1 (3) 9 8: 2 5 6 (8) 4 0 7 1 3 9 0: 2 5 6 8 (0) 4 7 1 3 9 |
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.