podr09


Dynamiczne struktury danych w języku Turbo Pascal Dynamiczne struktury danych w języku Turbo Pascal Rodzaje zmiennych Typy wskaźnikowe. Wskaźniki Typ wskaźnikowy Pointer Dynamiczny przydział pamięci Obsługa braku pamięci na stercie Czas życia zmiennych dynamicznych Procedura Getmem Operacje arytmetyczne na wskaźnikach Typ rekordowy Organizacja listy jednokierunkowej Podstawowe operacje wykonywane na liście jednokierunkowej Sortowanie listy jednokierunkowej Stosy i kolejki Lista dwukierunkowa Zadania do samodzielnego rozwiązania Rodzaje zmiennych W języku Pascal zmienne dzielą się na statyczne i dynamiczne. Zmienna statyczna tworzona jest w trakcie kompilacji programu i istnieje do momentu jego zakończenia lub też do momentu zakończenia funkcji lub procedury, w której została zadeklarowana. Program w momencie uruchamiania otrzymuje od systemu operacyjnego fragment pamięci o rozmiarze 64KB przeznaczony na dane statyczne - fragment ten nazywamy segmentem danych. W segmencie tym są umieszczane wszystkie zmienne globalne programu a także zmienne zadeklarowane lokalnie w procedurach i funkcjach. Łączny rozmiar tych zmiennych nie może przekroczyć rozmiaru segmentu danych, tj. 65536 bajtów. Zmienną dynamiczną nazywamy zmienną utworzoną w trakcie wykonywania programu. Zmienne te tworzone są na tzw. stercie, której rozmiar jest na ogół różny i może wynosić nawet do 600KB. W odróżnieniu od segmentu danych zmienną utworzoną na stercie można w dowolnym momencie usunąć, zwalniając w ten sposób niepotrzebnie zajmowaną pamięć. Typy wskaźnikowe. Wskaźniki Do pracy ze zmiennymi dynamicznymi używa się typów wskaźnikowych. Elementami tych typów są zmienne wskaźnikowe, które krótko nazywamy wskaźnikami. Wskaźnik jest adresem pewnej zmiennej określonego typu. Mówiąc o wskaźniku zawsze mamy na myśli zmienną, której adres zawiera wskaźnik. Ze względu na różny typ zmiennych mówimy o wskaźniku na liczbę typu Byte, wskaźniku na liczbę rzeczywistą Real, wskaźniku na łańcuch typu String itd - zawsze ze wskaźnikiem związany jest typ zmiennej na którą wskazuje dany wskaźnik. Używamy tu sformułowania "wskaźnik wskazuje na zmienną" gdyż w rzeczywistości zawiera on adres tej zmniennej. Adresy zmiennych mają swoją specyficzną budowę, która wynika ze sposobu adresacji pamięci w komputerach PC. Pamięć ta jest dzielona na segmenty o rozmiarze 64KB (65536). Segment taki zawiera 65536 bajtów (komórek) pamięci. Zarówno segmenty pamięci jak również wszystkie komórki pamięci w danym segmencie są numerowane od zera, co oznacza że do zapamietania adresu segmentu wystarcza liczba typu Word, oraz za pomocą takiej liczby można określić adres komórki pamięci w danym segmencie. Drugi z tych adresów, czyli położenie komórki w segmencie nazywamy ofsetem lub przesunięciem. Adres komórki pamięci znajdującej się w segmencie Seg i przesuniętej względem początku tego segmentu o Ofs komórek zapisujemy najczęściej w postaci $Seg:$Ofs, przy czym obie wartości zapisane są szesnastkowo. Standardowe funkcje języka Seg(Zmienna) Ofs(Zmienna) zwracają w postaci liczby typu Word odpowiednio segment i ofset podanej jako argument zmiennej. Przykład 1 Napisz program, który wypisze adresy dwóch zadeklarowanych w nim zmiennych oraz adres dowolnej funkcji lub procedury. (dop) Program Adresy; Uses Crt; Procedure Pusta; Begin End; Var A : Integer; B : Real; Begin ClrScr; WriteLn('Adres zmiennej A = ', Seg(A), ':', Ofs(A)); WriteLn('Adres zmiennej B = ', Seg(B), ':', Ofs(B)); WriteLn('Adres procedury Pusta = ', Seg(Pusta), ':', Ofs(Pusta)); ReadKey; End. Deklaracja zmiennej wskaźnikowej ma postać: Var Wsk : ^TYP; gdzie TYP jest dowolnym typem standardowym lub zdefiniowanym przez programistę. Niech dany będzie fragment programu: Var A, B : Integer; Wsk : ^Integer; Begin ... A:=-7; B:=12; Wsk:=@A; WriteLn('Wartość zmiennej A=', Wsk^); Wsk:=Addr(B); WriteLn('Wartość zmiennej B=', Wsk^); ... End. Wykonanie powyższego fragmentu spowoduje wydrukowanie liczb -7 oraz 12, czyli kolejno wartości zmiennych A i B. Początkowo zmiennej wskaźnikowej Wsk został przypisany adres zmiennej A za pomocą tzw. operatora pobrania adresu @. Po wykonaniu przypisania zmienna wskaźnikowa Wsk wskazuje na zmienną A. Wyrażenie Wsk^ jest w języku Pascal odwołaniem do zmiennej, na którą wskazuje dany wskaźnik - w tym wypadku jest to odwołanie do zmiennej A. Adres zmiennej można przypisać wskaźnikowi również za pomocą standardowej funkcji Addr. Po wykonaniu przypisania Wsk:=Addr(B) zmienna wskaźnikowa Wsk wskazuje na zmienną B i w tym momencie odwołanie Wsk^ jest równoznaczne z odwołaniem do zmiennej B. Typ wskaźnikowy Pointer W Pascalu istnieje uniwersalny typ wskaźnikowy Pointer, który jest zgodny z każdym innym typem wskaźnikowym. Oznacza to, że zmiennej wskaźnikowej typu Pointer można przypisać wartość dowolnego innego wskaźnika i odwrotnie, każdemu wskaźnikowi można przypisać wartość wskaźnika typu Pointer. Standardowa funkcja Ptr(Seg, Ofs) : Pointer; tworzy wskaźnik typu Pointer wskazujący komórkę pamięci w podanym segmencie i o wskazanym przesunięciu. Mając zmienną N:Byte oraz wskaźnik Wsk:^Byte można wskaźnikowi Wsk przypisać adres zmiennej N również w następujący sposób: Wsk:=Ptr(Seg(N), Ofs(N)); Przykład Przyjmijmy następującą definicję łańcucha: "Łańcuch reprezentowany jest przez zmienną typu ^Char. Wartość Nil tej zmiennej nazywamy łańcuchem pustym. Łańcuch tworzą wszystkie znaki poczynając od znaku wskazywanego zmienną reprezentującą łańcuch aż do znaku #0 (znak #0 nie jest częścią łańcucha). Napisz program, który wygeneruje losowy początek łańcucha, wydrukuje wszystkie znaki łańcucha o kodach od 32 oraz poda jego długość. Program Lancuch; Uses Crt; Var N : LongInt; Wsk : ^Char; Begin ClrScr; Randomize; Wsk:=Ptr(Random(65535), Random(65535)); N:=0; If Wsk<>Nil Then While Wsk^<>#0 Do Begin If Wsk^>=#32 Then Write(Wsk^); Inc(Wsk); Inc(N); End; WriteLn; WriteLn('Długość łańcucha = ', N); WriteLn('Naciśnij jakiś klawisz...'); While KeyPressed Do ReadKey; ReadKey; While KeyPressed Do ReadKey; End. Dynamiczny przydział pamięci Wskaźnikowi po zadeklarowaniu można przypisać adres dowolnej istniejącej zmiennej identycznego typu jak typ wskaźnika. Niezależnie od typu wskaźnika zawsze można mu przypisać tzw. adres pusty, który w języku Pascal oznaczamy słowem Nil - wskaźnik o wartości Nil nie wskazuje niczego. Program w dowolnym momencie może utworzyć nową zmienną i jej adres przypisać zmiennej wskaźnikowej. Tak powstałą zmienną nazywamy zmienną dynamiczną. Standardowa procedura New(Zmienna wskaźnikowa) tworzy na stercie nową zmienną typu identycznego z typem wskaźnika i jej adres przypisuje zmiennej wskaźnikowej. Po poprawnym wykonaniu procedury New zmienna wskaźnikowa zawiera adres utworzonej zmiennej, jeżeli jednak utworzenie zmiennej dynamicznej nie jest możliwe z powodu braku pamięci na stercie, to następuje błąd wykonania programu i w konsekwencji jego przerwanie. W związku z tym należy przed każdą próbą przydziału pamięci sprawdzać możliwość wykonania tej operacji za pomocą funkcji bezparametrowej MaxAvail, wartością której jest ilość możliwej do przydzielenia przez procedurę New pamięci. Sposób postępowania ilustruje poniższy przykład: Var Wsk : ^Integer; Begin ... If MaxAvail>=SizeOf(Integer) Then New(Wsk) Else Begin obsługa błędu End; ... End. Standardowa funkcja SizeOf zwraca rozmiar zmiennej podanego jako argument typu w bajtach. Przykład 2 Dana jest liczba N typu 1..100 oraz ciąg N łańcuchów typu String. Napisz program, który wyznaczy ilość liter alfabetu we wszystkich napisach. (dst) Program IloscLiter; Uses Crt; Var Napisy : Array[1..100] Of ^String; I, J, N : 1..100; Ilosc: Word; Begin ClrScr; Repeat Write('Podaj N='); ReadLn(N); Until (N>0) And (N<101); For I:=1 To N Do If MaxAvail<SizeOf(String) Then Begin WriteLn('Brak pamięci potrzebnej do wykonania.'); Napisy[I]:=Nil; End Else Begin New(Napisy[I]); Write('Podaj napis numer ', I,' = '); ReadLn(Napisy[I]^); End; Ilosc:=0; For I:=1 To N Do If Napisy[I]<>Nil Then For J:=1 To Length(Napisy[I]^) Do If Napisy[I]^[J] In ['a'..'z', 'A'..'Z'] Then Inc(Ilosc); WriteLn('Napisy zawierały ', Ilosc, ' liter.'); WriteLn('Naciśnij jakiś klawisz...'); While KeyPressed Do ReadKey; ReadKey; While KeyPressed Do ReadKey; End. W powyższym przykładzie znana jest maksymalna liczba wszystkich napisów równa 100, faktyczna liczba wprowadzonych napisów jest podawana dopiero w momencie wykonywania programu. Gdyby zadeklarować tablicę 100 napisów, to zajęłaby ona 256*100=25600 bajtów, czyli prawie połowę pamięci przeznaczonej na dane statyczne. Zamiast tego w programie zadeklarowano tablicę 100 wskaźników, która zajmuje zaledwie 200 bajtów, zaś pamięć dla napisów przydzielana jest podczas wykonywania programu. Dzięki takiemu postępowaniu program przydzieli tylko tyle pamięci ile jest niezbędne do prawidłowego wykonania programu. Obsługa braku pamięci na stercie Standardową reakcją na brak wymaganej do przydzielenia pamięci jest błąd wykonania programu. Błąd ten można obsłużyć programowo - wymaga to zdefiniowania w programie funkcji: Function Nazwa(Rozmiar : Word) : Integer; Far; Begin Nazwa:=1; End; której nazwa jest dowolna - istotne jest tylko przypisanie liczby 1 jako wartości funkcji. Ponadto funkcja ta musi zostać zadeklarowana jako "daleka" ze słowem kluczowym Far. Adres tak zdefiniowanej funkcji należy w programie przypisać standardowej zmiennej HeapError za pomocą instrukcji przypisania: ... HeapError:=@Nazwa; ... Po wykonaniu tych dwóch czynności program nie będzie kontrolował błędu braku pamięci na stercie - w momencie wystąpienia takiej sytuacji wskaźnikowi będącemu argumentem funkcji New będzie przypisywana wartość Nil. Dzięki temu można nie wywoływać funkcji MaxAvail przed przydziałem pamięci, a jedynie sprawdzić wartość wskaźnika po wykonaniu procedury New: Function Sterta(X : Word) : Integer; Far; Begin Sterta:=1; End; Var Wsk : ^Integer; Begin ... HeapError:=@Sterta; ... New(Wsk); If Wsk=Nil Then Begin obsługa błędu End; ... End. Czas życia zmiennych dynamicznych Zmienna dynamiczna utworzona procedurą New istnieje do momentu usunięcia jej przez programistę - w odróżnieniu od zmiennych statycznych zmienne dynamiczne nigdy nie są usuwane automatycznie. Usunięcie zmiennej wykonuje się procedurą Dispose(Zmienna wskaźnikowa) Należy pamietać, że jeżeli program utworzy zmienną dynamiczną, której adres zostanie zapamietany w zmiennej wskaźnikowej, a następniej przed usunięciem tej zmiennej dokona zmiany wskaźnika, to jej usunięcie nie będzie już możliwe - nie będzie bowiem znany adres tej zmiennej. W poniższym przykładzie: ... New(Wsk); ReadLn(Wsk^); If Wsk^=0 Then Wsk:=Nil; ... tworzona jest zmienna, której adres przypisywany jest zmiennej wskaźnikowej Wsk. Następnie następuje odczyt wartości liczbowej do tej zmiennej, po czym wskaźnik Wsk może otrzymać adres pusty Nil. Na skutek wykonania tej instrukcji program "gubi" adres utworzonej zmiennej - zmienna ta będzie zajmować pamięć do czasu zakończenia programu. Procedura GetMem Pamięć na stercie można również przydzielić procedurą GetMem(Zmienna wskaźnikowa, Rozmiar) w której argument rozmiar określa ilość potrzebnej do przydzielenia pamięci. Przed wykonaniem procedury należy sprawdzić ilość dostępnej pamięci funkcją MaxAvail lub też jeżeli program obsługuje błąd braku pamięci na stercie, wartość wskaźnika po wywołaniu procedury. Pamięć przydzieloną procedurą GetMem zwalnia się za pomocą procedury FreeMem(Zmienna wskaźnikowa, Rozmiar) gdzie argument rozmiar powinien być identyczny z argumentem użytym podczas przydziału pamięci. Procedur New i Dispose oraz GetMem i FreeMem nie wolno ze sobą mieszać - pamięć przydzielona przez New musi być zwolniona przez Dispose, analogicznie pamięć przydzielona przez GetMem należy zwolnić procedurą FreeMem. Operacje arytmetyczne na wkaźnikach Wskaźniki można ze sobą porównywać za pomocą operatorów = oraz <>. Dwa wskaźniki uważamy za równe, gdy wskazują na ten sam obszar pamięci lub gdy oba mają wartość Nil. Dozwolona jest również operacja zwiększania i zmniejszania wskaźnika o liczbę całkowitą za pomocą procedur Inc i Dec. Po wykonaniu instrukcji Inc(Wsk); wskaźnik Wsk wskazuje na kolejny element w pamięci typu identycznego z typem wskaźnika, tzn. jeżeli Wsk jest typu ^Word, to dodanie do wartości wskaźnika liczby 1 powoduje przesunięcie wskaźnika na kolejną liczbę typu Word, czyli o dwa bajty. Ogólnie, jeżeli Wsk jest typu ^TYP, to zwiekszenie wskaźnika o 1 procedurą Inc przesuwa wskaźnik o SizeOf(TYP) bajtów. Analogiczne zasady obowiązują podczas zmniejszania wskaźnika o liczbę całkowitą procedurą Dec. Przykład 3 Dana jest liczba naturalna N typu 1..100 i ciąg N liczb typu Integer. Napisz program, który wyznaczy liczbę najmniej różniącą się od średniej tego ciągu. (dst) Program MinSrednia; Uses Crt; Function Srednia(Tab : Pointer; N : Word) : Real; Var Wsk : ^Integer; Suma : Longint; I : Word; Begin Suma:=0; Wsk:=Tab; I:=N; While I>0 Do Begin Inc(Suma, Wsk^); Inc(Wsk); Dec(I); End; Srednia:=Suma/N; End; Function MinRoznica(Tab : Pointer; N : Word; Sr : Real) : Integer; Var Wsk : ^Integer; Wynik : Integer; Begin Wsk:=Tab; Wynik:=Wsk^; While N>1 Do Begin Inc(Wsk); Dec(N); If Abs(Sr-Wsk^)<Abs(Sr-Wynik) Then Wynik:=Wsk^; End; MinRoznica:=Wynik; End; Var T, Wsk : ^Integer; N, I : Word; Begin ClrScr; Repeat Write('Podaj ilość liczb N='); ReadLn(N); Until (N>=1) And (N<=100); If MaxAvail<N*SizeOf(Integer) Then Begin WriteLn('Brak pamięci potrzebnej do wykonania programu.'); WriteLn('Potrzebne:',N*SizeOf(Integer),' Dostępne:', MaxAvail); WriteLn('Nacisnij jakiś klawisz...'); While KeyPressed Do ReadKey; ReadKey; While KeyPressed Do ReadKey; Halt; End; GetMem(T, N*SizeOf(Integer)); Wsk:=T; For I:=1 To N Do Begin Write('Liczba numer ', I, ' = '); ReadLn(Wsk^); Inc(Wsk); End; WriteLn('Liczba najmniej różniąca sie od średniej : ', MinRoznica(T, N, Srednia(T, N))); WriteLn('Nacisnij jakiś klawisz...'); While KeyPressed Do ReadKey; ReadKey; While KeyPressed Do ReadKey; End. Typ rekordowy Rekord jest złożoną strukturą danych składającą się z pól o dowolnych typach, do których odwołania następują poprzez nazwę pola. Definicja typu rekordowego ma postać: Type Nazwa = Record pole01, pole02, ... : Typ1; pole11, pole12, ... : Typ2; ... polen1, polen2, ... : Typn; End; Definicja typu opisującego ucznia mogłaby mieć postać: Type Uczen = Record Nazwisko, Imie : String[30]; Adres : String[100]; Wiek : 15..20; Klasa : String[2]; End; Var A, B : Uczen; Odwołania do pól rekordu wykonuje się za pomocą operatora wyboru pola '.', np. A.Nazwisko:='Nowak', WriteLn(B.Wiek), itd. Obowiązuje przy tym następująca zasada: jeżeli X oznacza zmienną rekordową, Y jest nazwą pola tego rekordu typu TYP, to wyrażenie X.Y jest odwołaniem do pola Y rekordu X i traktowane jest jak zmienna typu TYP. Zatem wyrażenie A.Nazwisko jest zmienną typu String[30] a wyrażenie B.Wiek zmienną typu 15..20. Organizacja listy jednokierunkowej Niech dana będzie definicja typu rekordowego Skladnik: Type WSkladnik = ^Skladnik; Skladnik = Record X : Integer; Next : WSkladnik; End; Rekord Skladnik zawiera dwa pola: pole X typu Integer oraz pole wskaźnikowe Next typu WSkladnik, czyli wskaźnik na rekord Skladnik. Załóżmy, że w programie zadeklarowana jest zmienna Start typu wskaźnik na Skladnik, czyli Start : WSkladnik. Program może utworzyć dynamicznie zmienną rekordową, jej adres przypisać zmiennej wskaźnikowej Start i wczytać do tej zmiennej liczbę typu Integer: ... New(Start); Start^.Next:=Nil; Write('Podaj liczbę całkowitą = '); ReadLn(Start^.X); ... Ponieważ pole Next rekordu może zawieraż adres rekordu typu Skladnik można utworzyć dynamicznie kolejny rekord przypisując jego adres polu Next utworzonego przez chwilą rekordu: ... New(Start^.Next); Start^.Next^.Next:=Nil; Write('Podaj liczbę całkowitą = '); ReadLn(Start^.Next^.X); ... Powstała w ten sposób struktura zawierająca dwa rekordy, przy czym adres pierwszego pamiętany jest w zmiennej wskaźnikowej Start, adres drugiego w polu Next pierwszego rekordu. Wyrażenie Start jest typu WSkladnik i jest adresem pierwszego z rekordów, wyrażenie Start^.Next również jest typu WSkladnik i jest adresem rekordu drugiego. Proces ten można powtórzyć po raz trzeci: ... New(Start^.Next^.Next); Start^.Next^.Next^.Next:=Nil; Write('Podaj liczbę całkowitą = '); ReadLn(Start^.Next^.Next^.X); ... W ten sposób zostanie utworzony trzeci rekord, którego adres będzie pamiętany w polu Next drugiego z rekordów. Możemy utworzyć kolejny czwarty rekord wpisując jego adres w pole Next rekordu trzeciego, rekord piąty wpisując jego adres w pole Next rekordu czwartego, itd. Przyjmijmy teraz, że została utworzona tego typu struktura zawierająca 20 połączonych ze sobą rekordów. Każdy z rekordów zawiera jedną liczbę całkowitą typu Integer. Program może w każdej chwili odwołać się do dowolnej z tych liczb, choć odwołanie takie może być kłopotliwe - odwołanie do szóstej liczby miałoby postać: Start^.Next^.Next^.Next^.Next^.Next^.X - chcąc odwołać się do liczby o numerze 20 należałoby podać 19 razy wyrażenie Next^. Szczególna uwagę należy jednak poświęcić pamięci zajmowanej przez liczby w segmencie danych. Do zapamiętania owych 20 liczb użyto jednej zmiennej statycznej, a mianowicie wskaźnika Start, który zajmuje zaledwie 2 bajty segmentu danych. Oczywistym jest, że zwiększając ilość liczb w utworzonej strukturze np. do 200 nie zajmujemy żadnego dodatkowego bajtu w segmencie danych - wszystkie te liczby zostaną zapamietane dzieki użyciu jednego dwubajtowego wskaźnika Start : WSkladnik. Tak utworzoną strukturę nazywamy listą jednokierunkową (liniową). Rekord, który został użyty do utworzenia tej listy nazywamy składnikiem listy, zaś jej pierwszy składnik początkiem listy. Bardzo ważnym elementem listy jest pole Next o wartości Nil. Pamiętając adres pierwszego składnika listy mamy dostęp do wszystkich jej składników musimy jednak wiedzieć w którym miejscu lista się kończy. Koniec ten jest wyznaczany właśnie przez pole Next o wartości Nil. Ostatni składnik listy ma pole Next równe Nil, wszystkie poprzednie składniki przechowują w tym polu adres składnika następnego. Nazwa lista jednokierunkowa wynika ze sposobu łączenia poszczególnych składników listy: mając adres N-tego składnika można ustalić adres każdego ze składników o numerach N+1, N+2, N+3, itd., nie jest natomiast możliwe ustalenie adresu składnika wcześniejszego np. N-1. Po liście możemy zatem poruszać się tylko w jednym kierunku - od początku do końca listy, przejście w drugą stroną nie jest możliwe. Podstawowe operacje wykonywane na liście jednokierunkowej Poniższe przykłady zawierają definicje kilku podstawowych funkcji służących do zarządzania listą jednokierunkową. Za składnik listy przyjęto zdefiniowany powyżej rekord typu Skladnik. Przykład 4 Zdefiniować funkcję LRozmiar wartością której będzie ilość składników listy utworzonej przy pomocy rekordu typu Skladnik. (dst) Function LRozmiar(Start : WSkladnik) : Word; Var Wynik : Word; Begin Wynik:=0; While Start<>Nil Do Begin Start:=Start^.Next; Inc(Wynik); End; LRozmiar:=Wynik; End; Przykład 5 Zdefiniować funkcję LAdres która zwróci adres N-tego składnika na liście lub adres pusty Nil jeżeli lista nie zawiera takiego składnika. (dst) Function LAdres(Start : WSkladnik; N : Word) : WSkladnik; Begin If (N<1) Or (N>LRozmiar(Start)) Then LAdres:=Nil Else Begin While N>1 Do Begin Start:=Start^.Next; Dec(N); End; LAdres:=Start; End; End; Przykład 6 Zdefiniować funkcję LWar wartością której będzie N-ta liczba listy utworzonej przy pomocy rekordu Skladnik lub zero jeżeli lista nie zawiera składnika o takim numerze. (dst) Function LWar(Start : WSkladnik; N : Word) : Integer; Var Adr : WSkladnik; Begin Adr:=LAdres(Start, N); If Adr=Nil Then LWar:=0 Else LWar:=Adr^.X; End; Przykład 7 Zdefiniować funkcję LDodaj o wartościach logicznych, która doda do listy utworzonej przy pomocy rekordu Skladnik kolejną liczbę na jej końcu. Funkcja powinna zwrócić True w przypadku powodzenia operacji, False w przeciwnym wypadku. (dst) Function LDodaj(Var Start : WSkladnik; Liczba : Integer) : Boolean; Var Adr : WSkladnik; Begin If MaxAvail<SizeOf(Adr^) Then LDodaj:=False Else Begin New(Adr); Adr^.X:=Liczba; Adr^.Next:=Nil; If Start=Nil Then Start:=Adr Else LAdres(Start, LRozmiar(Start))^.Next:=Adr; LDodaj:=True; End; End; Przykład 8 Zdefiniować procedurę LUsunPierw, która usunie w listy utworzonej przy pomocy rekordu Skladnik pierwszy jej skladnik. (dst) Procedure LUsunPierw(Var Start : WSkladnik); Var Adr : WSkladnik; Begin If Start=Nil Then Exit; Adr:=Start; Start:=Start^.Next; Dispose(Adr); End; Sortowanie listy Przejdziemy teraz do zagadnienia sortowania listy. Listę zawierającą porównywalne elementy można posortować podobnie jak tablicę używając algorytmu sortowania bąbelkowego. W przypadku tablicy podstawową operacją wykonywaną na elementach tablicy jest zamiana miejscami dwóch kolejnych elementów. W ten sam sposób można posortować listę zamieniając miejscami dwie liczby przechowywane na dwóch kolejnych składnikach. Druga z metod polega na zmianie kolejności składników, tzn. jeżeli sortujemy listę rosnąco i liczba przechowywana w składniku I jest większa niż liczba przechowywana w składniku I+1, to zamieniamy miejscami składniki tak aby następnym składnikiem po składniku I-1 był składnik I+1, zaś następnym składnikiem po składniku I+1 składnik I. Przykład 9 Zdefiniować procedurę LZamien, która dla argumentu N typu Word zamieni miejscami dwa składniki listy: składnik o numerze N ze składnikiem o numerze N+1. (bdb) Procedure LZamien(Var Start : WSkladnik; N : Word); Var Adr, AdrNast, AdrPop : WSkladnik; Begin If N=0 Then N:=1; If N>=LRozmiar(Start) Then Exit; Adr:=LAdres(Start, N); AdrNast:=LAdres(Start, N+1); Adr^.Next:=AdrNast^.Next; AdrNast^.Next:=Adr; If N=1 Then Start:=AdrNast Else Begin AdrPop:=LAdres(Start, N-1); AdrPop^.Next:=AdrNast; End; End; Przykład 10 Dany jet ciąg liczb całkowitych zakończony liczbą zero, które nie jest elementem ciągu. Napisz program, który wydrukuje liczby tego ciągu w kolejności rosnącej. (bdb) Program SortujListe; Uses Crt; { ... Tutaj należy umieścić definicję wszystkich funkcji i procedur ... } Var Start : WSkladnik; X : Integer; I, J, Ilosc : Word; Begin ClrScr; Start:=Nil; Write('Podaj pierwszą liczbę ciągu = '); ReadLn(X); If X<>0 Then If Not LDodaj(Start, X) Then Begin WriteLn('Brak pamięci.'); Halt; End; While X<>0 Do Begin Write('Podaj kolejną liczbę = '); ReadLn(X); If X<>0 Then If Not LDodaj(Start, X) Then Begin WriteLn('Brak pamięci.'); Halt; End; End; If LRozmiar(Start)=0 Then WriteLn('Ciag nie zawiera żadnych liczb.') Else Begin Ilosc:=LRozmiar(Start); For I:=1 To Ilosc Do For J:=1 To Ilosc-I Do If LAdres(Start, J)^.X>LAdres(Start, J+1)^.X Then LZamien(Start, J); WriteLn('Liczby ciągu ułożone rosnąco:'); For I:=1 To Ilosc Do WriteLn('Liczba numer ', I, ' = ', LWar(Start, I)); End; WriteLn('Naciśnij jakiś klawisz...'); While KeyPressed Do ReadKey; ReadKey; While KeyPressed Do ReadKey; End. Stosy i kolejki Program może przetwarzać listę jednokierunkową w pewien specyficzny sposób. Jeżeli program dodaje kolejny składnik zawsze na końcu listy, zaś po przetworzeniu usuwa z listy jej pierwszy składnik, to taką strukturę nazywamy kolejką. Nazwa kolejki wywodzi się z kolejki w sklepie, w której obowiązuje zasada, że jako pierwszy opuszcza kolejkę ten, kto przebywa w niej najdłużej. Zasadę tą często formułuje się krótko: pierwszy przyszedł, pierwszy wychodzi. Jeżeli program przetwarzając listę jednokierunkową dodaje do niej kolejne składniki zawsze na początku, zaś po przetworzeniu usuwa z listy jej pierwszy składnik, to taką strukturę nazywamy stosem. Stos można sobie wyobrazić jako słupek ułożonych jedna na drugiej monet - zawsze można dołożyć monetę na szczycie stosu a także można usunąć tylko monetę znajdującą się na szczycie. W organizacji stosu obowiązuje zasada ostatni przyszedł, pierwszy wychodzi. Lista dwukierunkowa Umieszczenie w rekordzie pola o nazwie Next zawierającego adres kolejnego składnika listy pozwala na poruszanie się po jej składnikach w jednym kierunku. Jeżeli oprócz pola Next rekord bedzie zawierał pole o nazwie Last zawierające adres składnika poprzedniego, to taką strukturę nazywamy listą dwukierunkową. Program przetwarzając listę dwukierunkową pamięta dwa wskaźniki: adres pierwszego jej elementu oraz adres elementu ostatniego. Pole Next pozwala na przejście po wszystkich składnikach od pierwszego do ostatniego, pole Last na przejście od składnika ostatniego do pierwszego. Do utworzenia dwukierunkowej listy liczb całkowitych potrzebne są następujące typy danych: Type WSkladnik2 = ^Skladnik2; Skladnik2 = Record X : Integer; Last, Next : WSkladnik2; End; Dla listy dwukierunkowej można zdefiniować analogiczne funkcje i procedury jak dla listy jednokierunkowej. W tym wypadku każda z funkcji i procedur powinna otrzymać jako argumenty dwa wskaźniki: Start i Koniec wskazujące odpowiednio adres pierwszego i ostatniego elementu listy. Zadania do samodzielnego rozwiązania Napisz program, który wydrukuje ilość dostępnej na stercie pamięci, utworzy dynamiczną zmienną typu Byte, po czym ponownie wydrukuje ilość dostępnej pamięci. (dop) Napisz program, który zużyje całą dostępną na stercie pamięć tworząc dynamiczne zmienne typu Byte i wydrukuje ilość utworzonych zmiennych. Następnie typ Bajt zamień na typ Word. Na podstawie wyników oceń prawdziwość następującego stwierdzenia: "Liczba możliwych do utworzenia dynamicznie zmiennych typu Word jest dwa razy mniejsza niż liczba zmiennych typu Byte". (dop) Dana jest liczba naturalna N typu 1..100 oraz ciąg N liczb typy Integer. Napisz program, który wydrukuje liczbę najwięcej różniącą się od średniej liczb tego ciągu. (dst) Napisz program, który pozwoli ocenić prawdziwość następującego stwierdzenia: "Jeżeli argumentem funkcji lub procedury jest wskaźnik, to funkcja lub procedura może zmieniać dowolnie wartość tego wskaźnika, niemniej jednak po zakończeniu funkcji lub procedury argument podany faktycznie w wywołaniu pozostaje bez zmian". (db) Napisz program, który pozwoli ocenić prawdziwość następującego stwierdzenia: "Jeżeli argumentem funkcji lub procedury jest wskaźnik, to funkcja lub procedura może zmieniać dowolnie wartość wskazywaną przez ten wskaźnik, niemniej jednak po zakończeniu funkcji lub procedury wartość wskazywana przez wskaźnik podany faktycznie w wywołaniu pozostaje bez zmian". (db) Liczba rzeczywista typu Real zajmuje sześć bajtów pamięci. Napisz program, który wydrukuje 48 bitowy zapis liczby podanej przez użytkownika. (dst) Zdefiniować funkcję Pos o nagłówku: Function Pos(Start : WSkladnik; Liczba : Integer) : Word; która zwróci miejsce pierwszego wystąpienia podanej liczby Liczba na liście jednokierunkowej lub liczbę zero jeżeli lista nie zawiera podanej liczby. (dst) Zdefiniuj funkcję Min o nagłówku: Function Min(T : Pointer; N : Word) : Word; która zwróci numer najmniejszej liczby w tablicy T zawierającej N liczb typu Real. (db) Zdefiniuj funkcję Max o nagłówku: Function Max(T : Pointer; N : Word) : Word; która zwróci numer najwiekszej liczby w tablicy T zawierającej N liczb typu Real. (db) Zdefiniuj funkcję Srednia o nagłówku: Function Srednia(T : Pointer; N : Word) : Real; która zwróci średnią arytmetyczną liczb w tablicy T zawierającej N liczb typu LongInt. (db) Zdefiniuj procedurę Kopiuj o nagłówku: Procedure Kopiuj(P1, P2 : Pointer; N : Word); która skopiuje N bajtów z miejsca pamięci wskazywanego przez wskaźnik P1 do miejsca pamięci wskazywanego przez wskaźnik P2. (bdb) Poprawić definicję procedury GetText w taki sposób, aby ostatni jej argument był typu Pointer. Analogiczną zmianę wykonać w procedurze PutText. (db) Zdefiniować procedurę o nagłówku: Procedure LUsun(Var Start : WSkladnik); usuwającą całą listę. (db) Zdefiniować funkcję LDodajN o nagłówku: Function LDodajN(Var Start : WSkladnik; Liczba : Integer; N : Word) : Boolean; która doda do listy kolejną liczbę Liczba w taki sposób, aby liczba ta po dodaniu była N-tym składnikiem listy. Jeżeli N jest większe niż rozmiar listy, to należy dodać składnik na jej końcu, jeżeli N jest równe 0 lub 1 to dodajemy składnik na początku listy. Funkcja powinna zwrócić wartość True w przypadku powodzenia operacji, w przypadku braku pamięci zwracamy wartość False. (bdb) Zdefiniować procedurę LUsunN o nagłówku: Procedure LUsunN(Var Start : WSkladnik; N : Word); usuwającą z listy N-ty jej składnik. Jeżeli N jest liczbą równą zero lub też większą niż rozmiar listy procedura nie powinna wykonywać żadnych czynności. (bdb) Dany jest ciąg liczb całkowitych zakończony liczbą zero (liczba zero nie jest elementem ciągu). Napisz program, który wczyta ten ciąg a następnie wydrukuje liczbę najmniej różniącą się od średniej arytmetycznej wszystkich liczb tego ciągu. (db) Dany jet ciąg liczb całkowitych zakończony liczbą zero, które nie jest elementem ciągu. Napisz program, który umieści liczby ciągu na liście jednokierunkowej a następnie wydrukuje liczby tego ciągu w kolejności malejącej. (bdb) Dany jest ciąg napisów (mogą to być zdania) zakończony napisem '*' (napis '*' nie jest elementem ciągu). Napisz program który wydrukuje wszystkie słowa wystepujące we wszystkich napisach w kolejności rosnącej. Każde słowo powinno być drukowane tylko jeden raz. Program powinien podać ilość wystąpień każdego słowa we wszystkich napisach. W programie należy zdefiniować typ rekordowy przeznaczony do utworzenia dynamicznej listy słów. Rekord oprócz pola typu String powinien zawierać pole Ilosc, w którym będzie przechowywana ilość wystąpień danego słowa we wszystkich napisach. (bdb) Zdefiniować funkcję WindScr o nagłówku: Function WindScr(X1, Y1, X2, Y2, Attr : Byte; Start : WSkladnik) : Word; która pozwoli na wybór jednego z napisów i zwróci numer wybranego napisu lub liczbę zero. Argument WSkladnik jest wkaźnikiem na rekord o nazwie Skladnik zawierający pole typu String w którym przechowywany jest napis - składnik ten jest pierwszym elementem listy jednokierunkowej. Można wykorzystać typy i funkcje zdefiniowane w zadaniu poprzednim. Funkcja po zakończeniu działania powinna przywrócić zawartość ekranu przykrytą przez okno w którym wyświetlane są napisy. (bdb) Zdefiniować moduł o nazwie MyList zawierający definicję typów WSkladnik oraz Skladnik, a także funkcje i procedury pozwalające na przetwarzanie jednokierunkowej listy napisów. (dst) Zdefiniować moduł o nazwie MyList2 zawierający definicję typów WSkladnik2 oraz Skladnik2, a także funkcje i procedury pozwalające na przetwarzanie dwukierunkowej listy napisów. (bdb) <<---- Spis treści

Wyszukiwarka