background image

Podstawowe struktury danych 

 

1) Listy 

 
Lista to skończony ciąg elementów: q=[x

1

, x

2

, ... , x

n

 ]. 

Skrajne elementy x

1

 i x

n

  nazywamy końcami listy, a 

wielkość |q| = n długością (rozmiarem) listy. 
Szczególnym przypadkiem jest lista pusta: q = [ ]. 
 
Podstawowe  abstrakcyjne  operacje  na  listach                      
q =[x

1

, x

2

, ... , x

n

 ]  i  r =[y

1

, y

2

, ... , y

m

 ] dla   1 

 i  j  n  to: 

•  dostęp do elementu listy - q[i] = x

i

 ; 

•  podlista - q[i..j] = [x

, x

i+1 

, ... , x

j

 ] ; 

•  złożenie - q&r = [x

, ... , x

n

 , y

1

, ... ,y

m

 ] ; 

 
Na podstawie operacji podstawowych można zdefiniować 
inne operacje, np. wstawianie elementu x za element x

i

,  na 

liście q:  q[1..i] & [x] & q[i+1 .. |q| ]
 
W operacjach na listach ograniczamy się zwykle do zmian 
ich końców: 
a) 

front(q)

 = q[1]          - pobierz lewy koniec listy 

 
b) 

push(q,x)

 = [x]&q    - wstaw element na lewy koniec 

 
c) 

pop(q) 

=q[2..|q| ]       - usuń bieżący lewy koniec   

 
d) 

rear(q)

 = q[|q|]         - pobierz prawy koniec listy 

 
e) 

inject(q,x) 

=q&[x]    -wstaw element na prawy koniec  

 
f) 

eject(q)

 =q[1..|q|-1 ] - usuń bieżący prawy koniec 

 

background image

W zależności od możliwości wykonania różnych operacji 
wyróżniemy: 
• 

 kolejkę podwójną

  -  wszystkie sześć operacji 

•   

stos 

                         - tylko operacje front, push, pop 

•   

kolejkę 

                   - tylko operacje front, pop , inject 

 
Dwie podstawowe implementacje (reprezentacje) listy       
q =[x

1

, x

2

, ... , x

n

 ] to: 

•  tablicowa - q[i] = x

i

 , gdzie 1

 i 

 n, 

•  dowiązaniowa  

 
W implementacjach pojedynczej liniowej i podwójnej 
liniowej dowiązanie prowadzące do listy wskazuje na 
pierwszy element na liście. 
 
W implementacji pojedynczej cyklicznej i podwójnej 
cyklicznej dowiązanie prowadzące do listy wskazuje na 
element ostatni. 

background image

Aby dowiązana struktura nigdy nie była pusta dodaje się 
na początku listy element pusty zwany 

GŁOWĄ 

lub 

WARTOWNIKIEM

 listy. 

 
Operacje na listach o stałej złożoności czasowej: 
a)  w implementacji pojedynczej liniowej: operacje stosu, 

wstawianie jednego elementu za drugi, usuwanie 
następnego elementu, 

b) w implementacji pojedynczej cyklicznej: te co w a) oraz 

złożenie i operacje rear inject

c) w implementacji podwójnej cyklicznej: te co w b) oraz 

eject,  wstawianie jednego elementu przed drugim, 
wstawianie danego elementu, odwracanie listy. 

 

LISTY JEDNOKIERUNKOWE 

 
Lista jednokierunkowa jest oszczędną pamięciowo 
strukturą danych, pozwalającą grupować dowolną  - 
ograniczoną tylko ilością dostępnej pamięci - liczbę 
elementów: liczb, znaków, rekordów. 
 
Do budowy listy używane są dwa typy rekordów: 

informacyjny

 - wskaźniki, dowiązania do początku listy 

(głowa) i końca listy (ogon) 

robocze

 - pole wartości i wskaźnik do następnego 

elementu listy 

 
Dzięki rekordowi informacyjnemu mamy ciągły  dostęp do 
niektórych operacji, np. dołączanie elementu na koniec 
listy. 
 

Głowa, ogon i następny 

to wskaźniki,

  wartość 

to dowolna 

wielkość (znanego typu).

 

background image

 

 
 
 
 
Wskaźniki NULL oznaczają adresy pamięci pod którymi 
nie ma żadnej zmiennej.  
 
Przykład listy jednokierunkowej: 
 
 

 

 

background image

Pola głowa i ogon pozwalają na przeglądanie elementów 
listy i dołączanie nowych elementów. 
 
Przykład (pseudokod) przeglądania elementów listy: 
_________________________________________________ 
adres_tmp=info.głowa; 
while

 (adres_tmp <> NULL ) 

    { 
      if(adres_tmp.wartość == x) 
      { 
       Wypisz "Znalazłem szukany element" 
       opuść procedurę 
       } 

       else   
       

adres_tmp=adres_tmp.następny 

    } 
Wypisz

 "Nie znalazłem elementu"     

_________________________________________ 
 
Dokładanie nowych elementów (dwa podejścia): 
1)potraktowanie listy jak worek nie-uporządkowanych    
  elementów i umieszczanie nowych elementów na  
  początku 
 

 

background image

2) dokładanie elementów we właściwym ustalonym przez 

użytkownika porządku (całość listy musi być widziana 
jako posortowana) 

 

 
Możliwe są trzy przypadki: 

a) wstawiamy element na początek listy 

b) wstawiamy element na koniec listy 

c) wstawiamy element gdzieś w  środku 

 
W każdym z przypadków musimy zapamietywać dwa 
wskaźniki - przed
 który element wstawić i po którym 
mamy to zrobić. 

 

 
 
 

background image

Podobnie postępujemy przy fuzji (łączeniu) list tak by 
wypadkowa lista pozostała uporządkowana. 
 

 
Podsumowując wady i zalety list jednokierunkowych: 
 

Wady Zalety 

nienaturalny dostęp do 

elementów 

małe zużycie pamięci 

 

niełatwe sortowanie 

elastyczność 

 
 
Lista w której elementy są już na samym początku 
wstawiane w określonym porządku, służy obok 
gromadzenia danych, także do ich porządkowania. 
 
W sytuacji, gdy jest tylko jedno kryterium sortowania 
struktura działa bardzo dobrze "sama" dbając o 
sortowanie. 

background image

 
Dla kilku kryteriów sortowania należy wprowadzić obok 
listy danych, także kilka list z wskaźnikami do danych - list 
tych powinno byś tyle ile kryteriów sortowania. 
Sortowanie w takich wypadku polega na porządkowaniu 
wskaźników bez ruszania listy danych. 

 
Nieposortowaną listę DANE można uporządkować według 

zech kryteriów: 

tr
 

background image

-  imienia i nazwiska (Adam Fuks, Jan Kowalski, Michał 

Zaremba) 

-  kodów ( 30, 34, 37) 
-  kwot ( 1200, 2000, 3000 ) 
 
Tablicowa implemantacja list jest niezwykle prosta jeśli 
umówimy się, że  i
-temu indeksowi tablicy odpowiada i-ty 
element listy. Wymagana jest dodatkowa informacja 
wskazująca jak wiele elementów liczy lista (jak duża musi 
być tablica). 

  
Wadą jest marnotrawstwo pamięci bo najczęściej 
przydzielamy na tablicę większy obszar pamięci niż to 
zwykle potrzeba.  
 
Operacje na listach są w implementacji tablicowej proste: 
1) 

front(q)

 

, x=q[1] 

    - pobierz lewy koniec listy 

2) 

push(q,x)

 - przesuń wszystkie elementy tablicy o jeden w 

prawo i 

q[1]=x

      - wstaw element na lewy koniec 

3) 

pop(q)

,  przesuń wszystkie elementy tablicy poza 

pierwszym o jeden w lewo     - usuń bieżący lewy koniec   

4) 

rear(q), x=q[n]

         - pobierz prawy koniec listy 

5) 

inject(q,x), q[n+1]=x

    -wstaw element na prawy koniec  

6) 

eject(q), n=n-1

 - usuń bieżący prawy koniec 

 

background image

Dodatkowo: 
 
A)  usunięcie  k
-tego elementu to - przesunąć w lewo 

elementy tablicy q[k+1]...q[n], n=n-1 

B)  wstawienie elementu na pozycję  k to - przesunąć w 

prawo elementy tablicy q[k]...q[n], n=n+1 

 
 
 
 

LISTY DWUKIERUNKOWE 

 
Listy jednokierunkowe są wygodne i zajmują mało 
pamięci. Operacje na nich zajmują dużo czasu. 
 
W liście dwukierunkowej komórka robocza zawiera 
wskaźniki do elementów: poprzedniego 
następnego . 
•  pierwsza komórka na liście nie posiada swojego 

poprzednika (pole poprzedni zawiera NULL  wskaźnik 
pokazujący pusty element pamięci) 

•  ostatnia komórka na liście nie posiada swojego 

następnika (pole następny zawiera NULL wskaźnik 
pokazujący pusty element pamięci) 

 
Lista dwukierunkowa jest kosztowna jeśli chodzi o pamięć, 
ale wygodna gdy chodzi o szybkość.  
 
 

background image

Usunięcie elementu listy dwukierunkowej: 

 
Lista cykliczna jest zamknięta w pierścień , wskaźnik 
„ostatniego” elementu wskazuje na „pierwszy” element. 
 
Elementy „pierwszy” i „ostatni” są umowne. 
 

  

STOSY 

 
Stos jest struktura danych, do której dostęp jest możliwy 
tylko od strony tzw. wierzchołka, czyli pierwszego wolnego 
miejsca znajdującego się na nim. 
 
Funkcje odkładania elementu X na stos ( 

push(X)

 ) i 

pobieranie go ze stosu ( 

pop(X)

 ) można opisać 

symbolicznie (wraz z kodem błędu s wprowadzonym przez 
użytkownika): 

background image

 

 
Tablicowa implementacja stosu wygląda analogicznie jak 
dla listy, ale z dostępnymi jedynie operacjami 

front

push

pop

.