Struktury dynamiczne lista jednokierunkowa lista dwukierunkowa lista cykliczna stos kolejka drzewo Ich wielkość i stopień złożoności zmieniają się w czasie. Struktury dynamiczne oparte są o struktury (struct). Sposób definicji komponentów struktur związany jest z funkcjonalnościami i przeznaczeniem struktur. Poniżej znajdują się opisy implementacji struktur dynamicznych, jak również fragmenty kodów w języku c z podstawowymi działaniami w poszczególnych przypadkach.
Lista jednokierunkowa Lista jednokierunkowa zawiera pewne dane (elementy listy) tak jak struktura, oraz wskaźnik na następny element listy. Ostatni element listy jednokierunkowej posiada wskaźnik ustawiony na. Dostęp do listy umożliwia wskaźnik ustawiony na pierwszy element listy. Przeglądanie listy i pobieranie jej elementów (wstawianie tam wartości) możliwe jest dzięki operatorowi wskaźnikowemu ->. start next (1) (2) (3) (N) struct lista_jednokierunkowa { typ dana1; typ dana2;... typ danan; struct lista_jednokierunkowa *next; ;
Lista jednokierunkowa Przykład: 1. Zdefiniowanie struktury. struct list_element { int data1; int data2; struct list_element *next; ; 2. Deklaracja wskaźników typu strukturalnego (takiego jak element listy) i ustawienie ich tymczasem na : struct list_element *start=, *pointer=;
Lista jednokierunkowa 3. Dynamiczne przydzielenie pamięci dla struktury i ustawienie wskaźnika start na tym obszarze pamięci czyli na tym pierwszym elemencie listy: start = malloc ( sizeof (struct list_element) ); start 4. Ustawienie wskaźnika next na gdyż struktura ta jest jednocześnie ostatnim elementem listy. start->next = ; 5. Przy okazji nadamy wartości polom w pierwszym elemencie listy start->data1 = 100; start->data2 = 100;
Lista jednokierunkowa Bieżący stan start 6. Utworzenie następnego elementu listy, dynamicznie. Tym razem ustawimy na ten obszar pamięci wskaźnik pointer. pointer = malloc ( sizeof (struct list_element) ); Po tej operacji sytuacja wygląda następująco: dwa elementy listy jeszcze ze sobą nie skojarzone: pointer start
Lista jednokierunkowa 7. Następnie nadamy wartosci polom elemntowi listy, pokazywanemu wskaźnikiem pointer ale, co najważniejsze, połączymy elementy ze sobą w listę dwuelementową pointer->data1 = 200; pointer->data2 = 200; pointer->next = start; //!! po tych operacjach sytuacja wygląda następująco pointer start next 8. Na koniec ustawiamy wskaźnik start na początku listy i kończymy tym samym dodanie nowego elementu na początek listy. start=pointer; Na koniec sytuacja wygląda następująco: pointerstart next
Lista jednokierunkowa Teraz wypiszemy wartości elementów listy, poruszając się po elementach listy przy pomocy zdefiniowanych wcześniej wskaźników: while (pointer!= 0){ printf( dana1 = %d, dana2 = %d\n, \ pointer->dana1, pointer->dana2); pointer=pointer->next; //!! Wynik działania będzie następujący (dla przykładowej listy wcześniej zdefiniowanej, dwuelementowej): dana1 = 200, dana2 = 200 dana1 = 100, dana2 = 100
Lista jednokierunkowa - przyklad #include <stdio.h> #include <stdlib.h> //deklaracja elementu listy jednokier struct onedir { int data1; int data2; struct onedir *next; ; int main (){ 10 //deklaracja wskaznika do pierwsz. elementu listy i alokacja struct onedir *start; start = (struct onedir*)malloc(sizeof (*start) ); //ustawienie wskaznika next na bo to ostatni(!) elem. listy //kolejne elementy bedziemy wstawiac do listy na poczatek, nie na koniec start >next=; start >data1 = 100; 20 start >data2 = 100; //nastepny element listy, //ale bedzie on (pozniej) w kolejnosci pierwszy struct onedir *pointer; pointer = (struct onedir*)malloc(sizeof (*pointer) ); pointer >data1=200; pointer >data2=200; //ustawienie wskaznika elementu na elemencie nastepnym 30 pointer >next=start;
//ustawimy wskaznik start na pocztaku listy //w tym momencie obydwa wskazniki (pointer oraz start) //pokazuja na poczatek listy czyli na pierwszy element start=pointer; //nastepny element listy, chwilowo "niezlokalizowany" //ale za chwile ustawimy go na poczatku //"przesuwajac" pozostale 40 pointer = (struct onedir*)malloc(sizeof (*pointer) ); pointer >data1=300; pointer >data2=300; //ustawienie wskaznikow; jakby ustawienie elementu listy //na poczatku listy pointer >next=start; start=pointer; //wypisanie pol wszystkich elementow listy 50 //petla leci po elementach listy zaczynajac od poczatku listy printf ("\n elementy listy struktur:\n\n"); while (pointer!= 0){ printf (" data1 = %d; data2 = %d\n",pointer >data1, pointer >data2); pointer=pointer >next; printf ("\n\n"); return 0; 60
Wynik dzialania elementy listy struktur: data1 = 300; data2 = 300 data1 = 200; data2 = 200 data1 = 100; data2 = 100
Lista dwukierunkowa Lista dwukierunkowa zawiera pewne dane (elementy listy) tak jak struktura, oraz dwa wskaźniki: jeden pokazujący na następny element listy oraz drugi - na poprzedni element listy. Wskaźnik na następny element listy ostatniego elementu listy dwukier. jest ustawiany na, i podobnie, wskaźnik pierwszego elementu listy na element poprzedni jest ustawiany na. Przeglądanie listy dwukierunkowej może się odbywać zarówno od początku, za pomocą wskaźnika ustawionego na pierwszym elemencie (tutaj wskaźnik o nazwie start) jak też od końca za pomocą wskaźnika ustawionego na ostatnim elemencie listy. Podobnie jak w przypadku listy jednokierunkowej przeglądanie listy i pobieranie jej elementów (wstawianie tam wartości) możliwe jest dzięki operatorowi wskaźnikowemu ->. start next N pre pre (1) (2) (3) (N) struct lista_dwukierunkowa { typ dana1; typ dana2;... typ danan; struct lista_dwukierunkowa *next, *prev; ;
Lista dwukierunkowa Przykład: 1. Zdefiniowanie struktury. struct twodir_element { int data1; int data2; struct twodir_element *next, *prev; ; 2. Deklaracja wskaźników typu strukturalnego (takiego jak element listy) i ustawienie ich tymczasem na : struct twodir_element *start=, *pointer=;
Lista dwukierunkowa 3. Dynamiczne przydzielenie pamięci dla dwóch elementów strukturalnych (dwóch elementów listy) i ustawienie wskaźników: start = malloc ( sizeof (struct twodir_element) ); start->data1=100; start->data2=100; start->next=; start->prev=; pointer = malloc ( sizeof (struct twodir_element) ); pointer->data1=200; pointer->data2=200; pointer->next=; pointer->prev=; pointer start
Lista dwukierunkowa 4. Połączenie elementów listy poprzez odpowiednie ustawienie wskaźników next oraz prev. Wskaźnik start trzeba ustawić na początku listy, zaś pointer zostanie usunięty. Wcześniej wyświetlimy też listę tzn. pola jej elementów. pointer->next=start; start->prev=pointer; start=pointer; while(pointer!= 0) { printf( %d %d\n,wsk->data1, wsk->data2); pointer=pointer->next; free(pointer); Obraz końcowej sytuacji start
Lista dwukierunkowa Następny przykład: trzy elementy listy (obraz po zbudowaniu listy) pointer i usunięcie drugiego elementu (obraz po usunięciu drugiego elementu). start Poniżej pełny program.
Lista dwukierunkowa - pełny przykład #include <stdio.h> #include <stdlib.h> //deklaracja elementu listy dwukierunkowej struct twodir{ int data1; int data2; struct twodir *prev, *next; ; int main(){ //pierwszy element listy struct twodir *start; start = (struct twodir*)malloc(sizeof(*start)); start >data1 = 100; start >data2 = 100; start >next=; start >prev=; 10 //drugi elem. listy 20 struct twodir *pointer; pointer = (struct twodir*)malloc(sizeof(*pointer)); pointer >data1=200; pointer >data2=200; pointer >prev=; pointer >next=; //trzeci elem. listy struct twodir *pointer2; pointer2 = (struct twodir*)malloc(sizeof(*pointer)); 30 pointer2 >data1=300;
pointer2 >data2=300; pointer2 >prev=; pointer2 >next=; //Polaczenie elementow listy. //Pierwszy element trzeba ustawic na poczatku listy. //Czyli najpierw ma byc 100, pozniej 200 a pozniej 300 //(od lewej do prawej) start >next=pointer; 40 pointer >prev=start; pointer >next=pointer2; pointer2 >prev=pointer; //wypisanie elementow listy: //utworzymy pomocniczy wskaznik, aby pozniej // nie przesuwac wstecz wskaznikow. //ustawimy go na poczatku listy i nim bedziemy sie poslugiwac // przy wypisywaniu. 50 //pozostale wskazniki pozostaja nietkniete struct twodir *temp; // tu nie ma alokacji, nie potrzeba! temp=start; printf ("\n elementy listy, wierszami, przed usunieciem elem. drugiego:\n"); while ( temp!= 0 ){ printf (" data1 = %d, data2 = %d\n",temp >data1,temp >data2); temp=temp >next; printf ("\n\n"); 60 //usuniecie drugiego elementu i polaczenie pierwszego z trzecim pointer >next=; pointer >prev=; start >next=; pointer2 >prev=; free(pointer);
start >next=pointer2; pointer2 >prev=start; //wypisanie elementow listy przy pomocy wskaznika temp 70 temp=start; printf ("\n elementy listy, wierszami, po usunieciu elem. drugiego:\n"); while ( temp!= 0 ){ printf (" data1 = %d, data2 = %d\n",temp >data1,temp >data2); temp=temp >next; printf ("\n\n"); return 0; 80 elementy listy, wierszami, przed usunieciem elementu drugiego: data1 = 100, data2 = 100 data1 = 200, data2 = 200 data1 = 300, data2 = 300 elementy listy, wierszami, po usunieciu elementu drugiego: data1 = 100, data2 = 100 data1 = 300, data2 = 300
Listy cykliczne Lista dwukierunkowa cykliczna charakteryzuje się tym, że wskaźnik na następny element w ostatnim elemencie nie ma wartości ale pokazuje na początek listy. W przypadku listy cyklicznych dwukierunkowych wskaźnik na poprzedni element w pierwszym elemencie listy pokazuje na element ostatni, zaś wskaźnik elementu ostatniego pokazuje na pierwszy element tej listy. Jednakw przypadku list cyklicznych raczej nie mówi się o początku i końcu, choć często pierwszy element takiej listy wynika z kontekstu i zawartości danych struktury. Pozycję na liście wskazuje zewnętrzny wskaźnik, którego położenie może się dowolnie zmieniać, wówczas trzeba określić jakiś dodatkowy warunek na zakończenie pętli po elementach listy. pointer next next next xt (1) (2) (3) (N) pointer pre next pr xt (1) (2) (3) (N)
Stosy Stos jest kolejką typu LIFO (ang. Last In First Out), tzn. elementy położone jako ostatnie są zdejmowane jako pierwsze. Domyślamy się, że mamy zatem np. w przypadku list, szybko dostęp do takiego elementu za pomocą wskaźnika. W przypadku stosu taki wskaźnik nazywany jest wskaźnikiem stosu i konsekwentnie pokazuje na ostatnio dołożony element (pierwszy na górze, na szczycie stosu) Ze stosu zdejmujemy ostatnio dołożony element. Stos może też być zaimplementowany jako tablica jednowymiarowa. Musimy wtedy zaopatrzyć nasze procedury w jakiś sposób identyfikacji ostatnio wprowadzonego elementu do tablicy (indeksu ostatniego elementu, odpowiedniego licznika). Stos w formie tablicy musi zapewnić poprawne wykonanie przynajmniej dwóch funkcji push() - położenie elementu na stosie (w tablicy - dołożenie elementu do tablicy i zwiększenie dynamiczne rozmiaru tablicy o jeden element), oraz pop() - zdjęcie ostatniego elementu z listy (w tablicy - usunięcie ostatniego elementu tablicy i skrócenie tablicy o jeden element). W przypadku implementacji listowej, szczyt stosu znajduje się na początku listy, zaś nowe elementy dokładane są do początku listy. Usuwanie elementów odbywa się od wierzchołka stosu.
Stos 1. definicja elementów stosu (tutaj trzech). Bez połączenia, tymczasem. pierwszy = malloc (sizeof(*pierwszy)); pierwszy->data1=100; pierwszy->next=; drugi = malloc (sizeof(*drugi)); drugi->data1=200; drugi->next=; trzeci = malloc (sizeof(*trzeci)); trzeci->data1=300; trzeci->next=; 2. Połączenie elem. stosu pierwszy->next=drugi; drugi->next=trzeci; //ustawienie wskaźnika stosu: wsk_stosu=pierwszy;
Stos Stos wygląda tak (można też narysować w pionie ) wsk. stosu pierwszy drugi trzeci 3. zdjęcie elementu ze stosu (zgodnie z zasadą LIFO). To operacja podobna do działania funkcji pop(). wsk_stosu=drugi; pierwszy->next=; free(pierwszy); wsk. stosu drugi trzeci
Stos 4. Dołożenie elementu do stosu, w przypadku stosu - zawsze na początek (na szczyt stosu), tak jak w funkcji push(). czwarty = malloc (sizeof(*czwarty)); czwarty->data1=400; czwarty->next=; czwarty->next=drugi; //ustawienie wskaźnika stosu na szczycie stosu wsk_stosu=czwarty; wsk. stosu (pierwszy) drugi trzeci
Stos - pełny przykład #include <stdio.h> #include <stdlib.h> //deklaracja elementu stosu; struct stos { int data1; struct stos *next; ; int main (){ 10 //zaalokowanie 3 elementów stosu struct stos *pierwszy; pierwszy = (struct stos*)malloc(sizeof (*pierwszy) ); pierwszy >data1=100; pierwszy >next=; struct stos *drugi; drugi = (struct stos*)malloc(sizeof (*drugi) ); 20 drugi >data1=200; drugi >next=; struct stos *trzeci; trzeci = (struct stos*)malloc(sizeof (*trzeci) ); trzeci >data1=300; trzeci >next=; //polaczenie elemetow stosu pierwszy >next=drugi; 30 drugi >next=trzeci;
//wskaznik stosu i ustawienie go na pierwszym elemencie struct stos *wsk stosu; wsk stosu=pierwszy; //wypisanie pol wszystkich elementow stosu printf ("\n elementy stosu przed zdjeciem jednego elementu:\n"); while (wsk stosu!= 0){ printf (" data1 = %d;\n",wsk stosu >data1); 40 wsk stosu=wsk stosu >next; printf ("\n\n"); //zdjecie elementu ze stosu zgodnie z regula LIFO //czyli zdjecie nawyjzej polozonego elementu, pierwszego wsk stosu=drugi; pierwszy >next=; free(pierwszy); printf ("\n elementy stosu po zdjeciu pierwszego elementu:\n"); 50 while (wsk stosu!= 0){ printf (" data1 = %d;\n",wsk stosu >data1); wsk stosu=wsk stosu >next; printf ("\n\n"); //nastepnie dodanie kolejnego elementu na wierzch stosu, //czyli na poczatek listy struct stos *czwarty; czwarty = (struct stos*)malloc(sizeof (*czwarty) ); 60 czwarty >data1=400; czwarty >next=; czwarty >next=drugi; wsk stosu=czwarty;
printf ("\n elementy stosu po dodaniu czwartego elementu:\n"); while (wsk stosu!= 0){ printf (" data1 = %d;\n",wsk stosu >data1); wsk stosu=wsk stosu >next; 70 printf ("\n\n"); return 0; elementy stosu przed zdjeciem jednego elementu: data1 = 100; data1 = 200; data1 = 300; elementy stosu po zdjeciu pierwszego elementu: data1 = 200; data1 = 300; elementy stosu po dodaniu czwartego elementu: data1 = 400; data1 = 200; data1 = 300;
Kolejka Kolejka jest strukturą danych typu FIFO (ang. First In First Out), czyli pierwszy element jest zdejmowany jako pierwszy, w przeciwieństwie do stosu. Implementowana jest przy pomocy listy jednokierunkowej przy udziale dwóch wskaźników: jeden pokazuje na początek kolejki (na pierwszy element) zaś drugi - na koniec (na ostatni element). Elementy dopisywane są zawsze na koniec kolejki z przesunięciem wskaźnika. Zaś pobierane są zawsze z początku listy (z początku kolejki). start next next next xt (1) (2) (3) (N) struct que { int data1; int data2; struct que *next; ;
Kolejka 1. definicja elementów kolejki (tutaj trzech). Bez połączenia, tymczasem. struct que *pierwszy; struct que *drugi; struct que *trzeci; pierwszy = malloc (sizeof(*pierwszy)); pierwszy->data1=100; pierwszy->data2=100; pierwszy->next=; drugi = malloc (sizeof(*drugi)); drugi->data1=200; drugi->data2=200; drugi->next=; trzeci = malloc (sizeof(*trzeci)); trzeci->data1=300; trzeci->data2=300; trzeci->next=;
Kolejka 2. Połączenie elem. kolejki pierwszy->next=drugi; drugi->next=trzeci; //ustawienie wskaźnikow kolejki: struct stos *start; struct stos *end; start=pierwszy; end=trzeci; start next next next pierwszy drugi trzeci
Kolejka 3. Zdjęcie pierwszego elementu kolejki i dodanie czwartego elementu (na zasadzie FIFO) start=drugi; pierwszy->next=; free(pierwszy); struct que *czwarty; czwarty = malloc (sizeof(*czwarty)); czwarty->data1=400; czwarty->data2=400; czwarty->next=; pierwszy i) i (czwa
Kolejka - pełny przykład #include <stdio.h> #include <stdlib.h> //deklaracja elementu kolejki; struct que { int data1; int data2; struct que *next; ; int main (){ 10 //zaalokowanie 3 elementów kolejki struct que *pierwszy; pierwszy = (struct que*)malloc(sizeof (*pierwszy) ); pierwszy >data1=100; pierwszy >data2=100; pierwszy >next=; struct que *drugi; 20 drugi = (struct que*)malloc(sizeof (*drugi) ); drugi >data1=200; drugi >data2=200; drugi >next=; struct que *trzeci; trzeci = (struct que*)malloc(sizeof (*trzeci) ); trzeci >data1=300; trzeci >data2=300; trzeci >next=; 30
//polaczenie elemetow kolejki pierwszy >next=drugi; drugi >next=trzeci; //wskazniki kolejki struct que *start, *end; start=pierwszy; end=trzeci; //? end nie wydaje sie potrzebny //wypisanie pol wszystkich elementow kolejki //przed zmianami w kolejce printf ("\n elementy kolejki przed zdjeciem jednego elementu:\n"); while (start!= 0){ printf (" data1 = %d, data2 = %d;\n",start >data1,start >data2); start=start >next; printf ("\n\n"); 40 //usuniecie elementu z kolejki zgodnie z regula FIFO 50 //czyli zdjecie pierwszego elementu start=drugi; pierwszy >next=; free(pierwszy); printf ("\n elementy kolejki po usunieciu pierwszego elementu:\n"); while (start!= 0){ printf (" data1 = %d, data2 = %d;\n",start >data1,start >data2); start=start >next; printf ("\n\n"); 60 //nastepnie dodanie kolejnego elementu na koniec kolejki struct que *czwarty; czwarty = (struct que*)malloc(sizeof (*czwarty) ); czwarty >data1=400; czwarty >data2=400;
czwarty >next=; trzeci >next=czwarty; end=czwarty; //? end nie wydaje sie potrzebny 70 printf ("\n elementy kolejki po dodaniu czwartego elementu:\n"); start=drugi; // trzeba z powrotem ustawic na poczatku while (start!= 0){ printf (" data1 = %d, data2 = %d;\n",start >data1,start >data2); start=start >next; printf ("\n\n"); return 0; 80 elementy kolejki przed zdjeciem jednego elementu: data1 = 100, data2 = 100; data1 = 200, data2 = 200; data1 = 300, data2 = 300; elementy kolejki po usunieciu pierwszego elementu: data1 = 200, data2 = 200; data1 = 300, data2 = 300; elementy stosu po dodaniu czwartego elementu: data1 = 200, data2 = 200; data1 = 300, data2 = 300; data1 = 400, data2 = 400;
Drzewo Drzewo jest strukturą danych hierarchiczną i (najczęściej) rekurencyjną. Struktury nazywane są węzłami drzewa (ang. node). Drzewa dzielą się na drzewa mnogościowe (każdy węzęł może mieć dowolną ilość potomków nazywanych dziećmi) lub tylko dwóch (w takim przypadku mówimy o drzewach binarnych).
Drzewo Specjalny wskaźnik umożliwia dostęp do całego drzewa, ustawiany jest na węźle praoujcu, który nie ma żadnych rodziców, nazywany jest on root czyli korzeń. Jeśli węzęł nie ma któregoś z potomków wówczas wskaźnik next ustawiany jest na. Algorytmy operujące na drzewach oparte są najczęściej o rozwiązania rekurencyjne gdyż każdy węzeł może być traktowany jako osobny rodzic a więc od niego może się teoretycznie zaczynać kolejne drzewo. Typowe algorytmy: wstawianie nowego węzła w jakimś miejscu (na końcu, w środku), usuwanie węzłów i usuwanie całego drzewa, przeszukiwanie drzewa (różne warianty).
Rekurencja - przykład #include <stdio.h> //rekurencyjna funkcja obliczajaca silnie long silnia( long n ) { if ( n <= 1 ) return 1; else { return (n * silnia( (n 1) )); int main (){ 10 long ile = 10; long wynik; long i=1; printf("\n\n"); while (i <= 10){ wynik = silnia(i); printf(" %10ld! = %10ld\n",i,wynik); 20 i++; printf("\n\n"); return 0;
Drzewo - przykład #include <stdio.h> #include <stdlib.h> #include <time.h> //deklaracja wezla (nodu ) czyli elem. drzewa binarnego; struct node { int data1; struct node *left, *right, *up; ; //funkcja tworzaca nody (wezly) w drzewie //Jako wartosc w polu data1 zostanie wstawiony //podany parametr ile struct node* add node(struct node *new, int ile){ 10 //inicjalizacja wezla i ustawienie wskaznikow na //utworzenie wskaznika pomocniczego do nodu struct node *helper, *which; which = (struct node*)malloc(sizeof (*which) ); which >left = which >right = ; 20 which >data1 = ile; helper = new; // jesli nie bylo zadnego korzenia to mamy nowe drzewo // i nasz nowy nod jest wtedy rootem if (!helper) {new = which; else{ // Petla nieskonczona // W zaleznosci od klucza idziemy do lewego lub 30 // prawego syna, o ile takowy istnieje
while(1){ if (ile < helper >data1) { if (!helper >left){ helper >left = which; break; else helper=helper >left; else{ 40 if (!helper >right){ helper >right = which; break; else helper=helper >right; which >up = helper; 50 return which; void rmtree(struct node *tree){ if (tree) { rmtree(tree >left); rmtree(tree >right); rmtree(tree >up); free(tree); 60 int main (){
//wskaznik do nodu, //alokacje pamieci zapewnia funkcja add node struct node *root = ; int i, k, ile; 70 //liczba nodow, granicznie moze byc nawet tylko jeden ile = 10; srand(time()); for(i = 0; i < ile; i++){ //liczby losowe 1 9. Takie umiescimy w wezlach //i na podstawie wartosci tych liczb rozmiescimy //kolejne nody w odpowiedni sposob k = 1 + rand() % 9; 80 root = add node(root,k); //usuniecie drzewa rmtree(root); return 0; 90