Wykład 7
Abstrakcyjne typy danych – kolejki
Implementacja za pomocą tablicy i rekurencyjnej struktury danych czyli listy wiązanej
1. Pojęcie rekurencyjnych typów danych 2. Klasyfikacja struktur danych
3. Pojęcie abstrakcyjnego typu danych
4. Zastosowanie tablic i rekurencyjnych struktur danych (list wiązanych) do realizacji kolejek.
1. Pojęcie rekurencyjnych typów danych
Przykład (Wirth N. „Algorytmy + Struktury Danych = Programy”, WNT 1989) a) deklaracja modelu drzewa dziedziczenia:
type osoba = record
if znany then ( imię : alfa;
ojciec, matka: osoba) end;
b) opis reprezentacji zmiennej rodowód typu osoba:
jeśli znany = False, to istnieje tylko pole znacznikowe znany równe False (F) jeśli znany = True (T), to istnieją jeszcze trzy pola (imię, ojciec, matka)
c) przypadek danych: rodowód = (True, Jan,
(True, Marek,
(True, Adam, (False), (False) ),
(False) ),
(True, Maria, (False), (True, Ewa, (False), (False) )
) )
2. Klasyfikacja struktur danych
Struktury danych można podzielić na:
1) typy wymagające deklarowania rozmiarów - realizowane jako tablice lub struktury z bezpośrednim dostępem do każdego elementu tych struktur za pomocą operatorów indeksowania „[ ]” lub wyboru: „->” oraz „.”
2) typy nie wymagające deklarowania rozmiarów - rekurencyjne typy danych realizowane jako dynamiczne struktury danych z pośrednim dostępem do ich elementów. Rekurencyjny typ danych:
2.1) zawiera elementy jako struktury
2.2) używa wskaźników do wskazania następnego elementu typu, 2.3) używa dynamicznego przydziału pamięci dla swoich elementów
2.4) algorytm dostępu do poszczególnych składowych tej struktury określa programista dzięki jawnemu użyciu wskaźników.
3) uporządkowane: jako kryterium wstawiania danych do struktury jest ich wartość: drzewa poszukiwań binarnych, listy posortowane
4) nieuporządkowane: kryterium wstawiania danych do struktury dotyczy priorytetu danych: stosy, kolejki, listy nieposortowane, drzewa, grafy
Dynamiczne przydzielanie pamięci zmiennej typ* p=new typ.
P
*P
Wskaźnikowy model rekurencyjnych typów danych:
Przykład Wskaźnikowy model rodowodu a) deklaracja typu
struct osoba { char imie [10];
osoba* ojciec, *matka;
};
b) wskaźnikowa struktura rodowodu osoba* Poczatek;
Jan
Marek
Adam Ewa
Maria
NULL NULL NULL
NULL
NULL NULL
Poczatek
3. Abstrakcyjne typy danych
3.1.Główne cechy typów danych stosowanych w programach:
1) muszą być dostosowane do rozwiązywanego problemu 2) muszą zawierać dwa rodzaje informacji:
2.1) zbiór własności 2.2) zbiór działań.
Np. typ int
własności: reprezentuje liczby całkowite o wartościach od -32768 do +32767 zakodowanych w kodzie dwójkowym na dwóch bajtach
działania: zmiana znaku, dodawanie, mnożenie, dzielenie, modulo...
3.2. Trzy etapy procesu definiowania typów przez programistę [wg Stephen Prata - Szkoła programowania, Język C]:
a) Przygotowanie opisu ADT (abstrakcyjnego typu danych).
Abstrakcyjny opis obejmuje własności typu i operacji, jakie można na nim wykonać. Opis ten nie powinien być związany z żadną konkretną implementacją i językiem programowania. Ta formalna charakterystyka nosi nazwę abstrakcyjnego typu danych ADT.
b) Opracowanie interfejsu programistycznego realizującego ADT -
Jest to wskazanie sposobu przechowywania danych i opisanie zbioru funkcji wykonujących potrzebne operacje. W języku C/C++ etap ten może polegać na utworzeniu definicji struktury i prototypów funkcji przetwarzających. Funkcje te pełnią dla nowego typu tę samą rolę, co operatory w przypadku podstawowych typów języka C/C++.
Utworzony interfejs będzie stosowany przez osoby, które chcą skorzystać z nowego typu danych
c) Pisanie kodu implementującego interfejs
Ten krok jest kluczowy, w którym należy szczegółowo zrealizować wymagania wynikające z opisu. Należy zauważyć, że programista korzystający z interfejsu nie musi już orientować się w szczegółach implementacji
3.3 Podsumowanie:
Programista korzystający z abstrakcyjnego typu danych:
może wielokrotnie używać utworzony typ danych w programach
ma do dyspozycji jedynie operacje typu wstaw(...), usun(...), wyszukaj(...) itp.,
natomiast nie ma wglądu na typy danych, które w rzeczywistości zastosowano do przechowywania danych
dlatego nawet, jeśli zmieni się implementacja abstrakcyjnego typu danych a nagłówki funkcji nie zostaną zmienione, programy źródłowe korzystające z tego typu pozostają bez zmian.
Kolejka
Etap 1 - Opis ADT
Nazwa typu: Kolejka elementów
Własności typu: Potrafi przechować ciąg elementów o dowolnym rozmiarze Dostępne działania: Inicjalizacja kolejki
Określenie, czy kolejka jest pusta Dodanie elementu do kolejki, Usuwanie z kolejki,
typedef int dane; //typ informacji umieszczanej w kolejce typedef struct ELEMENT* stos; //typ wskazania na element kolejki
struct ELEMENT //typ elementu kolejki
{ dane Dane;
stos Nastepny;
};
struct kolejka //typ przechowujący dane kolejki
{ stos Poczatek, Koniec;
};
Etap 2 - Budowa interfejsu
void Inicjalizacja(kolejka& Kolejka);
{ działanie: inicjuje kolejkę
warunki wstępne: Kolejka.Poczatek i Kolejka.Koniec wskazują na pustą kolejkę warunki końcowe: kolejka zostaje zainicjowana jako pusta}
inline int Pusty(kolejka& Kolejka);
{działanie: określa, czy kolejka jest pusta; typ inline, bo często wywoływana warunki wstępne: Kolejka jest zainicjowana,
warunki końcowe: funkcja zwraca 1, jeśli kolejka jest pusta, jeśli nie- 0}
int Wstaw(kolejka& Kolejka, dane Dana);
{ działanie: dodaje element na koniec ciągu, zwany końcem kolejki
warunki początkowe: Dana jest daną do wstawienia na koniec zainicjowanej kolejki wskazanym przez Kolejka.Koniec
warunki końcowe: jeśli jest to możliwe, funkcja dodaje daną Dana na koniec kolejki i zwraca 1, w przeciwnym wypadku 0 }
int Usun(kolejka& Kolejka);
{ działanie: usuwa element wstawiony do kolejki na początku ciągu, warunki początkowe: Kolejka.Poczatek jest niepustą kolejką
warunki końcowe: usuwa element na poczatku kolejki i zwraca dane przez return. Kolejka.Koniec jest równy Kolejka.Poczatek, gdy kolejka jest pusta }
Etap 3. Implementacja kolejki jako listy wiązanej
wstawianie elementów zawsze na końcu struktury
„A” „B” NULL
Nowy „Z”
„A” „B”
Nowy „Z”
NULL
NULL
int Wstaw(kolejka& Kolejka, dane Dana) { stos Nowy;
Nowy = new ELEMENT;
if (Nowy!=NULL) Nowy->Dane=Dana;
else return 0;
if (Kolejka.Poczatek ==NULL) Kolejka.Poczatek=Nowy;
else Kolejka.Koniec->Nastepny= Nowy;
Kolejka.Koniec= Nowy;
Kolejka.Koniec->Nastepny=NULL;
return 1;
}
usuwanie elementów zawsze na początku struktury. W przypadku, gdy po usunięciu kolejka staje się pusta – Kolejka.Koniec jest równy Kolejka.Poczatek
„B” „Z” NULL
„A”
„B”
Kolejka.Poczatek
„Z” NULL
„A”
Pom
Pom
Kolejka.Poczatek
dane Usun(kolejka& Kolejka) {stos Pom;
dane d;
Pom = Kolejka.Poczatek;
Kolejka.Poczatek = Kolejka.Poczatek->Nastepny;
d= Pom->Dane; //((*Pom).Dane)
delete Pom;
if (Kolejka.Poczatek==NULL) Kolejka.Koniec=NULL;
return d; }
#include <conio.h>
Kolejka.
Poczatek
Kolejka.
Poczatek
Kolejka.Koniec Kolejka.Konie
c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
//1. interfejs ADT kolejki
typedef int dane; // dane umieszczone w kolejce
typedef struct ELEMENT* stos; //nazwa wskaznika na element stosu
struct ELEMENT { dane Dane;
stos Nastepny;
};
struct kolejka
{ stos Poczatek, Koniec;
};
//funkcje ADT kolejki void Inicjalizacja(kolejka& Kolejka);
inline int Pusty(kolejka Kolejka);
int Wstaw(kolejka& Kolejka, dane Dana);
dane Usun(kolejka& Kolejka);
//2. funkcje we/wy dla danych umieszczonych w kolejce void Pokaz_dane (dane Dana);
dane Dane();
//3. funkcje ogólnego przeznaczenia void Komunikat(char*);
char Menu(const int ile, char *Polecenia[]);
//4. elementy programu const int Esc=27;
const int POZ=4;
char * Tab_menu[POZ] = { "1 : Wstawianie do kolejki- na koniec",
"2 : Usuwanie z kolejki-na poczatku",
"3 : Wydruk kolejki wraz z jej usuwaniem", " >Esc Koniec programu"};
//funkcje klienta korzystajace z kolejki void Wstaw_do_kolejki(kolejka& Kolejka);
void Wyswietl_usun_z_kolejki(kolejka& Kolejka);
void main(void) { kolejka Kolejka;
char Wybor;
clrscr();
Inicjalizacja(Kolejka);
do
{ Wybor= Menu(POZ, Tab_menu);
switch (Wybor)
{ case '1' : Wstaw_do_kolejki(Kolejka);
break;
case '2' : if (Pusty(Kolejka))
Komunikat("\nKolejka pusty\n");
else (Usun(Kolejka));
break;
case '3' : if (Pusty(Kolejka))
Komunikat("\nKolejka jest pusta\n") ; else Wyswietl_usun_z_kolejki(Kolejka);
break;
}
} while (Wybor !=Esc );
}
//**************funkcje klienta korzystające ze stosu***********
void Wstaw_do_kolejki(kolejka& Kolejka) { dane Dana;
Dana= Dane();
if (Wstaw(Kolejka, Dana)==0) Komunikat("Brak pamieci\n");
}
void Wyswietl_usun_z_kolejki(kolejka& Kolejka) { dane d;
while (!Pusty(Kolejka)) { d=Usun(Kolejka);
Pokaz_dane(d);
} }
//*********funkcje interfejsu ADT kolejki************
void Inicjalizacja(kolejka& Kolejka)
{ Kolejka.Poczatek=Kolejka.Koniec = NULL;
}
inline int Pusty(kolejka Kolejka)
{ return Kolejka.Poczatek==NULL;
}
int Wstaw(kolejka& Kolejka, dane Dana) {
stos Nowy;
Nowy = new ELEMENT;
if (Nowy!=NULL) Nowy->Dane=Dana;
else return 0;
if (Kolejka.Poczatek ==NULL) Kolejka.Poczatek=Nowy;
else Kolejka.Koniec->Nastepny= Nowy;
Kolejka.Koniec= Nowy;
Kolejka.Koniec->Nastepny=NULL;
return 1;
}
dane Usun(kolejka& Kolejka) {
stos Pom;
dane d;
Pom = Kolejka.Poczatek;
Kolejka.Poczatek = Kolejka.Poczatek->Nastepny;
d= Pom->Dane; //((*Pom).Dane)
delete Pom;
if (Kolejka.Poczatek==NULL) Kolejka.Koniec=NULL;
return d;
}
//*******funkcje we/wy dla danych umieszczonych w kolejce*********
dane Dane() { int a;
do
{ fflush(stdin);
printf("\n\nPodaj dane typu int: ");
} while (scanf("%d",&a)!=1);
return a;
}
void Pokaz_dane(dane Dana) { printf("\nNumer: %d\n", Dana);
printf("Nacisnij dowolny klawisz...\n");
getch();
}
//*********funkcje ogolnego przeznaczenia************
char Menu(const int ile, char *Polecenia[]) { clrscr();
for (int i=0; i<ile;i++)
printf("\n%s",Polecenia[i]);
return getch();
}
void Komunikat(char* s) { printf(s);
getch();
}
Etap 3. Implementacja stosu za pomocą tablicy zainicjowanie kolejki: N=8 Koniec=0, Poczatek=N dodanie: tab[Koniec], Koniec=(Koniec+1) mod N
usuwanie: Poczatek=Poczatek mod N , return tab[Poczatek], Poczatek++
A) Kolejka pusta zainicjowana: N=8 Koniec=0, Poczatek=N
Koniec=0, Poczatek=N
0 1 2 3 4 5 6 7
B) Kolejka zawiera trzy elementy: Koniec=3, Poczatek=0 (dodano: 0(a, 1(b), 2(c)))
Poczatek Koniec
0 1 2 3 4 5 6 7
a b c
C) Kolejka zawiera trzy elementy: Poczatek=3, Koniec=5 (usunięto: 0(a), 1(b), 2(c) i dodano: 3(d), 4(e)))
Poczatek Koniec
0 1 2 3 4 5 6 7
d e
D)Kolejka zawiera pięć elementów: Koniec=2, Poczatek=5 (usunięto 3(d), 4(e)) i wstawiono: 5(a), 6(b), 7(c), 0(d), 1(e) )
Koniec Poczatek
0 1 2 3 4 5 6 7
d e a b c
E) Kolejka pełna: Koniec=5, Poczatek=5 (po wstawieniu na koniec następujących elementów: 2(f), 3(g), 4(h))
Poczatek Koniec
0 1 2 3 4 5 6 7
d e f g h a b c
F) Kolejka pusta, stan nieokreślony: Koniec=5, Poczatek=5 (po usunięciu elementów: 5(a), 6(b), 7(c), 0(d), 1(e), 2(f), 3(g), 4(h)). Należy więc po usunięciu ostatniego elementu zainicjować kolejkę tak, aby Poczatek =N, Koniec=0, czyli wprowadzić kolejkę w stan A)
Poczatek
Koniec
0 1 2 3 4 5 6 7
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
//1. interfejs ADT kolejki
typedef int dane; // dane umieszczone stosie
const long N=5;
struct kolejka //elementy typu kolejki
{ int Poczatek;
int Koniec;
dane tab[N];
};
//funkcje interfejsu mają ten sam nagłówek jak dla listy wiązanej void Inicjalizacja(kolejka& Kolejka);
inline int Pusty(kolejka Kolejka);
int Wstaw(kolejka& Kolejka, dane Dana);
dane Usun(kolejka& Kolejka);
//Funkcje we/wy, ogólnego przeznaczenia, klienckie oraz program są //identyczne dla obu implementacji
//2. funkcje we/wy dla danych umieszczonych w kolejce void Pokaz_dane (dane Dana);
dane Dane();
//3. funkcje ogolnego przeznaczenia void Komunikat(char*);
char Menu(const int ile, char *Polecenia[]);
//4. elementy programu const int Esc=27;
const int POZ=4;
char * Tab_menu[POZ] = { "1 : Wstawianie do kolejki- na koniec",
"2 : Usuwanie z kolejki-na poczatku",
"3 : Wydruk kolejki wraz z jej usuwaniem", " >Esc Koniec programu"};
//funkcje klienta korzystajace z kolejki void Wstaw_do_kolejki(kolejka& Kolejka);
void Wyswietl_usun_z_kolejki(kolejka& Kolejka);
void main(void) { kolejka Kolejka;
char Wybor;
clrscr();
Inicjalizacja( Kolejka);
do
{ Wybor= Menu(POZ, Tab_menu);
switch (Wybor)
{ case '1' : Wstaw_do_kolejki(Kolejka); break;
case '2' : if (Pusty(Kolejka)) Komunikat("\nStos pusty\n");
else (Usun(Kolejka));
break;
case '3' : if (Pusty(Kolejka)) Komunikat("\nStos pusty\n") ; else Wyswietl_usun_z_kolejki(Kolejka);
break;
}
} while (Wybor !=Esc );
}
//*********funkcje interfejsu ADT kolejki************
void Inicjalizacja(kolejka& Kolejka) { Kolejka.Poczatek = N;
Kolejka.Koniec=0; }
inline int Pusty(kolejka Kolejka)
{ return Kolejka.Poczatek==N && Kolejka.Koniec==0; } int Wstaw(kolejka& Kolejka, dane Dana)
{ if (Kolejka.Poczatek != Kolejka.Koniec)
{ Kolejka.tab[Kolejka.Koniec++] = Dana;
Kolejka.Koniec %= N;
if (Kolejka.Poczatek==N) Kolejka.Poczatek=0;
return 1; } else return 0;
}
dane Usun(kolejka& Kolejka) { dane d;
Kolejka.Poczatek %= N;
d= Kolejka.tab[Kolejka.Poczatek++];
if (Kolejka.Poczatek%N==Kolejka.Koniec) Inicjalizacja(Kolejka);
return d; }