Wykład czwarty: Jednokierunkowa lista liniowa
Transkrypt
Wykład czwarty: Jednokierunkowa lista liniowa
Podstawy Programowania semestr drugi Wykład trzeci 1. Jednokierunkowa lista liniowa Jednokierunkowa lista liniowa jest abstrakcyjną strukturą danych przechowującą elementy w określonym liniowym porządku. Elementy można dodawać i usuwać z listy w dowolnym jej miejscu. Czas wykonania tych operacji jest stały i niezależny od ilości elementów w liście 1, natomiast czas wyszukania elementu jest proporcjonalny do ilości elementów umieszczonych na liście. 2. Implementacja listy jednokierunkowej jako dynamicznej struktury danych Lista jednokierunkowa zostanie opisana na przykładzie programu, który wstawia do listy kolejne elementy, tak aby znajdowały się w porządku niemalejącym. Można również użyć listy w inny sposób. Definicja typu pojedynczego elementu listy jest taka sama jak w przypadku stosu i kolejki (nazwy pól nie mają większego znaczenia). W przypadku list stosuje się zazwyczaj jeden wskaźnik, wskazujący na element początkowy listy. Podstawowymi operacjami dotyczącymi listy są operacje wstawiania i usuwania elementu, i usuwanie listy. Można również zdefiniować operację wyszukiwania elementu na liście spełniającego ustalone kryterium. Oto kod wspomnianego wyżej programu: Operacja wstawiania elementu na listę została „rozbita” na dwie procedury: zadaniem procedury „crate” sprawdza, czy istnieje pierwszy element na tej liście. Jeśli nie to tworzy go (wiersze 69 74) i kończy swoje działanie. Jeśli takowy element istnieje wywoływana jest procedura „insert_node”. Zadaniem tej procedury jest utworzenie nowego elementu listy i umieszczenie go w odpowiednim miejscu na tej liście. Procedura ta pobiera dwa parametry. Pierwszym jest wskaźnik na pierwszy element listy, a poprzez drugi przekazywana jest wartość, która zostanie umieszczona w elemencie. Posiada ona również trzy lokalne zmienne wskaźnikowe. Zmienna „p” będzie służyła do poruszania się po liście. Zmienna „prev” będzie wskazywała na element poprzedzający ten, który wskazuje „p”. Zmienna „nowy” wskazuje na nowy, utworzony przez procedurę element listy. W wierszach 21 26 obsługiwany jest przypadek, kiedy ze względu na przechowywaną wartość nowy element powinien zostać elementem początkowym listy. Wówczas w wierszu 23 w jego polu „next” zapisywany jest adres bieżącego pierwszego elementu listy, a następnie w wierszu 24 do wskaźnika „f” wskazującego na pierwszy element listy zapisywany jest adres nowego elementu. Jeśli jednak nowy element nie może być wstawiony przed pierwszym elementem, wówczas w pętli (wiersze 28 37) przeszukiwana jest cała lista, w celu znalezienia miejsca dla niego. Jeśli w całej liście nie ma elementu o większej wartości, to jest on dodawany na końcu listy i procedura kończy swoje działanie. W przeciwnym przypadku jest on wstawiany przed elementem o większej wartości. Ilustruje to następujący rysunek: 1 program single_linked_list; 2 uses crt; 3 type 4 wskaznik=^element; 5 element=record 6 dana:integer; 7 next:wskaznik; 8 end; 9 var 10 first:wskaznik; 11 ne:integer; 12 13 14 procedure insert_node(var f:wskaznik; a:integer); 15 var 16 prev,p,nowy:wskaznik; 17 begin 18 19 nowy^.dana:=a; 20 nowy^.next:=nil; 21 if f^.dana > a then 22 begin 23 nowy^.next:=f; 24 f:=nowy; 25 end; 27 p:=f; 28 while (p^.dana <= a) do 29 begin dana NIL 31 p:=p^.next; if p=nil then nowy prev p dana next dana next 2. Po wykonaniu wiersza 38 dana next prev dana next 3. Po wykonaniu wiersza 39 prev:=p; 32 33 1. Przed wykonaniem wiersza 38 exit; 26 30 1 new(nowy); dana next prev nowy p dana next nowy p begin 34 prev^.next:=nowy; 35 exit; dana next Tak też jest w przypadku stosu i kolejki, które implementowane są na bazie listy liniowej. 1 dana next Podstawy Programowania 36 37 Operacja usuwania elementu przechowującego określoną wartość jest zrealizowana w procedurze „delete_node” posiada ona dwa parametry. Przez pierwszy pobiera wskaźnik na pierwszy element na liście, natomiast przez drugi wartość, którą powinien przechowywać element do usunięcia. Procedura ma również zmienne lokalne, które pełnią taką samą rolę jak w procedurze „insert_node”. W wierszu 47 następuje sprawdzenie, czy nie powinien być usunięty pierwszy element. Jeśli tak, to odbywa się to w taki sam sposób, jak w przypadku procedury „dequeue”, w kolejce FIFO. W przeciwnym przypadku, podobnie jak w „insert_node” przeszukiwana jest iteracyjnie cała lista (wiersze 55 58). Po wyjściu z pętli zmienna „p” może mieć wartość „NIL”. Wówczas kończymy procedurę, bowiem na liście nie ma elementu, który chcielibyśmy usunąć. Jeśli jednak „p” jest różne od „NIL”, wówczas w wierszach 60 i 61 element wskazywany przez tę zmienną jest usuwany z listy. Można to zilustrować następującym rysunkiem: end; end; 38 nowy^.next:=prev^.next; 39 prev^.next:=nowy; semestr drugi 40 end; 41 42 procedure delete_node(var f:wskaznik; a:integer); 43 var 44 p,prev,tmp:wskaznik; 45 begin 46 47 if f^.dana=a then 48 1. Przed wykonaniem wiersza 60 prev begin 49 tmp:=f^.next; 50 dispose(f); 51 f:=tmp; 52 exit; 53 end; 54 p:=f; 55 repeat 56 57 p:=p^.next; until (p=nil) or (p^.dana=a); 59 if p=nil then exit; 60 prev^.next:=p^.next; 61 dispose(p); dana next 2. Po wykonaniu wiersza 60 prev prev:=p; 58 dana next dana next dana next p dana next dana next 3. Po wykonaniu wiersza 61 prev dana next p dana next p dana next 62 end; 63 64 procedure create(var f:wskaznik; a:integer); 65 var 66 nowy:wskaznik; Procedura „remove” ma tylko jeden parametr. Do jej wnętrza przekazywany jest przez zmienną wskaźnik na pierwszy element listy. Usuwa ona wszystkie elementy listy, z pierwszym elementem włącznie. 67 begin 68 if f=nil then 69 begin 70 new(nowy); 71 nowy^.next:=nil; 72 nowy^.dana:=a; 73 W bloku głównym programu tworzona jest lista, przy pomocy procedury „create” zawierająca elementy o wartościach od 1 do 5. Następnie dodawane są do niej (nadal przy użyciu „create”) elementy o wartościach od 10 do 15. Po wyświetleniu zawartości listy dodawany jest pojedynczy element o wartości 16, a więc on powinien się znaleźć na końcu listy, co można sprawdzić po kolejnym wypisaniu jej na ekran. Następnie do listy dodawany jest element o wartości 6, który powinien zostać umieszczony pomiędzy elementami 5 i 10. Lista jest ponownie wypisywana na ekran i dodawany jest do niej element o wartości 0, czyli musi on zostać wstawiony przed pierwszym elementem na liście. W wierszu 112 lista jest wypisywana na ekran. Powyższe czynności mają na celu pokazanie, że procedura „insert_node” jest napisana poprawnie. Następnie wywoływana jest procedura „delete_node”, która usuwa z listy element o wartości 0. f:=nowy; 74 75 Zasada działania procedury „show” jest taka sama, jak procedury „print_queue”, która służyła do wypisania zawartości kolejki. end else 76 insert_node(f,a); 77 end; 78 79 procedure remove(var f:wskaznik); 80 var 81 tmp:wskaznik; 2 Podstawy Programowania Aby być pewnym jej działania należałoby dopisać jeszcze jej wywołania dla ostatniego elementu i elementu znajdującego się „wewnątrz” listy. Na koniec usuwana jest cała lista i wypisywana jest dostępna pamięć, celem weryfikacji poprawności działania procedury „remove”. 81 tmp:wskaznik; 82 begin 83 while f <> nil do 84 begin 85 tmp:=f^.next; 86 dispose(f); 87 f:=tmp; 88 semestr drugi end; 89 end; 90 91 procedure show(f:wskaznik); 92 begin 93 while f <> nil do 94 begin 95 write(f^.dana:3); 96 f:=f^.next; 97 end; 98 writeln; 99 end; 100 101 begin 102 clrscr; 103 writeln('Dostępna pamięć: ',MemAvail); 104 for ne:=1 to 5 do create(first,ne); 105 for ne:=10 to 15 do create(first,ne); 106 show(first); 107 insert_node(first,16); 108 show(first); 109 insert_node(first,6); 110 show(first); 111 insert_node(first,0); 112 show(first); 113 delete_node(first,0); 114 show(first); 115 remove(first); 116 writeln('Dostępna pamięć: ',MemAvail); 117 readln; 118 end. 3. Uwagi końcowe Listy jednokierunkowe można również implementować jako listy z wartownikami. W takim wypadku tworzy się na początku działania programu jeden lub dwa elementy, które tworzą atrapy. Dzięki nim nie trzeba w procedurach usuwania i wstawiania elementów sprawdzać dodatkowych warunków. Możliwa jest również implementacja listy jednokierunkowej w oparciu o tablicę dwuwymiarową. W górnym wierszu tej tablicy mogą być zapisywane wartości elementów, natomiast w dolnym indeksy następnych w kolejności elementów. Taka implementacja jest jednak mniej wydajna niż implementacja tablicowa, szczególnie trudna do oprogramowania jest w niej operacja wstawiania elementu. Trzeba też ustalić jaka wartość indeksu będzie oznaczała, że dany element został usunięty z listy lub że jest elementem ostatnim na liście. Listy mają szerokie zastosowania. Są na przykład, podobnie jak kolejki, używane w systemach operacyjnych przez mechanizmy szeregowania zadań. 3