R1KSK8bTfIKoK
Fotografia przedstawia kartki samoprzylepne, doklejane do ściany.

I_R_W13A_M03_JAVA Zaawansowane  struktury danych

Źródło: Kelly Sikkema, domena publiczna.

Lista

Przypomnijmy, że lista – podobnie jak stos i kolejka – jest liniową i dynamiczną strukturą danych. Elementami listy są węzły, które zawierają dane oraz referencję na element następny (w przypadku listy jednokierunkowej) lub na następny i poprzedni (w przypadku listy dwukierunkowej).

Do implementacji stosu i kolejki użyliśmy listy jednokierunkowej. Jej węzły zdefiniowaliśmy przy użyciu klasy zawierającej dwa pola i konstruktor argumentowy:

Linia 1. class Wezel otwórz nawias klamrowy. Linia 2. int liczba średnik. Linia 3. Wezel nastepny średnik. Linia 5. prawy ukośnik prawy ukośnik Konstruktor argumentowy. Linia 6. Wezel otwórz nawias okrągły int liczba zamknij nawias okrągły otwórz nawias klamrowy. Linia 7. this kropka liczba znak równości liczba średnik. Linia 8. this kropka nastepny znak równości null średnik. Linia 9. zamknij nawias klamrowy. Linia 10. zamknij nawias klamrowy.

Konstruktor argumentowy wymaga podania argumentu podczas tworzenia obiektu, co pozwala zainicjować pole liczba otrzymaną wartością:

Linia 1. prawy ukośnik prawy ukośnik Wykorzystanie konstruktora argumentowego. Linia 2. Wezel wezel znak równości new Wezel otwórz nawias okrągły 1 zamknij nawias okrągły średnik.

Lista jednokierunkowa

Listę jednokierunkową implementujemy jako klasę z jednym polem glowa – wskazującym na pierwszy element listy. Oprócz tego w klasie definiujemy metody umożliwiające dodawanie, usuwanie i wypisywanie elementów listy. W konstruktorze Lista() inicjalizujemy pole glowa referencją pustą.

Linia 1. class Lista otwórz nawias klamrowy. Linia 2. Wezel glowa średnik. Linia 4. public Lista otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 5. glowa znak równości null średnik. Linia 6. zamknij nawias klamrowy. Linia 8. prawy ukośnik prawy ukośnik Miejsce na metody. Linia 9. zamknij nawias klamrowy.

Metody czyPusta() oraz wypisz() działają tak samo jak w przypadku stosu i kolejki. Wyjaśnimy działanie metody dodającej węzły na końcu listy oraz usuwającej węzły o podanej pozycji.

Dodawanie węzłów na końcu listy jednokierunkowej

Metoda dodaj() jako argument będzie przyjmowała liczbę całkowitą. Nowy węzeł – inaczej niż w przypadku stosu i kolejki – będzie dodawany na końcu listy.

Linia 1. public void dodaj otwórz nawias okrągły int liczba zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. System kropka out kropka println otwórz nawias okrągły cudzysłów Dodaję cudzysłów plus String kropka valueOf otwórz nawias okrągły liczba zamknij nawias okrągły zamknij nawias okrągły średnik. Linia 4. prawy ukośnik prawy ukośnik Tworzymy nowy węzeł. Linia 5. Wezel nowy znak równości new Wezel otwórz nawias okrągły liczba zamknij nawias okrągły średnik. Linia 7. prawy ukośnik prawy ukośnik Jeżeli lista jest pusta. Linia 8. if otwórz nawias okrągły czyPusta otwórz nawias okrągły zamknij nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 9. glowa znak równości nowy średnik. Linia 10. return średnik. Linia 11. zamknij nawias klamrowy. Linia 13. prawy ukośnik prawy ukośnik W przeciwnym razie przejdź do ostatniego elementu. Linia 14. Wezel pomocniczy znak równości glowa średnik. Linia 15. while otwórz nawias okrągły pomocniczy kropka nastepny wykrzyknik znak równości null zamknij nawias okrągły otwórz nawias klamrowy. Linia 16. pomocniczy znak równości pomocniczy kropka nastepny średnik. Linia 17. zamknij nawias klamrowy. Linia 19. prawy ukośnik prawy ukośnik Dodajemy nowy węzeł na końcu listy. Linia 20. pomocniczy kropka nastepny znak równości nowy średnik. Linia 21. zamknij nawias klamrowy.

Na początku metody tworzymy nowy węzeł, przekazując do jego konstruktora otrzymaną liczbę. Następnie, jeżeli lista jest pusta, nowy węzeł staje się pierwszym elementem listy przypisanym do pola glowa.

W przeciwnym razie za pomocą węzła pomocniczy w pętli przechodzimy listę do ostatniego elementu, którego referencja nastepny będzie pusta. Po zakończeniu pętli pole nastepny dotychczasowego ostatniego elementu ustawiamy na nowy węzeł. W ten sposób dodajemy węzeł na końcu listy.

R1eSTXiWk4HIn
Schemat dodawania węzła do końca listy jednokierunkowej
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Usuwanie węzłów z listy jednokierunkowej

Metoda usuwająca węzły z listy otrzymywać będzie jako argument liczbę całkowitą wskazującą indeks czy też numer węzła, który chcemy usunąć. Przyjmijmy, że indeksy węzłów zaczynają się od 1.

Linia 1. public void usun otwórz nawias okrągły int indeks zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. if otwórz nawias okrągły czyPusta otwórz nawias okrągły zamknij nawias okrągły zamknij nawias okrągły return średnik. Linia 4. System kropka out kropka println otwórz nawias okrągły cudzysłów Usuwam pozycję dwukropek cudzysłów plus Integer kropka toString otwórz nawias okrągły indeks zamknij nawias okrągły zamknij nawias okrągły średnik. Linia 6. Wezel pomocniczy1 znak równości glowa przecinek pomocniczy2 znak równości null średnik. Linia 7. int rozmiar znak równości 0 średnik. Linia 9. prawy ukośnik prawy ukośnik Zliczamy elementy listy. Linia 10. while otwórz nawias okrągły pomocniczy1 wykrzyknik znak równości null zamknij nawias okrągły otwórz nawias klamrowy. Linia 11. pomocniczy1 znak równości pomocniczy1 kropka nastepny średnik. Linia 12. rozmiar plus plus średnik. Linia 13. zamknij nawias klamrowy. Linia 14. prawy ukośnik prawy ukośnik Jeżeli indeks jest większy od rozmiaru listy. Linia 15. if otwórz nawias okrągły indeks zamknij nawias ostrokątny rozmiar zamknij nawias okrągły otwórz nawias klamrowy. Linia 16. System kropka out kropka println otwórz nawias okrągły cudzysłów Indeks poza zakresem kropka cudzysłów zamknij nawias okrągły średnik. Linia 17. return średnik. Linia 18. zamknij nawias klamrowy. Linia 20. prawy ukośnik prawy ukośnik Poniżej dodamy kod usuwający elementy. Linia 22. zamknij nawias klamrowy.

Jeżeli lista nie jest pusta, definiujemy zmienne, których używamy w metodzie:

  • referencję pomocniczy1 ustawiamy na pierwszy element listy,

  • referencję pomocniczy2 wykorzystamy przy usuwaniu węzłów innych od pierwszego,

  • zmienna rozmiar posłuży do ustalenia rozmiaru listy.

Następnie zliczamy elementy listy za pomocą pętli while przechodzącej po wszystkich węzłach listy, zaczynając od pierwszego. Po dojściu do ostatniego elementu w zmiennej pomocniczy1 zostanie zapisana wartość null i pętla zakończy swoje działanie.

Ważne!

Warto zapamiętać omówioną pętlę, ponieważ przechodzenie wszystkich elementów listy, zaczynając od pierwszego, jest typową operacją wykonywaną na listach.

Następnie, jeżeli podany indeks nie jest większy od obliczonego rozmiaru listy, zaczynamy usuwanie węzła. Zadanie to realizuje kod, który umieszczamy na końcu omawianej metody:

Linia 1. prawy ukośnik prawy ukośnik Usuwanie pierwszego węzła. Linia 2. if otwórz nawias okrągły indeks znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 3. glowa znak równości glowa kropka nastepny średnik. Linia 4. return średnik. Linia 5. zamknij nawias klamrowy. Linia 7. prawy ukośnik prawy ukośnik Szukanie węzła do usunięcia otwórz nawias okrągły innego niż pierwszy zamknij nawias okrągły. Linia 8. pomocniczy1 znak równości glowa średnik. Linia 9. while otwórz nawias okrągły indeks minus minus zamknij nawias ostrokątny 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 10. pomocniczy2 znak równości pomocniczy1 średnik. Linia 11. pomocniczy1 znak równości pomocniczy1 kropka nastepny średnik. Linia 12. zamknij nawias klamrowy. Linia 14. prawy ukośnik prawy ukośnik Zmiana pola nastepny węzła poprzedzającego usuwany. Linia 15. pomocniczy2 kropka nastepny znak równości pomocniczy1 kropka nastepny średnik.

Jeżeli usuwanym elementem jest pierwszy węzeł (indeks 1), pole glowa ustawiamy na następny węzeł, co powoduje wyłączenie dotychczasowego pierwszego elementu z listy.

W przeciwnym razie w pętli, która wykonuje się, dopóki indeks jest większy od 1, szukamy węzła do usunięcia. W każdej iteracji indeks jest zmniejszany o 1 dzięki notacji postfiksowej. Wewnątrz pętli przechodzimy po indeks - 1 elementach listy od jej początku, więc po jej zakończeniu zmienna pomocniczy1 będzie wskazywała węzeł do usunięcia, a zmienna pomocniczy2 węzeł poprzedni.

Po zakończeniu pętli w węźle poprzedzającym węzeł usuwany ustawiamy referencję nastepny na węzeł następny po węźle usuwanym (w przypadku usuwania ostatniego węzła będzie to wartość null).

Dla zainteresowanych

W językach programowania Java i Python nie trzeba usuwać węzłów i zwalniać zajmowanych przez niego pamięci za pomocą osobnej instrukcji takiej jak np. wyrażenie delete() w języku C++. Jeżeli na jakiś obiekt nie wskazują żadne referencje, zostanie on usunięty przez mechanizm odśmiecania pamięci (ang. garbage collection).

R1DT71fUbiSfA
Schemat usuwania węzła z listy jednokierunkowej
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.
Ćwiczenie 1

Wykorzystaj omówiony kod i przygotuj program pozwalający sprawdzić działanie listy jednokierunkowej. Dodaj do listy trzy liczby i wypisz listę. Następnie usuń drugi element i wypisz listę. Na końcu jeszcze raz usuń drugi element, dodaj liczbę i wypisz listę.

R1Og0RiM25SjP
2

Lista dwukierunkowa

Lista dwukierunkowa od jednokierunkowej różni się tym, że jej węzły – oprócz referencji na element następny – zawierają także referencję na element poprzedni. Implementację przygotujemy, korzystając z omówionego kodu listy jednokierunkowej.

Na początku uzupełniamy klasę Wezel o pole poprzedni i uzupełniamy konstruktor tak, aby inicjalizował je wartością pustą. Do klasy Lista dodajemy pole ogon, które będzie wskazywało ostatni element listy. W konstruktorze klasy pola glowaogon ustawiamy na wartości puste.

Linia 1. class Wezel otwórz nawias klamrowy. Linia 2. int liczba średnik. Linia 3. Wezel nastepny średnik. Linia 4. Wezel poprzedni średnik. Linia 6. Wezel otwórz nawias okrągły int liczba zamknij nawias okrągły otwórz nawias klamrowy. Linia 7. this kropka liczba znak równości liczba średnik. Linia 8. this kropka nastepny znak równości null średnik. Linia 9. this kropka poprzedni znak równości null średnik. Linia 10. zamknij nawias klamrowy. Linia 11. zamknij nawias klamrowy. Linia 13. class Lista otwórz nawias klamrowy. Linia 14. Wezel glowa średnik. Linia 15. Wezel ogon średnik. Linia 17. public Lista otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 18. glowa znak równości ogon znak równości null średnik. Linia 19. zamknij nawias klamrowy. Linia 21. prawy ukośnik prawy ukośnik Poniżej dodamy kolejne metody. Linia 22. zamknij nawias klamrowy.

Pole ogon, tj. referencja na ostatni element listy, jest opcjonalne – może występować zarówno w listach jedno-, jak i dwukierunkowych. Używa się go m.in. do szybkiego dodawania elementów na końcu listy bez konieczności przechodzenia po wszystkich jej elementach w celu znalezienia ostatniego.

Dodawanie węzłów na końcu listy dwukierunkowej

Metody czyPusta() oraz wypisz() pozostają bez zmian. Modyfikacji wymagają tylko implementacje metod dodaj()usun(). Zacznijmy od metody dodającej nowe węzły:

Linia 1. public void dodaj otwórz nawias okrągły int liczba zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. System kropka out kropka println otwórz nawias okrągły cudzysłów Dodaję cudzysłów plus String kropka valueOf otwórz nawias okrągły liczba zamknij nawias okrągły zamknij nawias okrągły średnik. Linia 4. Wezel nowy znak równości new Wezel otwórz nawias okrągły liczba zamknij nawias okrągły średnik. Linia 6. prawy ukośnik prawy ukośnik jeżeli lista jest pusta. Linia 7. if otwórz nawias okrągły czyPusta otwórz nawias okrągły zamknij nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 8. glowa znak równości ogon znak równości nowy średnik. Linia 9. return średnik. Linia 10. zamknij nawias klamrowy. Linia 12. prawy ukośnik prawy ukośnik dodanie nowego węzła na końcu listy. Linia 13. ogon kropka nastepny znak równości nowy średnik. Linia 14. nowy kropka poprzedni znak równości ogon średnik. Linia 15. ogon znak równości nowy średnik. Linia 16. zamknij nawias klamrowy.

Różnice w porównaniu do implementacji tej metody w przypadku listy jednokierunkowej są następujące:

  • ustawienia pola ogon na nowy węzeł w przypadku dodawania pierwszego elementu listy,

  • wykorzystanie pola ogon – zamiast pętli przechodzącej wszystkie elementy listy – do wskazania ostatniego węzła listy,

  • ustawienie referencji nastepny dotychczasowego ostatniego węzła na nowy oraz poprzedni nowego węzła na dotychczasowy ostatni,

  • ustawienie pola ogon na nowo dodany element.

RlhFi1x6RJLwH
Schemat dodawania węzła do końca listy dwukierunkowej
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Usuwanie węzłów z listy dwukierunkowej

Metoda usuwająca elementy z podanej pozycji nie wymaga zmian, lecz uzupełnienia. Jeżeli usuwamy element niebędący ani pierwszym, ani ostatnim, musimy odpowiednio ustawić referencję poprzedni węzła występującego po węźle usuwanym. W przeciwnym razie, jeżeli usuwamy ostatni element, musimy zaktualizować pole ogon, aby wskazywało na dotychczasowy węzeł przedostatni. Poniżej kod uzupełnionej metody usun(). W komentarzu zaznaczyliśmy instrukcję warunkową realizującą omówione operacje:

Linia 1. public void usun otwórz nawias okrągły int indeks zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. if otwórz nawias okrągły czyPusta otwórz nawias okrągły zamknij nawias okrągły zamknij nawias okrągły return średnik. Linia 4. System kropka out kropka println otwórz nawias okrągły cudzysłów Usuwam pozycję dwukropek cudzysłów plus Integer kropka toString otwórz nawias okrągły indeks zamknij nawias okrągły zamknij nawias okrągły średnik. Linia 6. Wezel pomocniczy1 znak równości glowa przecinek pomocniczy2 znak równości null średnik. Linia 7. int rozmiar znak równości 0 średnik. Linia 9. while otwórz nawias okrągły pomocniczy1 wykrzyknik znak równości null zamknij nawias okrągły otwórz nawias klamrowy. Linia 10. pomocniczy1 znak równości pomocniczy1 kropka nastepny średnik. Linia 11. rozmiar plus plus średnik. Linia 12. zamknij nawias klamrowy. Linia 14. if otwórz nawias okrągły indeks zamknij nawias ostrokątny rozmiar zamknij nawias okrągły otwórz nawias klamrowy. Linia 15. System kropka out kropka println otwórz nawias okrągły cudzysłów Indeks poza zakresem kropka cudzysłów zamknij nawias okrągły średnik. Linia 16. return średnik. Linia 17. zamknij nawias klamrowy. Linia 19. if otwórz nawias okrągły indeks znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 20. glowa znak równości glowa kropka nastepny średnik. Linia 21. return średnik. Linia 22. zamknij nawias klamrowy. Linia 24. pomocniczy1 znak równości glowa średnik. Linia 25. while otwórz nawias okrągły indeks minus minus zamknij nawias ostrokątny 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 26. pomocniczy2 znak równości pomocniczy1 średnik. Linia 27. pomocniczy1 znak równości pomocniczy1 kropka nastepny średnik. Linia 28. zamknij nawias klamrowy. Linia 30. pomocniczy2 kropka nastepny znak równości pomocniczy1 kropka nastepny średnik. Linia 32. prawy ukośnik prawy ukośnik Kod obsługujący listę dwukierunkową. Linia 33. if otwórz nawias okrągły pomocniczy1 kropka nastepny wykrzyknik znak równości null zamknij nawias okrągły otwórz nawias klamrowy. Linia 34. prawy ukośnik prawy ukośnik Jeżeli usuwany węzeł nie jest ostatni. Linia 35. pomocniczy1 kropka nastepny kropka poprzedni znak równości pomocniczy2 średnik. Linia 36. zamknij nawias klamrowy else otwórz nawias klamrowy. Linia 37. prawy ukośnik prawy ukośnik Jeżeli usuwany element jest ostatni. Linia 38. ogon znak równości pomocniczy2 średnik. Linia 39. zamknij nawias klamrowy. Linia 40. zamknij nawias klamrowy.
R50oTO05RitRz
Schemat usuwania węzła z listy dwukierunkowej
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.
Ćwiczenie 2

Wykorzystaj omówiony kod i przygotuj program pozwalający sprawdzić działanie listy dwukierunkowej. Dodaj do listy trzy liczby i wypisz listę. Następnie usuń pierwszy, drugi i trzeci element. Dodaj do listy jeszcze jedną liczbę i wypisz listę.

RUHeK9ZkbEYI9
2