Programowanie komputerów
Transkrypt
Programowanie komputerów
Programowanie komputerów Jacek Lach Zakład Oprogramowania Instytut Informatyki Politechnika Śląska Plan • Dynamiczne struktury danych • Lista jednokierunkowa Jacek Lach. Programowanie komputerów • Lista dwukierunkowa • Lista podwieszana • Graf • Drzewa BST Dynamiczne struktury danych Jacek Lach. Programowanie komputerów • Struktura dynamiczna – struktura tworzona w trakcie działania programu • Najczęstsze zastosowanie – tworzenie abstrakcyjnych typów/struktur danych (szczególnie przydatne w zastosowaniach algorytmicznych) Dynamiczne struktury danych • Przykłady struktur: • lista jednokierunkowa Jacek Lach. Programowanie komputerów • lista dwukierunkowa • drzewo • graf • UWAGA: każdą z w/w struktur można zrealizować stosując struktury statyczne (np. tablice) Lista jednokierunkowa • Składa się z pewnej liczby węzłów (elementów), z których każdy zawiera Jacek Lach. Programowanie komputerów • pewien zestaw danych • adres następnego elementu • Dodatkowo należy zapamiętać adres pierwszego węzła (tzw. głowa) • Ostatni element wyróżnia się poprzez nadanie adresowi określonej wartości, którą można odpowiednio zinterpretować, tzn. inaczej niż adres (w języku C: NULL) Lista jednokierunkowa Jacek Lach. Programowanie komputerów Głowa Dane Dane Dane Adres Adres Adres (NULL) Definicja elementu Jacek Lach. Programowanie komputerów • Elementem listy jest najczęściej zmienna typu struktura #define MAX_ROZMIAR 50; /* definicja elementu listy */ struct elem_tag { char wyraz[MAX_ROZMIAR+1]; struct elem_tag *nast; }; /* korzystamy ze zmiennych wskaźnikowych glowa – jeśli jest zmienna statyczna, jest inicjowana wartością 0, czyli NULL */ struct elem_tag *glowa; Wyświetlanie listy • Styl Pascala: void wypisz ( char *Akomunikat ) { Jacek Lach. Programowanie komputerów struct elem_tag *p; printf("\n%s \n", Akomunikat); } /* wersja odpowiadająca programowi w "pascalu" */ p = glowa; while (p) { printf ( "kolejny wyraz to: %s\n", p->wyraz ); p = p->nast; } Wyświetlanie listy • Styl C: void wypisz (char *Akomunikat) { Jacek Lach. Programowanie komputerów struct elem_tag *p; printf("\n%s \n", Akomunikat); } for(p=glowa;p;p=p->nast) printf ( "kolejny wyraz to: %s\n", p->wyraz ); Jacek Lach. Programowanie komputerów Metoda fifo glowa ostatni nowy Jacek Lach. Programowanie komputerów Metoda fifo glowa ostatni nowy Metoda fifo Jacek Lach. Programowanie komputerów /* wczytanie wyrazow i utworzenie listy zgodnie z zasadą nowy na końcu listy */ void wczytaj_fifo ( void ) { char buf[MAX_ROZMIAR+1]; struct elem_tag *nowy, *ostatni; printf ( "wpisz [%s] aby zakonczyc\n", KONIEC ); // jesli wczytywanym wyrazem nie jest 'koniec' while (wczytaj_jeden_wyraz(buf)) { /* próba przydzielenia pamięci */ nowy = ( struct elem_tag* )malloc( sizeof(struct elem_tag) ); /* prawdopodobnie brak pamięci */ if (!nowy) exit(1); Metoda fifo Jacek Lach. Programowanie komputerów /* kopiowanie wczytanego wyrazu */ strcpy( nowy->wyraz, buf ); nowy->nast=NULL; /* pierwszy element listy - głowa wskazuje NULL */ if (!glowa) glowa=nowy; else ostatni->nast=nowy; } } /* nowo utworzony element listy jest jej ostatnim elementem */ ostatni=nowy; Jacek Lach. Programowanie komputerów Metoda lifo glowa ostatni nowy Jacek Lach. Programowanie komputerów Metoda lifo glowa nowy ostatni Metoda lifo Jacek Lach. Programowanie komputerów /* wczytanie wyrazow i utworzenie listy zgodnie z zasadą nowy na początku listy */ void wczytaj_lifo ( void ) { char buf[MAX_ROZMIAR+1]; struct elem_tag *poprzednia_glowa; printf ( "wpisz [%s] aby zakonczyc\n", KONIEC ); /* jesli wczytywanym wyrazem nie jest 'koniec' */ Metoda lifo Jacek Lach. Programowanie komputerów while (wczytaj_jeden_wyraz(buf)) { /* zapamietuje poprzednia glowe */ poprzednia_glowa=glowa; /* próba przydzielenia pamięci */ glowa = ( struct elem_tag* ) malloc( sizeof(struct elem_tag) ); /* prawdopodobnie brak pamięci */ if (!glowa) exit(1); /* wczytanego wyrazu */ strcpy( glowa->wyraz, buf ); /* lacze z poprzednia glowa */ glowa->nast=poprzednia_glowa; } } Usuwanie listy z pamięci /* usunięcie listy z pamięci */ void kasuj ( void ) { Jacek Lach. Programowanie komputerów struct elem_tag *p; } /* zaczynamy od pierwszego elementu */ while (glowa) { /* zapamiętanie adresu następnego elementu */ p = glowa->nast; /* zwolnienie pamięci */ free (glowa); /* glowa wskazuje na kolejny element */ glowa = p; } Lista jednokierunkowa • Wstawianie elementu na pewnej pozycji Jacek Lach. Programowanie komputerów • Dane: • wartości atrybutów • numer pozycji na której należy wstawić element Wstawianie Jacek Lach. Programowanie komputerów Dane Głowa Dane Dane Dane Adres Adres Adres (NULL) Wstawianie • Metoda 1 • Przesuń się na element o danym numerze Jacek Lach. Programowanie komputerów • Wstaw element do listy • Uzupełnij połączenia Wstawianie Jacek Lach. Programowanie komputerów Dane Adres Głowa Dane Dane Dane Adres Adres Adres (NULL) Dodawanie – v1 void dodaj_po_elem_v1(struct elem_tag *q, char *wyraz) { Jacek Lach. Programowanie komputerów struct elem_tag *nowy; /* próba przydzielenia pamięci kolejnemu elementowi */ nowy = ( struct elem_tag* ) malloc( sizeof(struct elem_tag) ); /* prawdopodobnie brak pamięci */ if (!nowy) exit(1); /* przepisanie wczytanego wyrazu */ strcpy( nowy->wyraz, wyraz ); Dodawanie – v1 Jacek Lach. Programowanie komputerów } /* połączenie listy */ /* na koncu listy zostanie skopiowany NULL */ nowy->nast = q->nast; q->nast = nowy; void dodaj_na_poz_v1(int poz, char *wyraz) { /* wyszukaj wskaźnik na kolejną pozycję */ for(p = glowa ;p && poz; p=p->nast, poz--); } if (p) dodaj_po_elem_v1(p, wyraz); Dodawanie – v1 • Wady Jacek Lach. Programowanie komputerów • Trudno dodać element na pozycji 0 • By dodać element na pozycji 0 należałoby modyfikować wskaźnik na pierwszy element, co skomplikowałoby dodatkowo kod • Rozwiązanie • Wykorzystać wskaźniki na wskaźniki Dodawanie Dane Jacek Lach. Programowanie komputerów Adres Głowa Dane Dane Dane Adres Adres Adres (NULL) Dodawanie Jacek Lach. Programowanie komputerów struct elem_tag * Głowa Głowa Głowa Dane Dane Dane Adres Adres Adres Dane Dane Adres Adres Dane Dane Dane Adres Adres Adres Dane Dane Dane Adres Adres Adres struct elem_tag ** Dodawanie – v2 void dodaj_po_v2(struct elem_tag **pp, char *wyraz){ Jacek Lach. Programowanie komputerów /* próba przydzielenia pamięci kolejnemu elementowi */ p = ( struct elem_tag* )malloc( sizeof(struct elem_tag) ); if (!p) exit(1); /* prawdopodobnie brak pamięci */ /*przepisanie wczytanego wyrazu */ strcpy( p->wyraz, wyraz ); // p jest wskaznikiem na nowoutworzony rekord, zawierajacy już slowo /* połączenie listy */ p->nast = *pp; // pp jest adresem glowy, *pp jest glowa, a zatem wskaznikem // na pierwszy element listy; operacja powoduje, ze kopiujemy glowe // do pola nast *pp = p; // *pp jest glowa, kopiuje wskazania na nowoutworzony // element do glowy } Dodawanie – v2 void dodaj_na_pozycji_v2(int poz, char *wyraz) { Jacek Lach. Programowanie komputerów struct elem_tag **pp; /* wyszukaj wskaźnik na kolejną pozycję */ for (pp = &glowa ;*pp && poz; pp=&((*pp)->nast), poz--); // pp jest adresem glowy w PAO } // jeżeli glowa istniala if (pp) dodaj_po_v2(pp, wyraz); Lista dwukierunkowa • Elementem listy dwukierunkowej jest najczęściej zmienna typu struktura Jacek Lach. Programowanie komputerów #define MAX_STR 50; struct elem_tag { /* dane */ int pole1; char [MAX_STR+1] pole2; double pole3; /* adres następnego elementu listy */ struct elem_tag *nast, *poprz; } glowa_1, glowa_2 Lista dwukierunkowa • Składowe listy wielokierunkowej • Dane: wartości atrybutów Jacek Lach. Programowanie komputerów • Wskaźnik na następny element • Wskaźnik na poprzedni element • Dwa wskaźniki na elementy (pierwszy i ostatni) Lista dwukierunkowa Jacek Lach. Programowanie komputerów Głowa_2 Głowa_1 Dane Dane Dane Adres Adres Adres (NULL) Adres (NULL) Adres Adres Wstawianie elementu Dane Adres Jacek Lach. Programowanie komputerów Adres Głowa_2 Głowa_1 Dane Dane Dane Adres Adres Adres (NULL) Adres (NULL) Adres Adres Tworzenie listy void dodaj_do_listy(char *buf) { /* próba przydzielenia pamięci elementowi */ struct elem_tag *p= (struct elem_tag* ) malloc( sizeof(struct elem_tag) ); Jacek Lach. Programowanie komputerów if (!p) exit(1); /* prawdopodobnie brak pamięci */ strcpy( p->wyraz, buf ); /*przepisanie wczytanego wyrazu */ if (!glowa_1) /* jesli lista nie istniała */ { glowa_1=glowa_2=p; p->nast=p->poprz=0; } else /* jeśli był choć jeden element listy */ { glowa_2->nast = p; p->poprz = glowa_2; glowa_2 = p; } } Metoda wstawiania • Mamy trzy przypadki Jacek Lach. Programowanie komputerów • Dodawanie elementu na pozycji 0 • Dodawanie elementu na pozycji n, gdzie 0<=n<liczba elementów listy • Dodawanie elementu jako ostatniego elementu listy • Pominiemy jawne rozróżnianie tych warunków (ćwiczenie) i przejdziemy do bardziej wyrafinowanego rozwiązania Wstawianie na danej pozycji void dodaj_na_pozycji_v2(int poz, char *wyraz) { Jacek Lach. Programowanie komputerów struct elem_tag **pp; for(pp = &glowa_1 ;*pp && poz; pp=&((*pp)->nast), poz--); if (pp) dodaj_po_v2(pp, wyraz); } Wstawianie na danej pozycji Jacek Lach. Programowanie komputerów void dodaj_po_v2(struct elem_tag **q, char *wyraz) { struct elem_tag *p = (struct elem_tag* ) malloc( sizeof(struct elem_tag) ); if (!p) exit(1); /* prawdopodobnie brak pamięci */ } strcpy( p->wyraz, wyraz ); p->nast = *q; if(*q) { p->poprz=(*q)->poprz; (*q)->poprz = p; } else { p->poprz=glowa_2; glowa_2 = p; } *q = p; Wstawianie na danej pozycji – inny zapis Jacek Lach. Programowanie komputerów void dodaj_po_compact(struct elem_tag **q, char *wyraz) { /* n - adres elementu, który ma stać się następny */ /* p - adres pola poprz do zmodyfikowania, adres pola poprz następnego rekordu lub głowa */ struct elem_tag *n = *q, **p = n ? &(n->poprz):&glowa_2; /* q jest adresem (głowy lub pola nast) do zmodyfikowania */ /* wartość wskazania *q została zapamiętana w n, tak więc if(!(*q = ( struct elem_tag* )malloc( sizeof(struct elem_tag)))) exit(1); strcpy( (*q)->wyraz, wyraz ); (*q)->nast = n; /* połączenie z następnym */ (*q)->poprz = *p; /* pole poprz kopiujemy z rekordu, który stał się następny */ *p = *q; /* pole poprz z następnego rekordu na nasz */ } Wstawianie na danej pozycji – inny zapis Jacek Lach. Programowanie komputerów • Uwaga: • Jak widać na poprzednim przykładzie, pewne konstrukcje języka C mogą prowadzić do zmniejszania się czytelności kodu. Należy raczej unikać takiego stylu programowania, chyba że istnieją ku temu obiektywne powody (wielkość kodu, szybkość działania itp.) Struktury danych implementowane jako listy • Stos (Stack) • Last In First Out (LIFO) Jacek Lach. Programowanie komputerów • Operacja push • Operacja pop • Kolejka (Queue) • First In First Out (FIFO) • Operacja put • Operacja get Jacek Lach. Programowanie komputerów Listy podwieszane: definicje • Mamy to do czynienia z sytuacją, gdy jedna z list o organizacji liniowej, zawiera wskaźniki do innych list, niejako podwieszonych pod listą główną • Elementem listy dwukierunkowej jest najczęściej zmienna typu struktura Listy podwieszane Jacek Lach. Programowanie komputerów struct elem_item { /* jakieś dane */ } /* adres elementu */ struct elem_item *dol; struct elem_tag { /* jakieś dane */ /* następny element */ struct elem_tag * nast; /* lista podwieszana */ struct elem_item *dol; } glowa; Listy podwieszane Jacek Lach. Programowanie komputerów Głowa_1 Dane Dane Dane Adres Adres Adres (NULL) AdresD AdresD (NULL) AdresD Dane Dane AdresD AdresD (NULL) Dane AdresD Dane AdresD (NULL) Szczególny przypadek: graf Głowa_1 Jacek Lach. Programowanie komputerów Dane o wierzchołku 1 1 2 3 Dane o wierzchołku 2 Dane o wierzchołku 3 Adres Adres Adres (NULL) AdresD AdresD (NULL) AdresD Adres Adres AdresD AdresD (NULL) Adres AdresD Adres AdresD (NULL) Zalety reprezentacji • Mała objętość w porównaniu do metod opartych o tablice, takich jak macierz sąsiedztwa, tablice wierzchołków wchodzących, wychodzących itp. Jacek Lach. Programowanie komputerów • Wady – zmniejszenie czytelności algorytmów grafowych • Dany wierzchołek grafu G=(N,E) można znaleźć w O(|N|) krokach • Wierzchołek można dodać w O(1) krokach • Krawędź można znaleźć, dodać lub usunąć w O(|N|+|E|) krokach (znalezienie wierzchołka, a następnie znalezienie, dodanie lub usunięcie krawędzi wychodzących) • Reprezentacja zalecana dla grafów dla których |N|2>>|E| ; w przypadku grafów o dużej liczbie krawędzi w stosunku do kwadratu liczby wierzchołków lepiej zastosować struktury statyczne Drzewa binarne Jacek Lach. Programowanie komputerów korzeń Data Data Data Data Data Data liście Data Drzewo BST (Binary Search Tree) • Każdy element zawiera klucz Jacek Lach. Programowanie komputerów • Klucze są takiej postaci, że można je uporządkować liniowo • Wszystkie elementy w lewym poddrzewie mają klucz mniejszy niż klucz rodzica • Wszystkie elementy w prawym poddrzewie mają klucze większe niż klucz poddrzewa BST – przykład Jacek Lach. Programowanie komputerów korzeń 15 lewe poddrzewo; wszystkie klucze lewego poddrzewa są mniejsze niż klucz korzenia 6 4 12 9 prawe poddrzewo; wszystkie klucze prawego poddrzewa są większe niż klucz korzenia 19 17 liść 22 20 25 BST • Operacje: • Poszukiwanie elementu Jacek Lach. Programowanie komputerów • Poszukiwanie poprzednika • Poszukiwanie następcy • Poszukiwanie minimum • Poszukiwanie maksimum • Wstawianie elementów • Usuwanie elementów • Wypisywanie elementów Rekord drzewa BST typedef struct bstel Jacek Lach. Programowanie komputerów { int data; struct bstel *left; struct bstel *right; } bn; Jacek Lach. Programowanie komputerów Poszukiwanie elementu int find(int key, struct bstel *node) { if (!node) return 0; /* nie znaleziono */ if (key == node->data) return 1; /* znaleziono */ if (key < node->data) return find(key, node->left); else return find(key, node->right); } Jacek Lach. Programowanie komputerów Wstawianie elementu int insert(bn *p, bn **root) { if (*root && (*root)->data == p->data) return 1; /* już jest w drzewie */ if (!*root) { /* drzewo bez elementów */ *root = p; return 0; } else if (p->data > (*root)->data) insert(p,&((*root)->right)); else insert(p,&((*root)->left)); return 0; } Jacek Lach. Programowanie komputerów Wstawianie elementu int ins_el(int key, bn **root) { bn *p; p = (bn*)malloc(sizeof(bn)); if (!p) return -1; /* brak pamięci */ p->data = key; p->left = p->right = NULL; return insert(p,root); } Jacek Lach. Programowanie komputerów Poszukiwanie minimum struct bstel* min(struct bstel *node) { if (!node) return NULL; /* błąd */ while (node->left) node = node->left; return node; } Jacek Lach. Programowanie komputerów Poszukiwanie maksimum struct bstel* max(struct bstel *node) { if (!node) return NULL; /* błąd */ while (node->right) node = node->right; return node; } Znajdowanie poprzednika i następnika • Wartość succ(17)=19 • Jak znaleźć taki węzeł: Jacek Lach. Programowanie komputerów • jeśli węzeł ma prawe poddrzewo, zwróć minimalny wierzchołek w prawym poddrzewie • jeśli węzeł nie ma prawego poddrzewa, znajdź wierzchołek, do którego lewego poddrzewa należy dany węzeł • jeśli warunki nie są spełnione, węzeł nie posiada następnika (posiada maksymalną wartość) 15 6 4 19 12 9 17 22 20 25 Jacek Lach. Programowanie komputerów Znajdowanie następnika struct bstel* succ(struct bstel *node) { bn* y; if (!node) return NULL; /* błąd */ if (node->right) return min(node->right); y = parent(node); /* zaimplementować */ while (y && node==y->right) { node = y; y = parent(y); } return y; } Usuwanie elementów • Załóżmy, że należy usunąć x Jacek Lach. Programowanie komputerów • Jeśli x ma obydwa poddrzewa, znajdź succ(x) i przesuń go na miejsce x 15 • Jeśli x ma tylko jedno poddrzewo, zastąp x tym poddrzewem • Jeśli x jest liściem, po prostu usuń x 6 4 19 12 9 17 22 20 25 Jacek Lach. Programowanie komputerów Wydruk całego drzewa void print_tree(bn *node) { if (node) { print_tree(node->left); printf("%d ",node->data); print_tree(node->right); } } Wykorzystanie Jacek Lach. Programowanie komputerów main() { bn *root = NULL; /* na początku puste drzewo */ ins_el(10,&root); ins_el( 6,&root); ... ins_el( 7,&root); ins_el(23,&root); ins_el(11,&root); /* budowanie drzewa */ print_tree(root); /* powinno być posortowane! */ printf(”\n %d \n", min(root)->data); printf("find(%d)=%d\n",13,find(13,root)); return 0; }