Podstawy Informatyki

Podobne dokumenty
Dynamiczny przydział pamięci w języku C. Dynamiczne struktury danych. dr inż. Jarosław Forenc. Metoda 1 (wektor N M-elementowy)

Wskaźniki i dynamiczna alokacja pamięci. Spotkanie 4. Wskaźniki. Dynamiczna alokacja pamięci. Przykłady

Wykład 3. Złożoność i realizowalność algorytmów Elementarne struktury danych: stosy, kolejki, listy

Sortowanie bąbelkowe

Algorytmy i Struktury Danych

Algorytmy i złożoności. Wykład 3. Listy jednokierunkowe

Temat: Dynamiczne przydzielanie i zwalnianie pamięci. Struktura listy operacje wstawiania, wyszukiwania oraz usuwania danych.

Lab 9 Podstawy Programowania

Podstawy Informatyki. Metody dostępu do danych

> C++ dynamiczna alokacja/rezerwacja/przydział pamięci. Dane: Iwona Polak. Uniwersytet Śląski Instytut Informatyki

> C++ wskaźniki. Dane: Iwona Polak. Uniwersytet Śląski Instytut Informatyki 26 kwietnia 2017

Struktury danych: stos, kolejka, lista, drzewo

Podstawy Informatyki. Wykład 6. Struktury danych

Programowanie w C++ Wykład 5. Katarzyna Grzelak. 26 marca kwietnia K.Grzelak (Wykład 1) Programowanie w C++ 1 / 40

Sortowanie. Bartman Jacek Algorytmy i struktury

INFORMATYKA SORTOWANIE DANYCH.

Laboratorium nr 9. Temat: Wskaźniki, referencje, dynamiczny przydział pamięci, tablice dynamiczne. Zakres laboratorium:

ZASADY PROGRAMOWANIA KOMPUTERÓW

Wysokość drzewa Głębokość węzła

Sortowanie - wybrane algorytmy

Podstawy programowania 2. Temat: Drzewa binarne. Przygotował: mgr inż. Tomasz Michno

Typy wyliczeniowe Konwersje napis <-> liczba Struktury, unie Scanf / printf Wskaźniki

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

ZASADY PROGRAMOWANIA KOMPUTERÓW ZAP zima 2014/2015. Drzewa BST c.d., równoważenie drzew, kopce.

Tabela wewnętrzna - definicja

ALGORYTMY I STRUKTURY DANYCH

Wstęp do programowania

KURS C/C++ WYKŁAD 6. Wskaźniki

Drzewa BST i AVL. Drzewa poszukiwań binarnych (BST)

Kolejka priorytetowa. Często rozważa się kolejki priorytetowe, w których poszukuje się elementu minimalnego zamiast maksymalnego.

dodatkowe operacje dla kopca binarnego: typu min oraz typu max:

Algorytmy i Struktury Danych, 9. ćwiczenia

Programowanie i struktury danych. Wykład 4 Dr Piotr Cybula

Drzewa poszukiwań binarnych

Rekurencja. Dla rozwiązania danego problemu, algorytm wywołuje sam siebie przy rozwiązywaniu podobnych podproblemów. Przykład: silnia: n! = n(n-1)!

Konwersje napis <-> liczba Struktury, unie Scanf / printf Wskaźniki

Dynamiczny przydział pamięci (język C) Dynamiczne struktury danych. Sortowanie. Klasyfikacja algorytmów sortowania. Algorytmy sortowania

Algorytmy i. Wykład 5: Drzewa. Dr inż. Paweł Kasprowski

prowadzący dr ADRIAN HORZYK /~horzyk tel.: Konsultacje paw. D-13/325

Wykład 3 Składnia języka C# (cz. 2)

Porządek symetryczny: right(x)

WSTĘP DO INFORMATYKI. Drzewa i struktury drzewiaste

Algorytmy i struktury danych Sortowanie IS/IO, WIMiIP

Co to jest sterta? Sterta (ang. heap) to obszar pamięci udostępniany przez system operacyjny wszystkim działającym programom (procesom).

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

Programowanie obiektowe i C++ dla matematyków

Podstawowe algorytmy i ich implementacje w C. Wykład 9

Algorytmy i struktury danych. Drzewa: BST, kopce. Letnie Warsztaty Matematyczno-Informatyczne

Wskaźniki. Przemysław Gawroński D-10, p marca Wykład 2. (Wykład 2) Wskaźniki 8 marca / 17

Programowanie obiektowe i C++ dla matematyków

Teoretyczne podstawy informatyki

Wskaźniki. Programowanie Proceduralne 1

Algorytmy i struktury danych. wykład 5

Zmienne i struktury dynamiczne

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

Wyszukiwanie w BST Minimalny i maksymalny klucz. Wyszukiwanie w BST Minimalny klucz. Wyszukiwanie w BST - minimalny klucz Wersja rekurencyjna

Sortowanie w czasie liniowym

Tadeusz Pankowski

ALGORYTMY I STRUKTURY DANYCH

ALGORYTMY I STRUKTURY DANYCH

Szablony klas, zastosowanie szablonów w programach

Uniwersytet Zielonogórski Instytut Sterowania i Systemów Informatycznych. Ćwiczenie 3 stos Laboratorium Metod i Języków Programowania

Podstawy programowania skrót z wykładów:

Wykład 1: Wskaźniki i zmienne dynamiczne

Teoretyczne podstawy informatyki

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

WYŻSZA SZKOŁA INFORMATYKI STOSOWANEJ I ZARZĄDZANIA

Dynamiczne struktury danych

DYNAMICZNE PRZYDZIELANIE PAMIECI

Dla każdej operacji łącznie tworzenia danych i zapisu ich do pliku przeprowadzić pomiar czasu wykonania polecenia. Wyniki przedstawić w tabelce.

// Liczy srednie w wierszach i kolumnach tablicy "dwuwymiarowej" // Elementy tablicy są generowane losowo #include <stdio.h> #include <stdlib.

Wstęp do wskaźników w języku ANSI C

Wskaźniki. Informatyka

Podstawowe struktury danych

Podstawy programowania. Wykład 7 Tablice wielowymiarowe, SOA, AOS, itp. Krzysztof Banaś Podstawy programowania 1

Uniwersytet Zielonogórski Instytut Sterowania i Systemów Informatycznych. Algorytmy i struktury danych Laboratorium 7. 2 Drzewa poszukiwań binarnych

1 Wskaźniki i zmienne dynamiczne, instrukcja przed zajęciami

Podstawy programowania. Wykład 6 Wskaźniki. Krzysztof Banaś Podstawy programowania 1

Wstęp do programowania INP001213Wcl rok akademicki 2018/19 semestr zimowy. Wykład 4. Karol Tarnowski A-1 p.

Listy, kolejki, stosy

Język C zajęcia nr 11. Funkcje

Materiał Typy zmiennych Instrukcje warunkowe Pętle Tablice statyczne Wskaźniki Tablice dynamiczne Referencje Funkcje

. Podstawy Programowania 2. Drzewa bst - część druga. Arkadiusz Chrobot. 12 maja 2019

1. Które składowe klasa posiada zawsze, niezależnie od tego czy je zdefiniujemy, czy nie?

typ y y p y z łoż o on o e n - tab a lice c e w iel e owym m ar a o r we, e stru r kt k ury

Zaawansowane programowanie w języku C++ Zarządzanie pamięcią w C++

< K (2) = ( Adams, John ), P (2) = adres bloku 2 > < K (1) = ( Aaron, Ed ), P (1) = adres bloku 1 >

Algorytmy sortujące i wyszukujące

Algorytmy i Struktury Danych. Co dziś? Drzewo decyzyjne. Wykład IV Sortowania cd. Elementarne struktury danych

Lista liniowa dwukierunkowa

liniowa - elementy następują jeden za drugim. Graficznie możemy przedstawić to tak:

Jeszcze o algorytmach

Egzamin, AISDI, I termin, 18 czerwca 2015 r.

Wstęp do programowania

dr inż. Paweł Myszkowski Wykład nr 11 ( )

Wykład X. Programowanie. dr inż. Janusz Słupik. Gliwice, Wydział Matematyki Stosowanej Politechniki Śląskiej. c Copyright 2016 Janusz Słupik

Podstawy programowania. Wykład: 8. Wskaźniki. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Struktury Danych i Złożoność Obliczeniowa

Programowanie obiektowe W3

Język ludzki kod maszynowy

Transkrypt:

Podstawy Informatyki Wskaźniki w języku C i C++ dr inż. Piotr Kaczmarek Piotr.Kaczmarek@put.poznan.pl http://pk.cie.put.poznan.pl/wyklady.php

Organizacja pamięci Pamięć ma organizację bajtową, liniową każdy bajt posiada swój indywidualny adres, który jest liczbą całkowitą długość adresu wynosi obecnie 4 bajty (32bit) co pozwala zaadresować do 4Gb pamięci zmienne przechowują dane w kolejnych komórkach np. zmienna unsigned int a=0xffeeddcc char z='0'; zmienna a z komórka pamięci 0xcc 0xdd 0xee 0xff 0x32... adres 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 100A 100B FF01 FF02 FF03 FF04

Typy wskaźnikowe zmienna typu wskaźnikowego, służy do przechowywania adresów innych zmiennych zmienna typu wskaźnikowego, przechowuje wartość całkowitą, (4b), która jest adresem początku obszaru pamięci w którym zlokalizowano dane Przy deklaracji wskaźnika, zawsze trzeba określić zmiennej jakiego typu będzie on dotyczył (typ* nazwa;) np.: int* p1; float* p2; char* p2; zmienna a z p1 p2 komórka pamięci 0xcc 0xdd 0xee 0xff 0x32... adres zmienn. int Adres zm. float... adres 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 100A 100B FF01 FF02 FF03 FF04 FF05 FF06

Operacje na wskaźnikach Przypisanie adresu zmiennej do wskaźnika unsigned int a=0xffeeddcc; int* p1; p1= &a; //adres początku Zmiana wartości pod adresem wskazywanym unsigned int a=0xffeeddcc; int* p1; p1= &a; *p1=2; //zmienna a=2 Uwagi: &zmianna odwołanie się do adresu zmiennej *wskaźnik odwołanie się do wartości przechowywanej pod wskazanym adresem zmienna *p1 a z p1 p2 komórka pamięci 0xcc 0xdd 0xee 0xff 0x32... adres zmienn. int 1002 Adres zm. float... adres 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 100A 100B FF01 FF02 FF03 FF04 FF05 FF06 &a p1=&a

Operacje na wskaźnikach cd. Przypisanie adresu zmiennej do wskaźnika char z='0'; char* p2; p2= &z; *p2='a'; ile wynosi wartość: p2, &z, z,*p2 z 'a' *p2 'a' wartości p2 i &z są takie same (1009) Uwagi: &zmianna odwołanie się do adresu zmiennej *wskaźnik odwołanie się do wartości przechowywanej pod wskazanym adresem zmienna *p2 a z p1 p2 komórka pamięci 0xcc 0xdd 0xee 0xff 0x32... adres zmienn. int 1002 1009... adres 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 100A 100B FF01 FF02 FF03 FF04 FF05 FF06 &z p2=&z

Operacje na wskaźnikach cd.. inkrementacja char z='0'; char* p2; p2= &z; *p2++; //inkrem. wartości ile wynosi wartość: p2, &z, z,*p2 z '1', *p2 '1' wartości p2 i &z są takie same (1009) p2++; inkrementacja adresu p1++; ile wynosi wartość: p2, &z, z,*p2, p1 z '1', *p2 '1', &z bez zmian (1009) p2 100A (kolejny element char) p1 1006 (kolejny elemnet int) zmienna *p2 a z p1 p2 komórka pamięci 0xcc 0xdd 0xee 0xff 0x32 ('0')... adres zmienn. int 1002 1009... adres 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 100A 100B FF01 FF02 FF03 FF04 FF05 FF06 p1++; p2++;

Operacje arytmetyczne na wskaźnikach Operacje na wskaźnikach mają inny przebieg niż w zwykłej arytmetyce. Operacja dodawania lub odejmowania wsk+k wsk-k oznacza: przejdź o k elementów, stąd adres wynikowy zależy od rozmiaru wskazywanej zmiennej: int* p1=1002; char* p2=1009; p2=p2+1;// adres wynosi 100A (char - 1b) p1=p1+1;//adres wynosi 1006 (int - 4b) Uwaga: To o ile zmienia się wartość adresu determinowane jest przez typ wskaźnika. a z p1 p2 komórka pamięci 0xcc 0xdd 0xee 0xff 0x32 ('0')... adres zmienn. int 1002 adres 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 100A 100B FF01 FF02 FF03 FF04 FF05 1009 FF06... p1++; p2++;

Referencje Zmienne tworzona jako referencja przechowuje wartość w tym samym obszarze pamięci int a=1; int&b=a; // b jest referencją a int* p1, p2; p1=&a; p2=&b; ile wynoszą wartości: *p1,*p2, &a, &b, p1,p2,a,b p1,p2,&a,&b mają tą samą wartość (1002) p1,p2,a,b mają wartość 1; *p1, *p2 a b z p1 p2 komórka pamięci 0x01 0x00 0x00 0x00 0x32 ('0')... adres zmienn. int 1002 1002... adres 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 100A 100B FF01 FF02 FF03 FF04 FF05 FF06

Wykorzystanie wskaźników int a=2, b=3; int* p1, p2; int& c=a; a c 2 p1 p2 p1=&a; p2=&b; b 3 *p1=*p2; b++; p2=p1; *p2++; c++; ile wynosi wartość: a,b, c, *p1, *p2 p1, p2, &a, &b, &c

Wykorzystanie wskaźników int a=2, b=3; int* p1, p2; int& c=a; a c 3 *p1 p1 p2 p1=&a; p2=&b; b 3 *p2 *p1=*p2; b++; p2=p1; *p2++; c++; odwołanie się do wartości we wskazywanej komórce ile wynosi wartość: a,b, c, *p1, *p2 p1, p2, &a, &b, &c

Wykorzystanie wskaźników int a=2, b=3; int* p1, p2; int& c=a; a c 3 *p1 p1 p2 p1=&a; p2=&b; b 4 *p2 *p1=*p2; b++; p2=p1; *p2++; c++; ile wynosi wartość: a,b, c, *p1, *p2 p1, p2, &a, &b, &c

Wykorzystanie wskaźników int a=2, b=3; int* p1, p2; int& c=a; a c 3 *p1 p1 p2 p1=&a; p2=&b; b 4 *p2 *p1=*p2; b++; p2=p1; *p2++; c++; przypisanie do p1 adresu p2 ile wynosi wartość: a,b, c, *p1, *p2 p1, p2, &a, &b, &c

Wykorzystanie wskaźników int a=2, b=3; int* p1, p2; int& c=a; a c 5 *p1 p1 p2 p1=&a; p2=&b; b 4 *p2 *p1=*p2; b++; p2=p1; *p2++; c++; ile wynosi wartość: a,b, c, *p1, *p2 p1, p2, &a, &b, &c

Wykorzystanie wskaźników int a=2, b=3; int* p1, p2; int& c=a; a c 3 *p1 p1 p2 p1=&a; p2=&b; b 4 *p2 *p1=*p2; b++; p2=p1; *p2++; c++; przypisanie do p1 adresu p2

jest tożsama operacji: *(tab+i) = 0.5*i; Tablice i wskaźniki tablica jest ciągłym obszarem pamięci, w którym kolejno umieszczone są wartości elementów zmienna tab jest wskaźnikiem (typ float*) do pierwszego elementu tablicy stąd *tab, odwołuje się do wartości elementu 0 natomiast tab+i, jest adresem i-tego elementu tablicy, a *(tab+i) odwołuje się do wartości i-tego elementu tablicy. stąd operacja : tab[i] = 0.5*i; nr elementu tab *tab *(tab+1) 0 1 2 0.0 0.5 1.0 tab+4 float tab[5]; for(i=0;i<5;i++) tab[i]=0.5*i; 3 4 1.5 2.0 adres k k+6 k+12 k+18 k+24

Wskaźniki jako argument funkcji Zastosowanie wskaźników pozwala na tworzenie funkcji przekazujących na zewnątrz więcej niż jedną wartość Przekazanie argumentów przez wartość void Zamien(int a,int b) { int t=a; a=b; b=t; } Przekazanie argumentów przez wskaźnik void Zamien(int *a,int *b) { int t=*a; *a=*b; *b=t; } Przekazanie argumentów przez referencję void Zamien(int& a,int& b) { int t=a; a=b; b=t; } int main { int x=2,y=3; Zamien(x,y); } int main { int x=2,y=3; Zamien(&x,&y); } int main { int x=2,y=3; Zamien(x,y); } x,y nie zmieniają wartości x,y zmieniają swoje wartości

Wskaźniki jako argument funkcji Przekazując argument przez wartość (void Zamien(int a, int b)) przy każdym wywołaniu funkcji tworzone są lokalne zmienne a,b i przypisywana jest im wartość argumentów Czas wywołania funkcji oraz ilość pamięci rezerwowanej dla argumentów jest zależny od typu argumentu (musi zostać skopiowana pewna ilość bajtów z zmiennej przekazanej jako argument do zmiennej lokalnej) Przekazując argument jako wskaźnik (void Zamien(int *a, int *b)) przy każdym wywołaniu funkcji tworzone są lokalnie zmienne wskaźnikowe a i b i przypisywana jest im wartość adresów argumentów wejściowych (po 4 bajty na adres) Czas wywołania i ilość pamięci zajmowane przez argumenty funkcji jest więc niezależny od typu argumentów Wszystkie operacje modyfikujące wartość wskazywaną przez a lub b będą miały swoje odzwierciedlenie na zewnątrz funkcji Przekazanie argumentu przez referencję, działa tak jak przekazanie argumentu przez wskaźnik, z tym że nie ma etapu kopiowania adresu (metoda ta jest najszybsza)

Ochrona argumentów przed zmianami Przekazanie argumentów przez wskaźnik lub referencję pozwala na zwiększenie szybkości wywoływania funkcji (szczególnie w przypadku argumentów o dużym rozmiarze) Może to jednak powodować że nastąpi zmiana argumentów przekazanych do funkcji (co nie zawsze jest pożądane) Zapewnić że argumenty nie zostaną zmodyfikowane przekazując je jako referencję do stałej void SzybkaFunkcja(const int& X) { int a=x; //ok X=1;//błąd zmienna tylko do odczytu } void SzybkaFunkcja(const int* X) { int a=*x; //ok *X=1;//błąd zmienna tylko do odczytu }

Dynamiczny przydział pamięci Pamięć dla zmiennych i tablic deklarowanych w sposób pokazywany poprzednio jest przydzielona w chwili gdy program napotyka deklarację zmiennej: { float a; int tab[100]; char znak;... } Rozmiar zmiennej (np. tablicy) musi być znany już w chwili uruchamiania programu. Konieczne jest więc deklarowanie tablic większych niże maksymalny rozmiar wprowadzony przez użytkownika. Zmienne automatyczne istnieją od momentu deklaracji do końca zasięgu ich widoczności. Program sam przydziela i zwalnia pamięć

Dynamiczny przydział pamięci cd.. Pamięć może być również przydzielana ręcznie przez użytkownika: przez zastosowanie operatora new. Składnia ma postać: typ* wsk = new typ; float *wsk; wsk=new float; *wsk = 6.0; wsk=new float; przydzielony blok nie ma stowarzyszonej żadnej zmiennej automatycznej i istnieje do czasu aż użytkownik uwolni przydzieloną pamięć. Składnia ma postać: delete wskaźnik; delete wsk; wsk; wsk komórka pamięci... adres zmienn. int 1002... adres 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 100A 100B FF01 FF02 FF03 FF04 FF05 FF06

Przydział pamięci dla tablic w programie można uzależnić ilość przydzielanej pamięci od aktualnego zapotrzebowania. rezerwacja pamięci dla tablicy o N elementach ma postać: typ* wsk=new typ[n]; float *ptab; int N; cout<< Ile elementów chcesz wprowadzić? ; cin>>n; ptab=new float[n]; for(int i=0;i<n;i++) cin>>ptab[i]; //lub cin>>*(ptab+i); Zwolnienie pamięci dla tablicy odbywa się za pomocą operatora delete[] delete[] ptab; Uwaga: delete ptab; //zwolnienie pamięci elementu 0 tablicy!!!

Weryfikacji prawidłowego przydziału pamięci Sprawdzenie poprawności przydzielenia pamięci jest istotne szczególnie przy tworzeniu tablic o dużych rozmiarach. przy stosowaniu operatora new, jeśli można zarezerwować żądany przez użytkownika blok pamięci zwracany jest jego adres, w innym przypadku, operator new nie zwraca żadnej wartości Stąd można zastosować następującą procedurę sprawdzania: int N; float *ptab = null; cin>>n; ptab=new float[n]; if(ptab==null) { cout<< blad przydzialu ; exit(-1); }... // pamięć przydzielona OK

Przydział pamięci cd. Można stworzyć funkcje dedykowaną do przydziału pamięci, której zadaniem będzie sprawdzenie poprawności przydziału float* PrzydzielPamiec(int N) { float *ptab = null; cin>>n; ptab=new float[n]; if(ptab==null) { cout<< blad przydzialu ; exit(-1); } return ptab; } Użycie (wywołanie) będzie miało postać: int n; cin>>n; float* tab=przydzielpamiec(n);...

Tablice 2D W języku C można zdefiniować tablicę dwuwymiarową: int n=2,m=4; //Deklaracja tablicy 2x4 int tab[2][4]; //zapis wartości do pierwszego elementu Tab[0][0]=1; //wypelnenie wszystkich elementów zerami for(int i=0;i<n;i++) for(int j=0;j<m;j++) tab[i][j]=0;

Wskaźniki do funkcji Dla funkcji: typ nazwa(argumenty){} np.: int funkcja(float wartosc){} Można stworzyć wskaźnik: typ (*nazwa)(argumenty) np.: int (*wsk_funkcji)(float); Taki że wsk_funkcji=&funkcja; Wywołąnie funkcji może mieć postać: (*wsk_funkcji)(2.0);

Dynamiczne tablice 2D W języku C nie ma komendy automatycznie przydzielającej pamięć dla tablic 2D, możemy natomiast można stworzyć wiele tablic 1D, a wskaźniki do nich umieścić w tablicy, której elementy są typu typ* Int** wsk_2d //tworzenie 2D tablicy NxM typu int Int** tab=new int*[n]; Int* wsk1 Int* wsk2. Int* wskn int.... int int.... int. int.... int for(int i=0;i<n;i++) tab[i]=new int[m]; //odczyt/zapis danych // z dynamicznej tablicy 2D Tab[0][0]=0; //usuwanie tablicy 2D NxM typu int for(int i=0;i<n;i++) delete[] tab[i];//usuwa wiersze delete[] tab; //usuwa kolumny

Grupowanie zmiennych Często pewien obiekt jest opisywany nie przez jedną zmienną, ale przez pewien zestaw w celu przechowania danych osoby w programie należy zdefiniować kilka zmiennych: char imie_nazwisko[25]; int wiek; char plec; aby dodaś kolejną osobę należy stworzyć kolejne zmienne char imie_nazwisko1[25]; int wiek1; char plec1;

Struktury Struktury są typami umożliwiającymi grupowanie zmiennych. struct snazwa { typ pole1; typ pole 2; typ pole 3; }; Struktura służąca do przechowywania danych osoby: struct sosoba { char Imie_Nazwisko[25]; int wiek; char plec; }; Uwaga: definicja typu strukturalnego powinna znajdować się w pliku nagłówkowym

Struktury - wykorzystanie Można zadeklarować zmienną typu strukturalnego sosoba student; Do odwołania się do konkretnego pola struktury stosuje się operator '.' cout<< Wprowadz imię i nazwisko ; cin.getline(student.imie_nazwisko,25); cout<< Podaj wiek ; cin>>student.wiek; cout<< Podaj plec ; cin>>student.plec;

Tablice struktur - wykorzystanie W celu przechowania wielu osób można zadeklarować tablicę: sosoba studenci[50]; Do odwołania się do konkretnego pola struktury stosuje się operator '.' for(int i=0; i<50; i++) { cout<< Wprowadz imię i nazwisko ; cin.getline(studenci[i].imie_nazwisko,25); cout<< Podaj wiek ; cin>>studenci[i].wiek; cout<< Podaj plec ; cin>>studenci[i].plec; }

Wskaźniki do struktur Wskaźników do struktur używa się tak jak innych wskaźników do typów wbudowanych sosoba student1,student2; sosoba* wsk; wsk = &student1; Odwołanie do pól struktury (selektor '.') *wsk.wiek=16; cin.getline(*wsk.imie_nazwisko,25); Student1 Student2 Imie_Nazwisko wiek plec Imie_Nazwisko wiek plec wsk Bardziej poprawne odwołanie do pól struktury, dla wskaźników (selektror ->) wsk->wiek=16; cin.getline(wsk->imie_nazwisko,25);

Funkcje operujące na strukturach Zmienna typu strukturalnego może być przekazywana do funkcji tak jak zmienna wbudowana void Wyswietl(sOsoba* O) { cout<<o->imie_nazwisko<<endl << wiek: <<O->Wiek<<endl << plec: <<O->plec<<endl; } Przekazanie adresu zmiennej pozwala przyspieszyć czas wykonywania funkcji (pola nie są kopiowane) sosoba student;... Wyswietl(&student);

Przydział pamięci do struktur Przydział pamięci dla pojedynczej zmiennej strukturalnej, wygląda analogicznie jak dla typów wbudowanych sosoba* wsk; wsk = new sosoba;... delete wsk; Przydział pamięci dla tablic struktur sosoba* posoby; int N; cout<< ile osób chcesz wprowadzic ; cin>>n; posoby = new sosoba[n];... delete[] posoby;

Wady stosowania tablic struktur Dodawanie/usuwanie elementów stworzyć nową tablicę o właściwym rozmiarze, przekopiować wszystkie elementy ze starej tablicy do nowej (dodając/usuwając pewne elementy) usunąć starą tablicę Aby przestawić 2 elementy (np. przy sortowaniu) w tablicy należy zapamiętać pierwszy element w elemencie tymczasowym (skopiować wszystkie jego pola) przypisać wartości pół 2 elementu do 1 elementu przypisać wartość elementu tymczasowego do 2 elementu kopiowanie 2 elementów: sosoba o1,o2;... strcpy(o1.imienazwisko, o2.imienazwisko); o1.wiek=o2.wiek; o1.plec=o2.plec; Uwaga: struktury mogą zajmować dużo miejsca w pamięci, stąd operacja kopiowania elementów jest czasochłonna i może pochłaniać wiele zasobów

Dynamiczne struktury danych STOS wstawianie, usuwanie i dostęp do składników są możliwe tylko w jednym końcu zwanym wierzchołkiem stosu KOLEJKA dołączanie składników jest możliwe tylko w jednym końcu, a usuwanie tylko w drugim końcu LISTA dla każdego składnika (poza pierwszym i ostatnim) jest określony jeden składnik poprzedni i jeden składnik następny lub tylko składnik poprzedni lub następny, w dowolnym miejscu można dołączać lub usuwać składnik DRZEWO dla każdego składnika (poza pierwszym) jest określony jeden składnik poprzedni i dla każdego składnika (poza ostatnim) jest określonych n (n 2) składników następnych GRAF struktury definiowane przez dwa zbiory: zbiór wierzchołków i zbiór krawędzi określający powiązania pomiędzy poszczególnymi wierzchołkami.

Stos (LIFO) Początek -wstawianie -usuwanie -odczyt Element osoba P Element osoba P Element osoba P

Kolejka (FIFO) Początek -wstawianie Element osoba P Element osoba P Element osoba P Koniec -usuwanie -odczyt

Listy dynamiczne dwukierunkowe Tablice wymagały przydzielenia ciągłego obszaru pamięci, stąd każda modyfikacja wiązała się z ingerencją w zawartość całej tablicy Zastosowanie struktur dynamicznych tj. Listy pozwala ominąć ten problem. Rozpatrzmy strukturę umożliwiającą zaimplementowanie wytworzenie listy dwukierunkowej: struct sosoba { char Imie_Nazwisko[25]; int wiek; char plec; sosoba* N;//nastepny element sosoba* P;//poprzedni element }; Pola N i P przechowują adresy następnego i poprzedniego elementu listy. Wartość NULL tego pola oznacza, że nie istnieje następny (aktualny element jest ostatni) lub poprzedni element (aktualny element jest pierwszy.

Dodawanie elementów do listy Załóżmy że zmienna sosoba *root przechowuje adres pierwszego elementu listy sosoba *root; root = new sosoba; root->n=null;//ostatni element root->p=null; //pierwszy element root null osoba N P null dodawanie kolejnego elementu d listy sosoba* nowy=new sosoba nowy->p=root; root->n=nowy; nowy->n=null; //ostatni element root null osoba N P osoba nowy N P null

Poszukiwanie ostatniego elementu na liście Ostatnim elementem na liście jest ten, którego pole N ma wartość null. Poniższa funkcja poszukuje ostatniego elementu na liście i zwraca jego adres. sosoba* Ostatni(sOsoba* root) { sosoba* aktualny=root; while(aktualny->n!= null) { aktualny = aktualny->n; } return aktualny; } root null osoba N P osoba N P osoba N P osoba N P null

Dodawanie elementu na końcu listy Aby dodać element na końcu listy należy odszukać element ostatni przydzielić pamięć dla nowego elementu zaktualizować pola N i P nowego i ostatniego elementu //dodawanie elementu sosoba *koniec=ostatni(root); sosoba *nowy=new sosoba; koniec->n=nowy; nowy->p=koniec; nowy->n=null; root null osoba N P osoba N P osoba N P koniec osoba N P null osoba nowy N P null

Przeszukiwanie listy Wyświetlić osoby, których wiek wynosi 28 lat; sosoba* aktualny=root; int wiek =28; while(aktualny!= null) // przechodzi do pierwszego do ostatniego elementu { if(aktualny->wiek==wiek) Wyswietl(aktualny)l aktualny = aktualny->n; } root null osoba N P osoba N P osoba N P osoba N P osoba N P null

Przestawianie 2 elementów Aby przestawić 2 sąsiednie elementy należy określić adresy tych elementów zmienić wartości pól elementów poprzedzających i następujących sosoba* E1;E2,Po,Na... E2=E1->N; Po=E1->P; Na=E2->N; osoba E2 N P Po->N=E2; E2->P=Po; Na->P=E1; E1->N=Na root null osoba Po N P osoba E1 N P E1->P=E2; E2->N=E1; osoba Na N P

Usuwanie elementów z listy Aby usunąć element o adresie E1 Określić adresy elementów poprzedniego i następnego Zaktualizować pole N poprzednika tak by wskazywało adres elementu następnego Zaktualizować pole P el. następnego tak by wskazywało adres elementu poprzedniego uwolnić pamięć dla obiektu E1 sosoba* E1;Po,Na... Po=E1->P; Na=E1->N; Po->N=Na; Na->P=Po; delete E1; root null osoba N P osoba Po N P osoba E1 N P osoba Na N P osoba N P null

Listy jednokierunkowe Element strukturalny struct sosoba { char Imie_Nazwisko[25]; int wiek; char plec; sosoba* N;//nastepny element }; osoba N osoba nowy N null Gdzie wskaźnik N przechowuje adresy następnego i poprzedniego elementu listy. Wartość NULL tego pola oznacza, że nie istnieje następny (aktualny element jest ostatni) lub poprzedni element (aktualny element jest pierwszy. Operacje przeszukiwania listy, oraz dodawanie elementu do końca listy przebiegają tak jak dla list dwukierunkowych. Natomiast operacja usuwania elementu z listy czy też przestawiania są trudniejsze.

Usuwanie elementów z listy jednokierunkowej Aby usunąć element o adresie E1 Określić adresy elementu poprzedzającego element do usunięcia (spełniającego warunek E->N==E1 Dokonać przepisania E->N=E1->N Usunąć element E1 (delete E1;) sosoba* E1;Po,Na... //znajdź E taki że E->N==E1 E->N=E1->N; Delete E1; root osoba N osoba Po N osoba E1 N osoba Na N osoba N null

Złożoność obliczeniowa Typy złożoności: Złożoność algorytmów (ilość operacji) Złożoność czasowa Złożoność pamięciowa Sposób reprezentacji: zazwyczaj złożoność określa się jako funkcję rozmiaru problemu (np.. ilości danych, wielkości zbioru).

Złożoność obliczeniowa - funkcja Typy złożoności: O(n) złożoność liniowa np. T(n)=a 1 n+a 0 złożoność będzie funkcją liniową ilości danych O(n2 ) złożoność kwadratowa np. T(n)=a 2 n 2 +a 1 n+a 0 O(log(n)) złożoność logarytmiczna NP - (non deterministic polynomial time problems)

Złożoność dla sortowania stabilnego sortowanie bąbelkowe (ang. bubblesort) O(n2 ) sortowanie przez wstawianie (ang. insertion sort) O(n 2 ) sortowanie przez scalanie (ang. merge sort) O(nlog(n)), wymaga O(n) dodatkowej pamięci sortowanie przez zliczanie (ang. counting sort lub count sort) O(n + k), wymaga O(n + k) dodatkowej pamięci sortowanie kubełkowe (ang. bucket sort) O(n), wymaga O(k) dodatkowej pamięci sortowanie pozycyjne (ang. radix sort) O(d(n + k)), gdzie k to wielkość domeny cyfr, a d szerokość kluczy w cyfrach. Wymaga O(n + k) dodatkowej pamięci sortowanie biblioteczne (ang. library sort) O(nlog n), pesymistyczny O(n 2 )

Złożoność dla sortowania niestabilnego sortowanie przez wybieranie (ang. selection sort) O(n2 ) może być stabilne po odpowiednich zmianach sortowanie Shella (ang. shellsort) złożoność nieznana; sortowanie grzebieniowe (ang. combsort) złożoność nieznana; sortowanie szybkie (ang. quicksort) Θ(nlog n), pesymistyczny O(n 2 ); sortowanie introspektywne (ang. introspective sort lub introsort) O(nlog n); sortowanie przez kopcowanie (ang. heapsort) O(nlog n);

Sortowanie kubełkowe Podziel zadany przedział liczb na n podprzedziałów (kubełków) o równej długości. Przypisz liczby z sortowanej tablicy do odpowiednich kubełków. Sortuj liczby w niepustych kubełkach. Wypisz po kolei zawartość niepustych kubełków.

Struktura drzewiasta pojęcia podstawowe Korzeń drzewa wierzchołek, do którego nie dochodzi żadna krawędź Następnik węzła p każdy węzeł r dla którego istnieje krawędź ( p, r ) Wierzchołek wewnętrzny każdy wierzchołek który ma następnik Liść wierzchołek nie posiadający następnika Poprzednik węzła p węzeł r z którego prowadzi krawędź ( r, p )

Struktura drzewiasta pojęcia podstawowe (cd..) Potomkami węzła p są wszystkie następniki węzła p jak i następniki tych następników itd. a do liści włącznie. Przodkiem węzła p jest jego poprzednik, ale także poprzednik tego poprzednika itd. a do korzenia włącznie. Synem węzła p jest każdy jego bezpośredni następnik. Ojcem węzła p jest jego bezpośredni poprzednik. Głębokość (lub poziom) wierzchołka w drzewie to jego odległość od korzenia Wysokość wierzchołka to maksymalna długość drogi od danego wierzchołka do liścia.

Struktury drzewiaste pojęcia podstawowe (cd..) Drzewo uporządkowane drzewo w którym gałęzie każdego węzła są uporządkowane Stopień węzła p liczba bezpośrednich potomków węzła p. Stopień drzewa maksymalny stopień z wszystkich węzłów drzewa Długość drogi do węzła x liczba krawędzi (gałęzi) przez które należy przejść od korzenia do węzła x Długość drogi drzewa (długość drogi wewnętrznej) suma dróg wszystkich jego składowych.

Drzewo binarne Drzewo binarne drzewo uporządkowane o stopniu 2) Jest to skończony zbiór elementów (węzłów), który jest albo pusty, albo zawiera korzeń (węzeł) z dwoma rozłącznymi binarnymi drzewami zwanymi lewym i prawym poddrzewem.

Reprezentacja działań arytmetycznych za pomocą DB y=(a+b/c)*(d-c*f)

Binarne drzewo poszukiwań Binarne drzewo poszukiwań (ang. Binary Search Tree (BST)) dynamiczna struktura danych będąca drzewem binarnym, w którym lewe poddrzewo każdego węzła zawiera wyłącznie elementy o kluczach nie większych niż klucz węzła a prawe poddrzewo zawiera wyłącznie elementy o kluczach nie mniejszych niż klucz węzła. Dla pełnego drzewa BST o n węzłach pesymistyczny koszt każdej z podstawowych operacji wynosi O(log n). (operacje wykonywane są wzdłuż drzewa). w skrajnym przypadku, gdy drzewo składa się z jednej gałęzi koszt ten wzrasta do O(n).

Struktury drzewiaste Element strukturalny struct slisc { char napis[25]; Int licznik; slisc* L;//lewy lisc slisc* R;//prawy lisc }; Lisc R L root osoba Lisc R L osoba Lisc R L null osoba Lisc R L null null null Gdzie wskaźnik R przechowuje adres prawego a L lewego elementu węzła null

Sortowanie z wykorzystaniem drzew Załóżmy że w drzewie chcemy przechowywać zbiór słów znajdujących się w pewnym tekście. Umieszczanie słów będzie przebiegało wg następującego algorytmu 1) Pobierz słowo 2) Porównaj słowo ze słowem w aktualnym węźle 3) Jeśli słowo jest: - mniejsze (w sensie alfabetycznym) przejdź do lewego węzła - większe (w sensie alfabetycznym) przejdź do prawego węzła - równe, zwiększ licznik węzła i wróć do pkt 1 4) Jeśli kolejny węzeł nie istnieje dodaj go, umieść w nim słowo i wróć do pkt. 1 null Lisc R L osoba Lisc R L osoba Lisc R L null null osoba Lisc R L null null

Sortowanie z wykorzystaniem drzew Najmniejszy element znajduje się w końcowym liściu lewym Największy element znajduje się w końcowym liściu prawym W każdym elemencie znajduje się ilość wystąpień danego słowa Funkcja wyświetlająca posortowane drzewo void Wyświetl(sLisc* lisc) { if(lisc->l!=null) Wyswietl(lisc->L); cout<<lisc->napis<<, <<lisc->licznik; if(lisc->r!=null) Wyswietl(lisc->R); }

Tablice z haszowaniem (hash table) Tablice z haszowaniem są stosowane, gdy z pewnego dużego zbioru danych w danej chwili musimy znać stosunkowo jego niewielki podzbiór. Elementy z tego podzbioru są zapisywane w tablicy, której indeksy dla danego elementu oblicza specjalna funkcja hashująca.

Kopce Kopiec binarny (czasem używa się też określenia sterta) (ang. binary heap) - tablicowa struktura danych reprezentująca drzewo binarne, którego wszystkie poziomy z wyjątkiem ostatniego muszą być pełne. W przypadku, gdy ostatni poziom drzewa nie jest pełny, liści ułożone są od lewej do prawej strony drzewa. index 1 2 3 4 5 6 7 8 9 10 klucz 20 16 8 10 15 2 5 7 6 3

Tworzenie kopców Na pierwszej pozycji znajduje się korzeń. Indeksy lewego i prawego syna węzła i to odpowiedni 2i oraz 2i+1. Indeks rodzica węzła i niebędącego korzeniem to floor(i/2) Wyróżniamy dwa rodzaje kopców binarnych: kopce binarne typu max w których wartość danego węzła niebędącego korzeniem jest zawsze mniejsza niż wartość jego rodzica oraz kopce binarne typu min w których wartość danego węzła niebędącego korzeniem jest zawsze większa niż wartość jego rodzica

Dodawanie elementu do kopca Dla kopca n-elementowego typu max wstaw wierzchołek na pozycję n+1 zamieniaj pozycjami z rodzicem (przepychaj w górę) aż do przywrócenia warunku kopca (czyli tak długo, aż klucz rodzica jest większy niż k, lub element dotrze na pozycję 1)

Usuwanie wierzchołka kopca usuń wierzchołek ze szczytu kopca przestaw ostatni wierzchołek z pozycji n+1 na szczyt kopca; niech k oznacza jego klucz spychaj przestawiony wierzchołek w dół, zamieniając pozycjami z większymi z dzieci, aż do przywrócenia warunku kopca (czyli aż dzieci będą mniejsze od k lub element dotrze na spód kopca)

Sortowanie kopcowe O obl (nlogn), O pam (1) Stwórz kopiec umieszczając w nim kolejne elementy tablicy począwszy od pierwszego elementu nieposortowanej tablicy kopiec Reszta nieposortowanej tablicy Z kopca o rozmiarze n usuń element nr 1 (element maksymalny) i zapisz go na końcu tablicy do początku kopca dodaj element n-1, powtarzaj aż do wyczerpania elementów kopca kopiec Posortowana tablica