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 i, x i+1,..., x j ] ; złożenie - q&r = [x 1,..., 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
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.
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 i inject, c) w implementacji podwójnej cyklicznej: te co w b) oraz eject, wstawianie jednego elementu przed drugim, 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).
Wskaźniki NULL oznaczają adresy pamięci pod którymi nie ma żadnej zmiennej. Przykład listy jednokierunkowej:
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
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ć.
Podobnie postępujemy przy fuzji (łączeniu) list tak by wypadkowa lista pozostała uporządkowana. Podsumowując wady i zalety list jednokierunkowych: Wady nienaturalny dostęp do elementów niełatwe sortowanie Zalety małe zużycie pamięci 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.
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 trzech kryteriów:
- 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
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 i 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ść.
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):
Tablicowa implementacja stosu wygląda analogicznie jak dla listy, ale z dostępnymi jedynie operacjami front, push, pop.