Jednym z najważniejszych typów danych obsługujących dołączanie I usuwanie obiektów ze zbioru jest stos – inaczej kolejka lifo



Pobieranie 58,5 Kb.
Data23.10.2017
Rozmiar58,5 Kb.



Abstrakcyjny typ danych
Klient korzystający z abstrakcyjnego typu danych:

    1. ma do dyspozycji jedynie operacje typu wstaw(), usun(), wyszukaj() itp.,

    2. natomiast nie ma wglądu na typy danych, które w rzeczywistości zastosowano do przechowywania danych

    3. nawet, jeśli zmieni się implementacja abstrakcyjnego typu danych a nagłówki funkcji nie zostaną zmienione, programy korzystające z tego typu pozostają bez zmian.


Pojęcie stosu
Jednym z najważniejszych typów danych obsługujących dołączanie i usuwanie obiektów ze zbioru jest stos – inaczej kolejka LIFO (ang. last in first out). Angielska nazwa dobrze ilustruje zasadę umieszczania i wyjmowania elementów z tej struktury. Mianowicie usuwany jest zawsze element, który jako ostatni był w zbiorze umieszczony. Każda operacja umieszczania elementu na stosie zwiększa wielkość stosu o 1, a każda operacja pobrania elementu zmniejsza ją o 1.

Zilustrujemy sposób funkcjonowania stosu graficznie podając po lewej stronie w kolumnie elementy przekazywane do umieszczenia na stosie lub symbol * oznaczający operację pobrania elementu, dalej zawartość stosu po wykonaniu operacji (na szczycie stosu jest element zapisany najbardziej po prawo), a na końcu linii element zdjęty ze stosu w przypadku operacji pobrania elementu. Każda linia odpowiada jednej operacji.


T T

A T A

K T A K

* T A K

D T A D

Z T A D Z

I T A D Z I

A T A D Z I A

Ł T A D Z I A Ł

A T A D Z I A Ł A

* T A D Z I A Ł A

S T A D Z I A Ł S

T T A D Z I A Ł S T

O T A D Z I A Ł S T O

S T A D Z I A Ł S T O S

* T A D Z I A Ł S T O S

* T A D Z I A Ł S T O

* T A D Z I A Ł S T

* T A D Z I A Ł S

W implementacji elementy można zorganizować w dowolny sposób pod warunkiem, że programy klienty będą odnosiły wrażenie, że elementy zostały zorganizowane w sposób opisany powyżej.


W języku C++ dysponujemy szczególnym sposobem tworzenia abstrakcyjnych typów danych, a mianowicie klasami. Chcąc napisać programy korzystające z abstrakcji stosu należy wcześniej określić interfejs. Ma on umożliwiać porozumienie między implementacją stosu a programem klientem. Deklaracje funkcji zapewniają, że ich wywołania w programach klientach i ich definicje w implementacjach będą zgodne.

Interfejs stosu przechowującego elementy typu int (liczby całkowite)

Deklaracja klasy

class stack

{

private:


// kod zależny od implementacji

public:


stack (); //konstruktor

void push (int elem); // funkcja umieszczająca element będący jej

//argumentem na stosie

int pop (); // funkcja zdejmująca ostatnio umieszczony na // stosie element i zwracająca ten element

};

Implementacja tablicowa stosu

Implementacja tablicowa stosu jest stosunkowo łatwa. Umieszczamy elementy w tablicy tak jak na rysunku wyjaśniającym zasadę działania stosu zapamiętując indeks wskazujący szczyt stosu.

Funkcja push zwiększa o jeden indeks szczytu i umieszcza w tablicy swój argument na miejscu indeksowanym przez szczyt. Nie sprawdza czy jest jeszcze miejsce w tablicy na umieszczenie elementu.

Funkcja pop sprawdza, czy stos nie jest pusty (indeks szczytu jest wtedy równy zero). Jeśli jakiś element jest na stosie, to zapamiętuje element stojący w tablicy na miejscu indeksowanym jako szczyt, zmniejsza indeks szczytu o jeden i zwraca zapamiętany element. Dodatkowo informuje jaki element został zdjęty.

W implementacji umieszczono jeszcze funkcję wypisującą aktualną zawartość stosu w kolejności od elementów umieszczonych najdawniej.

Klasie i funkcjom nadano polskie nazwy ( stack – stos, push – dopisz, pop – zdejmij ).


#include
class stos

{

private:



int* s; int n;

public:


stos ();

void dopisz (int elem); //push

int zdejmij (); //pop

void wypisz ();

};
stos::stos()

{

s=new int[20]; n=0; //konieczne jest zadeklarowanie //przewidywanej wielkości stosu



//n będzie indeksem szczytu

}
void stos::dopisz(int elem)

{

n++;s[n]=elem; //nie zapisujemy na miejscu zerowym



}
int stos::zdejmij()

{

if (n==0) //sprawdzamy, czy nie jest pusto



{

cout <<"Jest pusto.\n"; //informacja o sytuacji na stosie

return 0 ;}

else


{

int zdjety =s[n--];

cout <<"zdjalem "<informacja o usuwanym elemencie

return zdjety;


}

}
void stos::wypisz()

{

int i;


for(i=1;i

cout<

cout<<"\n";

}

Załączamy krótki program sprawdzający poprawność implementacji. Przy każdej linii programu zapisane zostały komunikaty wyrzucane na ekran w wyniku wykonania danej linii programu.


void main()

{

stos st; // deklarujemy używanie egzemplarza klasy stos // o nazwie st



st.wypisz();

st.zdejmij(); „Jest pusto.”

st.dopisz(5);

st.wypisz(); „5”

st.dopisz(20);

st.dopisz(34);

st.wypisz(); „5 20 34”

st.zdejmij(); „Zdjąłem 34”

st.wypisz(); „5 20”

}

Wada reprezentacji tablicowej stosu, to konieczność zarezerwowania w pamięci miejsca na tablicę o z góry zadanym rozmiarze. Można spowodować, że stos będzie zmieniał swoją wielkość dynamicznie, podwajając wielkość tablicy, gdy stos osiąga połowę maksymalnej wielkości i zmniejszając tablicę o połowę, gdy wielkość stosu jest mniejsza od ¼ wielkości zadeklarowanej tablicy.




Implementacja dowiązaniowa stosu

Jeżeli wielkość stosu będzie zmienna, a zwłaszcza, gdy zależy nam na racjonalnym wykorzystaniu zasobów pamięci lepsza jest realizacja stosu w postaci listy dowiązaniowej.

W tej implementacji stosuje się odwrotną kolejność elementów stosu w stosunku do poprzednio prezentowanej implementacji ( pierwszy element listy jest ostatnio umieszczonym na stosie ).
Funkcja push tworzy nowy węzeł zawierający w polu danej argument funkcji i umieszcza go na początku listy. Wskaźnik do szczytu ma pokazywać na nowy węzeł.
Funkcja pop sprawdza, czy stos nie jest pusty (wskaźnik do szczytu jest wtedy równy null). Jeśli jakiś element jest na stosie, to zapamiętuje element stojący w polu danej węzła, na który pokazuje szczyt, przesuwa wskaźnik szczytu na następny węzeł, a pierwszy usuwa z listy i zwalnia pamięć. Funkcja zwraca usunięty element oraz informuje jaki element został zdjęty.
W implementacji umieszczono także funkcję wypisującą aktualną zawartość stosu w kolejności od elementów umieszczonych najdawniej.

#include


class stos

{

private:



struct wezel // deklaracja struktury węzła przechowującego //obiekty typu int

{

int liczba; wezel* next;



wezel (int x,wezel* t) { liczba = x; next = t;}

};


typedef wezel* link; //link będzie typem oznaczającym

// wskaźnik do węzła

link szczyt;


public:

stos ();

void dopisz (int elem); //push

int zdejmij(); //pop

void wypisz ();

};
stos::stos()

{

szczyt=NULL;



}
void stos::dopisz(int elem)

{

szczyt=new wezel(elem, szczyt);



}
int stos::zdejmij()

{

if (szczyt==NULL)



{

cout <<"Jest pusto.\n";

return 0 ;}

else


{

int zdjety =szczyt->liczba; link t=szczyt->next;

delete szczyt; szczyt =t;

cout <<"zdjalem "<

return zdjety;

}

}


void stos::wypisz()

{

link pom;



pom=szczyt;

while( pom!=NULL)

{

cout<
liczba <<"\t";



pom=pom->next;

}

cout<<"\n";



}

Oczywiście uruchomienie programu sprawdzającego poprawność implementacji tablicowej da efekt identyczny z uzyskanym poprzednio.



Przykłady zastosowania stosu:
DopasowanieOgraniczników(plik)

wczytaj znak ch z plik;

while (nie koniec plik)

if (ch == `(`, `[` lub `{` )

push(ch);

else if (ch == `)`, `]` lub `}` )

if (ch i ogranicznik zdjęty ze stosu nie pasują)

błąd;


else if (ch== `/`)

wczytaj następny znak;

if (wczytany znak== `*`)

pomiń wszystkie znaki, do `*/`;

wczytaj następny znak ch z plik;

if (stos jest pusty)

sukces;

else


błąd;

DodawanieWielkichLiczb

odczytaj cyfry pierwszej liczby, zapisz je na pierwszym stosie;

odczytaj cyfry drugiej liczby, zapisz je na drugim stosie;

wynik=0;

while (przynajmniej jeden stos nie jest pusty)

zdejmij cyfrę z każdego z niepustych stosów, dodaj je do wynik;

wstaw cyfrę jednostek z wynik na trzeci stos;

wynik=cyfra dziesiątek z wynik;

wstaw wynik na trzeci stos;

zdejmij cyfry z trzeciego stosu i wyświetl je;
Pojęcie kolejki FIFO
Podobnie jak stos, kolejka FIFO jest jednym z najważniejszych typów danych obsługujących dołączanie i usuwanie obiektów ze zbioru. Angielska nazwa (first in, first out) dobrze ilustruje zasadę umieszczania i wyjmowania elementów z tej struktury, odmienną niż w przypadku stosu. Zamiast usuwać element, który jako ostatni był w zbiorze umieszczony, usuwamy ten, który w kolejce przebywa najdłużej. Każda operacja umieszczania elementu w kolejce „zwiększa” ją o 1, a każda operacja pobrania elementu „zmniejsza” ją o 1.

Kolejki tego typu znane nam są dobrze z codziennego życia. Według tej reguły obsługiwani są klienci oczekujący w kolejce w sklepie.

Zilustrujemy sposób funkcjonowania kolejki graficznie podając po lewej stronie w kolumnie elementy przekazywane do umieszczenia w kolejce lub symbol * oznaczający operację pobrania elementu, dalej zawartość kolejki po wykonaniu operacji, a na końcu linii element wyjęty z kolejki w przypadku operacji pobrania elementu. Każda linia odpowiada jednej operacji.
T T

A T A

K T A K

* A K T

D A K D

Z A K D Z

I A K D Z I

A A K D Z I A

Ł A K D Z I A Ł

A A K D Z I A Ł A

* K D Z I A Ł A A

F K D Z I A Ł A F

I K D Z I A Ł A F I

F K D Z I A Ł A F I F

O K D Z I A Ł A F I F O

* D Z I A Ł A F I F O K

* Z I A Ł A F I F O D

* I A Ł A F I F O Z

* A Ł A F I F O I

Tak samo jak w przypadku stosu, w implementacji elementy można zorganizować w dowolny sposób pod warunkiem, że programy klienty będą odnosiły wrażenie, że elementy zostały zorganizowane w sposób opisany powyżej.

Interfejs kolejki FIFO jest analogiczny do używanego w wypadku stosu.

Interfejs kolejki FIFO przechowującej elementy typu int (liczby całkowite)


Deklaracja klasy

class queue

{

private:


// kod zależny od implementacji

public:


queue (); //konstruktor

void put (int elem); // funkcja umieszczająca element będący



//jej argumentem w kolejce

int get (); // funkcja zdejmująca element umieszczony

//w kolejce najdawniej i zwracająca ten element

};

Implementacja tablicowa kolejki FIFO

Przy implementacji kolejki FIFO z użyciem tablicy należy unikać przesuwania elementów w tablicy tak, aby pierwszy element kolejki znajdował się ciągle na początku tablicy. Rozwiązanie takie byłoby nieekonomiczne ze względu na koszt wykonywania przesunięć. Wygodniejsze jest utrzymywanie dwóch indeksów tablicy pokazujących początek i koniec kolejki.
Funkcja put umieszcza w tablicy swój argument na miejscu indeksowanym przez „ogon” i zwiększa ten indeks o jeden. Nie sprawdza czy jest jeszcze miejsce w tablicy na umieszczenie elementu.
Funkcja get sprawdza, czy kolejka nie jest pusta (indeks głowy jest wtedy równy indeksowi ogona). Jeśli jakiś element jest w kolejce, to zapamiętuje element stojący w tablicy na miejscu indeksowanym jako „głowa”, zmniejsza indeks „głowy” o jeden i zwraca zapamiętany element. Dodatkowo informuje jaki element został zdjęty.

Jak zwykle wadą reprezentacji tablicowej jest konieczność zarezerwowania w pamięci miejsca na tablicę o z góry zadanym rozmiarze. Ze względu na to, iż sekwencja operacji umieszczania i usuwania elementów daje wrażenie jakby kolejka przemieszczała się w tablicy, zarezerwowana ilość pamięci musi znacznie przewyższać ilość pamięci zajętą przez pola tablicy wypełnione istotnymi danymi. Można spowodować, żeby kolejka „zawracała” w tablicy, to znaczy, gdy indeks ogona osiągnie maksymalną wartość przy następnym dołożeniu elementu ustawiamy go na zero. Analogicznie postępujemy z indeksem głowy. Sygnałem o przepełnieniu kolejki będzie wtedy zrównanie się indeksów głowy i ogona po operacji dokładania elementu, podczas gdy zrównanie się tych indeksów po operacji zdjęcia elementu oznacza, jak dotąd, że kolejka jest pusta. Omówiona implementacja nie sprawdza przepełnienia kolejki.



Implementacja dowiązaniowa kolejki FIFO

Reprezentacja kolejki za pomocą listy połączonej wykorzystuje przestrzeń proporcjonalną do ilości elementów w strukturze danych, chociaż dzieje się to kosztem dodatkowego miejsca na łącza.


W tej implementacji nowy element umieszczamy na końcu, a wyjmujemy element stojący na początku i kontrolujemy dwa wskaźniki: na początek i na koniec listy.
Funkcja put tworzy nowy węzeł zawierający w polu danej argument funkcji i przypisuje łączu zawartemu w węźle wskazywanym przez koniec wskaźnik do nowego węzła. Następnie aktualizowany jest wskaźnik końca tak, aby wskazywał na właśnie dołączony węzeł.
Funkcja get sprawdza, czy kolejka nie jest pusta (wskaźnik początku jest wtedy równy null). Jeśli jakiś element jest w kolejce, to zapamiętuje element stojący w polu danej węzła, na który pokazuje początek, przesuwa wskaźnik początku na następny węzeł, a pierwszy usuwa z listy i zwalnia pamięć. Funkcja zwraca usunięty element oraz informuje jaki element został zdjęty.







©operacji.org 2017
wyślij wiadomość

    Strona główna