Temat: Zmienne dynamiczne – tablica wskaźników i stos
Transkrypt
Temat: Zmienne dynamiczne – tablica wskaźników i stos
Instrukcja Podstawy programowania 2 laboratoryjna Temat: Zmienne dynamiczne – tablica wskaźników i stos dynamiczny 2 Przygotował: mgr inż. Tomasz Michno 1 Wstęp teoretyczny 1.1 Tablice wskaźników Tablice wskaźników, są zwykłymi tablicami, które zamiast zmiennych przechowują wskaźniki. Sposób ich deklaracji jest identyczny do deklaracji zwykłych tablic (typ należy poprzedzić znakiem ^): nazwa : Array[od..do] of ^typ_wskaźnika; Tablice wskaźników mogą być wykorzystane, np. gdy mamy zamiar przechowywać dane, które mogą zająć dużo pamięci operacyjnej lub nie będą potrzebne przez cały czas działania programu. 1.2 Stos Stos jest jedną z najprostszych i najczęściej wykorzystywanych struktur danych, służy do przechowywania danych. Dane są w nim odkładane „na sobie”, co powoduje że można pobrać jedynie element znajdujący się na szczycie stosu (tzn. aby pobrać element znajdujący się na dnie stosu, musimy zdjąć wszystkie elementy). Odkładanie danych na stos (Instrukcja push): Przypuśćmy, że chcemy odłożyć na stosie 3 elementy: A, 0 i @. 1. Stos jest pusty. Odkładamy element A (pierwsza instrukcja push) A Stos 2. Odkładamy element 0 (druga instrukcja push) 0 @ 0 A @ Stos 3. Odkładamy element @ (trzecia instrukcja push): 0 A @ Stos Stos po odłożeniu wszystkich trzech elementów (można zauważyć, że dane są ułożone „na sobie”): @ 0 A Stos Pobieranie ze stosu (instrukcja pop): Chcemy teraz pobrać ze stosu element 0, zostawiając na nim resztę elementów. Ponieważ jest to stos, mamy jedynie dostęp do elementu znajdującego się na szczycie stosu. Dlatego w celu pobrania elementu 0 musimy najpierw zdjąć ze stosu element @. 1. Zdejmujemy ze stosu element @ (instrukcja pop), który w przypadku programu należałoby zapamiętać np. w zmiennej tymczasowej: 0 A Stos @ 2. Zdejmujemy ze stosu element 0 – element o który nam chodziło (instrukcja pop): 0 A @ Stos 3. Ponieważ chcemy resztę elementów stosu pozostawić bez zmian, musimy na niego z powrotem odłożyć element @ (instrukcja push): A @ Stos Stos po wszystkich operacjach wygląda następująco: @ A Stos Jak można było zauważyć, w stosie nie ma możliwości bezpośredniego dostępu do danych znajdujących się pod szczytowym elementem. Szczegóły implementacyjne: Stos można zaimplementować na dwa sposoby: używając tablicy lub używając specjalnie stworzonej struktury dynamicznej. Stos oparty o tablicę: Stos oparty o tablicę jest najprostszą implementacją stosu, posiada jednak wadę w postaci stałego rozmiaru (którym jest rozmiar tablicy) oraz gorszego zarządzania pamięcią w porównaniu z w pełni dynamicznym stosem (tablica łącznie ze wszystkimi jej elementami zajmuje pamięć nawet gdy stos jest pusty). Struktura takiego stosu jest bardzo prosta – składa się z tablicy oraz zmiennej przechowującej indeks ostatnio dodanego elementu (szczytu stosu). Na przykładzie 1 został przedstawiony stos przechowujący znaki. Dla większej czytelności i wygody używania został zadeklarowany typ rekordowy STOS, w którego środku znajduje się tablica i zmienna wskazująca na szczyt stosu. Pełny kod przykładu został przedstawiony w oddzielnym pliku. Przykład 1. type STOS = record stosTab : Array [1..STOS_MAX] of char; {Na stosie bedziemy przechowywali znaki} szczytStosu : integer; {przechowuje indeks elementu, ktory jest na szczycie stosu, 0 oznacza, ze stos jest pusty} end; Działanie instrukcji push() dla stosu tablicowego: 1. sprawdź, czy nie osiągnięto maksymalnej pojemności (STOS_MAX) (jeśli tak, zakończ) 2. zwiększ wartość zmiennej szczytStosu (jest to indeks w tablicy) 3. zapamiętaj w stosTab[szczytStosu] wartość zmiennej do odłożenia na stos. Działanie instrukcji pop() dla stosu tablicowego: 1. sprawdź, czy stos nie jest pusty (sprawdź, czy wartość zmiennej szczytStosu nie jest mniejsza niż minimalny indeks w tablicy – w tym przykładzie wartość 1) (jeśli tak, zakończ) 2. zapamiętaj w dodatkowej zmiennej wartość stosTab[szczytStosu] 3. zmniejsz wartość zmiennej szczytStosu o 1 Wynik działania: Na rysunku można zauważyć typowe działanie stosu – na stos odłożyliśmy napis PASCAL, natomiast po zdjęciu go ze stosu otrzymaliśmy odwrócenie: LACSAP. Jest to jeden z przykładów zastosowania stosu – odwracanie napisów lub tablic. Maksymalny rozmiar stosu został ustawiony na 6 elementów, dlatego znak '!' nie mógł być już dodany. Stos w wersji dynamicznej: Stos budowany w oparciu o struktury dynamiczne jest zazwyczaj o wiele lepszym rozwiązaniem niż stos stworzony z użyciem tablicy. Dzięki nieznacznie bardziej skomplikowanej implementacji otrzymujemy dużo większą kontrolę nad zużyciem pamięci (gdy stos jest pusty praktycznie nie zajmuje pamięci). Dodatkowo wielkość stosu jest ograniczona jedynie poprzez ilość wolnej pamięci, w przeciwieństwie do wersji tablicowej. W przykładzie 2 została przedstawiona struktura stosu przechowującego znaki (podobnie jak w poprzednim przykładzie został stworzony nowy typ). Pełny kod przykładu został przedstawiony w oddzielnym pliku. Przykład 2. type WSKDSTOS = ^DSTOS; DSTOS = record {typ rekordowy stosu} znak : char; {przechowywany typ - znaki} wskPonizej : WSKDSTOS; {wskaznik na element znajdujacy sie ponizej w stosie} end; Typ DSTOS jest zwykłym rekordem, który służy do przechowywania pojedynczego elementu stosu. Każdy element składa się z dwóch zmiennych: przechowywanego znaku oraz wskaźnika na element znajdujący się bezpośrednio pod nim (w przypadku tablicy byłby to element o indeksie mniejszym o 1). Dzięki wskaźnikowi otrzymujemy powiązanie pomiędzy wszystkimi elementami odłożonymi na stosie bez użycia tablicy. Jak można zauważyć na poniższym rysunku, pod elementem znajdującym się na dnie stosu nie ma już żadnego innego elementu i jego wskaźnik ma wartość nil1. @ wskaźnik 0 wskaźnik A wskaźnik nil Stos Oznaczenia: szczyt stosu – wskaźnik na element, który znajduje się na szczycie stosu. Dzięki zaproponowanej strukturze jest to jedyny wskaźnik, jaki musimy posiadać dla stosu, aby z niego korzystać. Działanie instrukcji push(): 1. utwórz nowy element 2. zapisz w element^.znak znak do odłożenia na stos 3. ustaw wskaźnik element^.wskPonizej na aktualny szczyt stosu 4. przypisz do wskaźnika szczyt stosu wskaźnik na nowo utworzony element Działanie instrukcji pop() dla stosu tablicowego: 1. sprawdź, czy stos nie jest pusty (sprawdź czy szczyt stosu nie jest równy nil) (jeśli tak, zakończ) 2. odczytaj/zapamiętaj w dodatkowej zmiennej wartość znaku z szczyt stosu^.znak 3. zapamiętaj wskaźnik na element poniżej szczytu stosu (szczyt stosu^.wskPonizej) 4. usuń element wskazywany przez szczyt stosu (Dispose(szczyt stosu)) 5. do wskaźnika szczyt stosu przypisz zapamiętany wskaźnik z punktu 3. Wynik działania programu jest praktycznie identyczny do wyniku z przykładu 1. Jedyną różnicą jest to, że było możliwe dodanie znaku '!' (dzięki rezygnacji z tablicy i jej stałego rozmiaru). 1 nil jest to wartość w języku Pascal, która informuje że wskaźnik na nic nie wskazuje 2 Zadania 1. Uruchom przykładowe programy i prześledź sposób ich działania. Napisz z wykorzystaniem stosu dynamicznego program, który będzie odwracał ciąg liczb podanych przez użytkownika (aplikacja ma wyświetlać odwrócony ciąg). 2. Dany jest zestaw informacji o n studentach: nazwisko, imię, rok urodzenia, średnia ocen. Napisać program, który umożliwi umieszczenie tych danych w pamięci komputera w taki sposób, że dostęp do nich będzie się odbywał za pośrednictwem tablicy zawierającej wskaźniki do informacji o poszczególnych osobach. 3. Uzupełnić program z punktu 1 o funkcję wyznaczającą średnią arytmetyczną ocen wszystkich wprowadzonych studentów oraz o procedurę uwalniającą pamięć zajętą przez dane studentów. Sprawdzić dostępność danych po odblokowaniu przypisanej im pamięci. 4. Zmodyfikować rozwiązanie zadania z punktu 1 poprzez wykorzystanie stosu dynamicznego do przechowywania danych. 5. Uzupełnić program z punktu 3 o procedurę wyświetlającą zawartość stosu bez jego likwidacji.