Jak ocenić złożoność obliczeniową algorytmu? - 2h


Cele ogólne
Uczeń:
  • porównuje działanie różnych algorytmów dla wybranego problemu, analizuje algorytmy na podstawie ich gotowych implementacji (I.4),
  • objaśnia dobrany algorytm, uzasadnia poprawność rozwiązania na wybranych przykładach danych i ocenia jego efektywność (RI.3),
  • ilustruje i wyjaśnia rolę pojęć, obiektów i operacji matematycznych w projektowaniu rozwiązań problemów informatycznych i z innych dziedzin, posługuje się pojęciem logarytmu (RI.4),
  • definiuje pojęcia: operacje dominujące (elementarne), czasowa złożoność obliczeniowa algorytmu, złożoność pesymistyczna, złożoność oczekiwana (średnia), notacja dużego O, złożoność liniowa, złożoność logarytmiczna, złożoność kwadratowa, złożoność stała, złożoność wykładnicza, pamięciowa złożoność obliczeniowa, algorytm optymalny.
  • wyjaśnia, czym jest przekazanie parametru przez wskaźnik.
  • szacuje czasową złożoność obliczeniową algorytmów,
  • stosuje notację dużego O do szacowania złożoności czasowej algorytmów.
  • analizuje czasową złożoność algorytmów.
  • ocenia algorytmy pod kątem czasowej złożoności.
  • tworzy i projektuje algorytmy optymalne.
  • Przydatne narzędzia/oprogramowanie
  • komputer z zainstalowanym środowiskiem Code::Blocks http://www.codeblocks.org/downloads/binaries
  • Zapoznaj się tekstem w podręczniku strony od 164 do 169 ze szczególnym uwzględnieniem Podsumowania.

    Teoria obliczeń

    Teoria obliczeń to dział informatyki. Jedną z gałęzi tego działu jest teoria złożoności obliczeniowej. W uproszczeniu można powiedzieć, że zajmuje się ona oszacowaniem wydajności czasowej i pamięciowej algorytmów. Teoria złożoności obliczeniowej bazuje na wielu modelach, które służą do łatwego porównywania algorytmów.

    Przykład wyznaczania złożoności obliczeniowejń

    Załóżmy że chcemy policzyć sumę elementów tablicy. Może nam w tym pomóc następujący algorytm:

    public int sum(int[] numbers) {
    int sum = 0;
    for (int number : numbers) {
    sum += number;
    }
    return sum;
    }
    Ile mamy w nim operacji? int sum = 0;, przypisanie to jedna operacja. Następnie mamy pętlę for. Jej ciało zawiera jedną operację. Sama pętla wykona się dokładnie tyle razy ile jest elementów tablicy numbers. Liczbę tych elementów określmy jako n. Na końcu mamy instrukcję return sum;. Jest to ostatnia operacja.
    Dodając te operacje do siebie otrzymujemy wzór:
    f(n)=1+n+1=n+2
    Zatem złożoność naszego algorytmu opisana jest przez funkcję f(n) = n + 2.
    Złożoność obliczeniową określamy jako funkcję danych wejściowych algorytmu. Wyznacza się ją jak opisałem w poprzednim punkcie – licząc operacje. O ile dla naukowców znalezienie dokładnej funkcji może być bardzo istotne, to w praktyce wystarczą jej oszacowania. Takie oszacowania to notacja Ο (dużego O), notacja Ω (omega) i notacja Θ (theta).

    Notacja Ο (dużego O)

    Notacja ta zakłada, że istnieje funkcja g(n), dla której spełniona jest poniższa własność:
    ∀n⩾n0:f(n)⩽c∗g(n)
    Własność ta oznacza, że wynik funkcji g(n) pomnożony przez jakąś stałą c będzie większy bądź równy wynikowi funkcji f(n). Własność ta jest spełniona dla wszystkich n, które będą większe od n0.

    Notacja Ο jest oszacowaniem z góry. Zatem można powiedzieć, że jeśli algorytm ma złożoność Ο(n^2) to ma także złożoność Ο(n^3) czy nawet Ο(n!). Jednak Ο(n^2) może być najlepszym oszacowaniem złożoności danego algorytmu.

    Z racji tego, że jest to oszacowanie pomijamy w nim wszelkiego rodzaju stałe. Zatem Ο(2n + 123), Ο(2n) i Ο(n) to ta sama złożoność obliczeniowa. Stałe te i tak nie mają znaczenia przy odpowiednio dużych wartościach n.

    O(1)

    Ο(n)

    Złożoność liniowa. Jest to specyficzny przypadek złożoności wielomianowej. Czas rozwiązania problemu jest wprost proporcjonalny do wielkości danych wejściowych. Przykład problemu, dla którego istnieje algorytm Ο(n):
    Na wejściu programu jest tablica liczb o długości N. Znajdź sumę wszystkich liczb w tablicy wejściowej.

    Ο(log(n))

    Złożoność logarytmiczna, czas rozwiązania zależy od wyniku logarytmu3 z wielkości danych wejściowych. Przykład problemu, dla którego istnieje algorytm Ο(log(n)):
    Na wejściu programu jest posortowana tablica liczb o długości N. Sprawdź czy liczba x istnieje w tablicy wejściowej.
    To popularny algorytm przeszukiwania binarnego. Jego nazwa pochodzi od tego, że przy każdej iteracji algorytmu dzielimy przeszukiwany zbiór na dwie równe4 części. Algorytmy, które dzielą w ten sposób problem na mniejsze problemy przeważnie są zależne od logarytmu wielkości danych wejściowych.

    Warto podkreślić praktyczne zastosowanie łączenia zdań logicznych poprzez spójniki logiczne w zdania złożone. Dzięki temu możemy pisać kody źródłowe, które są krótsze i bardziej zwięzłe, gdyż wiele instrukcji warunkowych można zastąpić jedną.

    Realizując ćwiczenia i zadania polegające na stworzeniu kodu źródłowego o określonym działaniu, warto podkreślić, by przed przystąpieniem do pisania kodu źródłowego pamiętać o wcześniejszych etapach rozwiązywania problemów z wykorzystaniem komputera – na przykład sformułowaniu algorytmu.

    Wykorzystując zmienne typu string w języku C++, do kodu źródłowego dodaje się dyrektywę #include . Warto podkreślić, że string i iostream nie są jedynymi bibliotekami, z jakich można korzystać w tym języku. Biblioteki znacznie zwiększają możliwości języka. Można to zilustować animacją Podstawowe biblioteki języka C++, dostępną na stronie dlanauczyciela.pl.

     

    Zadanie 2 (łatwe)

    Przelicz na system dwójkowy podane poniżej liczby dziesiętne:
    10(10) = (2)  

    .

     100(10) = (2)  

    .

    1000(10) = (2)  

    .

    10000(10) = (2)  

    .

    93673(10) = (2)  

    .

     

    Zadanie 3 (dosyć łatwe)

    Ile razy wzrośnie zakres n-bitowych liczb binarnych, gdy liczbę bitów zwiększymy o 1, 2, 3, 4, m bitów? Odpowiedź uzasadnij.

    Zadanie 4 (łatwe)

    Obliczyć wartość dziesiętną następujących liczb dwójkowych:
    11,101(2) = (10)  

    .

    101,1101(2) = (10)  

    .

    1101011,11001(2) = (10)  

    .


    Zadanie 5 (łatwe)

    Przeliczyć podane liczby dziesiętne na zapis dwójkowy:
    7,6875(10) = (2)  

    .

    15,84375(10) = (2)  

    .

    27,8359375(10) = (2)  

    .


    Zadanie 6 (dosyć łatwe)

    Wyznaczyć 100 cyfr ułamkowych dwójkowego rozwinięcia liczby dziesiętnej 0,8(10).

     

    Zadanie 7 (dosyć łatwe)

    Wyznacz błąd względny i bezwzględny rozwinięcia dwójkowego liczby dziesiętnej 0,2(10) o dokładności 10 binarnych cyfr ułamkowych.






    Zadanie 1 (łatwe)- binarne na dziesiętne



    Oblicz wartość następujących liczb binarnych:
    1100000011(2) =   

    .

    111000111(2) =   

    .

    10101010101(2) =   

    .

    11110000(2) =   

    .

    11001100110011(2) =   

    .




    Podział liczby na cyfry



    Zapisz specyfikację oraz algorytm wypisujący poszczególne cyfry liczby.


    Schemat blokowy



    Zapisz w postaci schematu blokowego algorytm obliczający dla danej liczby naturalnej liczbę jej dzielników właściwych większych od 1. Wykorzystaj pseudokod zapisany w podręczniku na stronie 17 /Ćwiczenie 5 str. 16/


    Programy w C++



    Napisz w środowisku CodeBlocks/ na st następujące programy:
    1. Program sprawdzający czy liczba jest parzysta
    2. Program wypisujący poszczególne cyfry z liczby
    3. Program wyświetlający liczbę dzielnikó właściwych liczby n większych od 1
    4. Program wypisujący NWD dla dwóch liczb naturalnych a i b



    Test wiedzy



    |
    		Pytanie 1.  Zamień liczbę 111 systemu dwójkowego na dziesiątkowy i podaj prawidłowy wynik
    
    		A/  6
    		B/  7
    		C/  10
    		D/  11
    		E/  12
    		
    		Pytanie 2  Zamień liczbę 11001 systemu dwójkowego na dziesiątkowy i podaj prawidłowy wynik
    
    		A/  20
    		B/  24
    		C/  25
    		D/  27
    		E/  31
    		
    		Pytanie 3  Zamień liczbę 101 systemu dwójkowego na dziesiątkowy i podaj prawidłowy wynik
    
    		A/  5
    		B/  7
    		C/  9
    		D/  11
    		E/  13
    		
    		Pytanie 4  Zamień liczbę 110011 systemu dwójkowego na dziesiątkowy i podaj prawidłowy wynik
    
    		A/  37
    		B/  40
    		C/  47
    		D/  50
    		E/  51
    		
    		Pytanie 5  Zamień liczbę 10001 systemu dwójkowego na dziesiątkowy i podaj prawidłowy wynik
    
    		A/  12
    		B/  14
    		C/  17
    		D/  21
    		E/  27
    
    		Pytanie 6  Zamień liczbę 56 systemu dziesiętnego na dwójkowy i podaj prawidłowy wynik
    
    		110011
    		111000
    		110101
    		110101
    		111111
    		
    		Pytanie 7  Zamień liczbę 20 systemu dziesiętnego na dwójkowy i podaj prawidłowy wynik
    
    		10100
    		11111
    		10010
    		10001
    		111101
    
    		Pytanie 8  Zamień liczbę 17 systemu dziesiętnego na dwójkowy i podaj prawidłowy wynik
    
    		A/  11111
    		B/  10101
    		C/  100101
    		D/  10111
    		E/  10001
    		
    		Pytanie 9  Zamień liczbę 120 systemu dziesiętnego na dwójkowy i podaj prawidłowy wynik
    
    		A/  1100100
    		B/  1101100
    		C/  1100110
    		D/  1000010
    		E/  1111000
    		
    		Pytanie 10  Zamień liczbę 6 systemu dziesiętnego na dwójkowy i podaj prawidłowy wynik
    
    		A/  110
    		B/  101
    		C/  100
    		D/  111
    		E/  1001
    		
    		Pytanie 11	Zamień liczbę 76 systemu ósemkowego na szesnastkowy i podaj prawidłowy wynik
    		
    		A/  A3
    		B/  4F3
    		C/  4C
    		D/  2A
    		E/  3E
    		
    		Pytanie 12  Zamień liczbę 5F systemu szesnastkowego na dziesiątkowy i podaj prawidłowy wynik
    
    		A/  79
    		B/  87
    		C/  64
    		D/  95
    		E/  112
    			
    		



    Podsumowanie



  • Instrukcja wejścia (wczytywanie danych): cin >> nazwa_zmiennej
  • Instrukcja wyjścia (wypisanie informacji na ekran) : cout << "teks do wyswietlenia" ;
    cout <<nazwa_zmiennej;
  • Instrukcja warunkowa if - else w języku C++ ma postać:
    if (warunek) instrukcja na tak;
    else instrukcja na nie;
  • Instrukcja pętli for
    for
    ( inicjalizacja zmiennej; warunek; aktualizacja zmiennej )
    {
    // Wykonywany kod, jeśli warunek został spełniony
    }
    Przykład:
    for ( int i = 0; i < 10; i++ )
    {
    cout << i << endl;
    }
  • Etapy rozwiązywania problemu: określenie problemu (zadania), podanie specyfikacji, sformułowanie rozwiązania - algorytmu, zaprogramowanie rozwiązania, testowanie rozwiązania.
  • Specyfikacja zadania to określenie danych i wyniku oraz określenie związku między nimi
  • Algorytm to skończony ciąg instrukcji prowadzących do rozwiązania problemu
  • Zapis algorytmu w języku programowania to kod źródłowy programu
  • Testowanie programu polega na wielokrotnym uruchamianiuu go dla różnych danych, dane muszą spełniać warunki specyfikacji.
  • Podstawowe instrukcje sterujące to: instrukcja warunkowa, instrukcja iteracji (pętla)
  • Translatory (które dzielimy na na kompilatory i interpretery) to programy tłumaczące kod źródłowy programu na język maszynowy.
  • Zintegrowane środowisko programistyczne (IDE) to program posiadający edytor do pisania kodu źródłowego, translator oraz funkcje ułatwiające uruchamianie programów



  • Przykładowe rozwiązania oraz komentarze do wybranych ćwiczeń i zadań
  • (ćw. 1, s. 28)
    a) Największą liczbą ośmiocyfrową w systemie dwójkowym jest 11111111, której zapis dziesiętny to 255.
    b) Największą liczbą szesnastocyfrową w systemie dwójkowym jest 1111111111111111, której zapis dziesiętny to 65535. Rozwiązanie w pliku T02_CW1.
  • (ćw. 2, s. 29) Lista kroków: Kroki 2, 3 powtarzaj, aż liczba będzie równa 0: Podziel liczbę przez 2. Część całkowitą z dzielenia liczby zapisz, a resztę z dzielenia wypisz. Rozwiązanie w pliku T02_CW2.
  • (ćw. 3, s. 31) Oczekiwany zapis w systemie szesnastkowym to B3. Rozwiązanie w pliku T02_CW3.
  • (ćw. 4, s. 32)
    a) 2 bajty to 16 bitów, czyli w zapisie binarnym 16 cyfr. Największą liczbą będzie zatem 1111111111111111 zapisane binarnie, czyli 65535 w systemie dziesiętnym.
    b) 78 w systemie binarnym to 1001110, liczba składa się z 7 znaków, czyli do zapisu potrzeba minimum 7 bitów. Rozwiązanie w pliku T02_CW4.
  • (ćw. 5, s. 34) a), c) Aby przedstawić w kodzie U2 liczbę ujemną, należy najpierw dodać do niej wartość podwojonej wagi najstarszego bitu. Na przykład, jeśli liczbę −100 chcemy zapisać w U2 za pomocą 8 bitów, dodajemy do niej liczbę 256, więc otrzymujemy: −100 + 256 = 156. Liczba −100 będzie zatem odpowiadać w kodzie U2 liczbie dziesiętnej 156, przedstawionej w sposób binarny bez znaku. b) Dla liczb nieujemnych zapis w kodzie U2 odpowiada naturalnemu zapisowi liczb binarnych. Rozwiązanie w pliku T02_CW5.
  • (ćw. 6, s. 35) Odpowiedź: −43. Rozwiązanie w pliku T02_CW6.
  • (ćw. 7, s. 36) Rozwiązanie w pliku T02_CW7.
  • (ćw. 8, s. 39) Rozwiązanie w pliku T02_CW8.
  • (ćw. 9, s. 42) a) Zapis operatora koniunkcji w języku C++ to &&. Kod źródłowy do realizacji tego podpunktu znajduje się na s. 41. Rozwiązanie w pliku T02_CW9.
  • (zad. 1) Odpowiedź: 551. Rozwiązanie w pliku T02_ZAD1.
  • (zad. 2) Odpowiedź: 89D0. Rozwiązanie w pliku T02_ZAD2.
  • (zad. 3) Z systemu szesnastkowego można dokonać bezpośredniego przeliczenia na system binarny, przeliczając poszczególne cyfry. E → 1110, 0 → 0000, A → 1010, 8 → 1000, zatem cała liczba w zapisie binarnym to 1110000010101000. Rozwiązanie w pliku T02_ZAD3.
  • (zad. 4) Odpowiedź: 35243. Rozwiązanie w pliku T02_ZAD4.
  • (zad. 5) Odpowiedź: −119. Rozwiązanie w pliku T02_ZAD5.
  • (zad. 6) Rozwiązanie w pliku T02_ZAD6.
  • (zad. 7) Rozwiązanie w pliku T02_ZAD7.
  • (zad. 8) Rozwiązanie w pliku T02_ZAD8.
  • (zad. 9) Liczba Część całkowita z dzielenia przez 4 Reszta z dzielenia przez 4 80 20 0 20 5 0 5 1 1 1 0 1 Liczbę 1100 w systemie czwórkowym można przedstawić, wykorzystując dwa znaki, ustawione parami jeden pod drugim. Poprawną odpowiedzią będzie c., gdyż kropki mogą odpowiadać cyfrze 1, a gwiazdki, cyfrze 0. Rozwiązanie w pliku T02_ZAD9.
  • (zad. 10) Rozwiązanie w pliku T02_ZAD10.
  • (zad. 11) Rozwiązanie w pliku T02_ZAD11.
  • (zad. 12) Rozwiązanie w pliku T02_ZAD12.
  • (zad. 13) Rozwiązanie /T02_ZAD13/.
  • (zad. 14) Rozwiązanie w pliku T02_ZAD14.
  • (zad. 15) Rozwiązanie w pliku T02_ZAD15.
  • (zad. 16) Rozwiązanie w pliku T02_ZAD16.
  • (zad. 17) Przy rozwiązywaniu zadania można posłużyć się także tabelą kodów ASCII ze s. 37. Zdaniem, którym firma Google podzieliła się z użytkownikami Twittera, było: I’m feeling lucky. Rozwiązanie w pliku T02_ZAD17.

    Rozwiązania do ćwiczeń z tej strony

    Ćwiczenie 5 - test
  • 1 --> B; 2 --> C; 3 --> A; 4 --> E; 5 --> C; 6 --> B; 7 --> A; 8 --> E; 9 --> E; 10 --> A; 11 --> E; 12 --> D;