Literatura T. H. Cormen, C. E. Leiserson, R. L. Rivest, Wprowadzenie do algorytmów, WNT, 1997, J. Grębosz, Symfonia C++ standard, Editions 200 Kraków,2008, D. Harel, Rzecz o istocie informatyki (algorytmika), WNT, 1992, K. Jamsa, Wygraj z C++, ZNI "MIKOM", 1996.
Początek W IV w. p. n. e. Euklides podał algorytm wyznaczania NWD dwóch liczb naturalnych - jest to pierwszy znany algorytm. W III w. p. n. e. Eratostenes podał metodę znajdowania liczb pierwszych. Słowo algorytm wywodzi się od nazwiska perskiego matematyka z IX wieku Muhammeda Alchwarizmi (łac. Algorismus), który podał reguły dodawania, odejmowania, mnożenia i dzielenia liczb dziesiętnych.
Pierwsze kalkulatory William Oughtred stworzył suwak logarytmiczny w 1632 roku - po tym jak John Napier odkrył logarytmy. W 1642 roku Blaise Pascal stworzył Pascaline - pierwszy mechaniczny kalkulator - posiadający 8 obrotowych tarcz, dało się na nim wykonywać operacje dodawania i odejmowania na ośmiocyfrowych liczbach (zbudował takich urządzeń koło pięćdziesięciu - można je oglądać w muzeach). W 1694 roku Gottfried Wilhelm Leibniz zbudował Staffelwalze - wałek korkowy - maszynę do mnożenia (którą zaprojektował w 1671 roku). Thomas de Colmar skonstruował w 1820 roku kalkulator działający dzięki bębnom z kołami zębatymi - był on masowo produkowany przez kolejne 100 lat. Kalkulatory bazujące na prototypie stworzonym przez Willgodta Odhnera w 1890 roku używające tarcz z ruchomymi bolcami używane były do lat siedemdziesiątych dwudziestego wieku.
Pierwsze komputery W 1833 roku Charles Babbage zbudował "maszyne różnicową" służącą do obliczania pewnych wzorów matematycznych. Zrobił też projekt "maszyny analitycznej" do realizacji algorytmów zakodowanych w postaci otworów wydziurkowanych w kartach (wzorowanej na krośnie tkackim wynalezionym w 1801 roku przez Josepha Jacquarda). Kiedy Ada Byron zasugerowała, aby zilustrować działanie maszyny, pokazując, jak obliczałaby liczby Bernoulliego powstał algorytm uważany dziś za pierwszy program komputerowy.
Pierwsze współczesne komputery W 1890 roku Herman Hollerith zbudował maszyną tabulacyjną umożliwiającą dokonanie spisu ludności w USA w niecałe 3 lata. Do wprowadzania, sortowania i podliczania danych wykorzystywała dziurkowane karty. W 1896 roku Hollerith założył firmę Tabulating Machine Company, która w 1911 przekształciła się w International Business Machines (IBM). W 1939 Wiliam Hewlett i David Packard założyli firmę HP. Pierwszy komputer używający systemu binarnego został stworzony w 1933 roku przez Konrada Zuse. W 1945 roku powstał ENIAC - ważący 27 ton i zawierający 18000 lamp eelektronowych komputer służył do obliczeń różnorakiego rodzaju. W tym samym roku powstały tranzystory, dzięki którym kilka lat póżniej prędkość i wydajność komputerów została zwielokrotniona.
Asemblery Pierwsze "prawdziwe" komputery powstały w latach 40-tych XX wieku. Pierwsze języki programowania to asemblery. Są to języki bardzo zbliżone do kodu maszynowego. Jedna instrukcja języka odpowiada tutaj jednej instrukcji dla procesora. Języki tego typu nazywamy językami programowania niskiego poziomu. Kompilatory (języki tłumaczące zapis symboliczny na maszynowy) w tym przypadku również nazywane są asemblerami.
Języki wysokiego poziomu Aby ułatwić pracę użytkownikowi powstały języki wysokiego poziomu. Są to języki w dużym stopniu zrozumiałe dla użytkownika. Do przerobienia na język maszynowy konieczny jest kompilator, który rozbija polecenia na podpolecenia języka maszynowego. Przykładowe dzisiaj stosowane języki wysokiego poziomu to Java, Pascal, Python. Pierwszym takim językiem był Fortran stworzony w latach 50-tych przez Johna Backusa. Na przełomie lat 50-tych i 60-tych powstał Algol pierwszy język zaprojektowany we współpracy międzynarodowej.
Początki C W 1966 roku Martin Richards zaprojektował język BCPL (Basic Combined Programming Language). Jego następcą był język B, natomiast w 1972 roku Dennis Ritchie i Ken Thompson stworzyli język C (dla rozszerzenia możliwości języka B). Dopiero w 1978 roku język zyskał (ogromne) zainteresowanie w związku z pojawieniem się książki Briana Kerninghana i Dennisa Ritchiego pt. The C Programming Language (Język C, WNT, 1988). W 1981 roku po wprowadzeniu przez IBM komputera PC, język C oderwał się od UNIXowych korzeni i stał się populanym językiem mikrokomputerów.
C++ Język C zyskał popularność wśród programistów, gdyż umożliwia większą, niż inne języki kontrolę nad komputerem (można powiedzieć, że jest językiem niższego poziomu, niż np. Java). W roku 1983 Bjarne Stroustrup rozwinął C++. Rozszerza on C między innymi o obiekty (struktury mogące przechowywać dane i funkcje).
Kompilatory Rozwojowi języka grozi zawsze rozbicie na wiele niezgodnych ze sobą pakietów programowania. Kompilatory języka C dla PC są zgodne w obszarach objętych normą ANSI/ISO (American National Standarts Institute - Amerykański Narodowy Instytut Normalizacji, International Organization for Standardization - Międzynarodowa Organizacja Normalizacyjna), ale poza nimi występują różnice.
Najkrótszy możliwy program Najkrótszy możliwy program w języku C to main() {} Każdy program musi posiadać funkcję główną, której polecenia znajdują się w nawiasie {}. (W zależności od kompilatora do funkcji głównej często dodaje się przedrostek void lub int). Oczywiście powyższy program nic nie robi.
Instrukcja include Aby móc używać podstawowych komend wejścia/wyjścia musimy umieścić w programie ich definicje. Robimy to za pomocą instrukcji #include. Jest to dyrektywa preprocesora (kompilator wykonuje ją przed kompilowaniem). Dołącza ona plik odpowiedniej biblioteki. W poniższym przypadku mówi kompilatorowi, aby pobrał plik stdio.h i umieścił jego zawartość w tym miejscu programu.
Pierwszy program #include <stdio.h> int main() { printf("witaj świecie!"); //wypisuje na ekran: // Witaj świecie! }
Biblioteki W trakcie tego wykładu zamiast stdio.h będziemy używać biblioteki iostream. Ponadto dodamy przestrzeń nazw poleceniem using namespace std; (bez tego przed niektórymi poleceniami konieczne byłoby dopisywanie przedrostka std::). (Podobny do iostream z przestrzenią nazw std zasób poleceń, chociaż mniejszy, daje biblioteka iostream.h.)
Polecenia wejścia - wyjścia Jako polecenia standartowego wyjścia (wypisywania na ekran) będziemy wówczas mogli użyć cout (jest to polecenie języka C++) zamiast komendy printf z biblioteki stdio.h. Dla standartowego wejścia użyjemy komendy cin (a nie polecenia scanf).
Polecenia wejścia - wyjścia Wczytywane zmienne oddzielamy od siebie (oraz od polecenia cin) sybmolem >>. Wypisywane zmienne oraz wypisywany tekst (który wstawiamy w cudzysłów) odzielamy symbolem << (składnia poleceń printf oraz scanf jest bardziej skomplikowana)
Polecenia pomocnicze Poleceniem system("chcp 1250"); otrzymujemy możliwość dostania w konsoli polskich znaków - trzeba w tym celu we właściwościach konsoli wybrać czcionka: Lucida Console. Poleceniem ("cls"); możemy wyczyścić tekst w konsoli - po uruchomieniu programu pojawi się tylko tekst tego programu dotyczący. Aby konsola nie zniknęła natychmiast po wykonaniu zadania 1 w treści kodu programu musimy dopisać polecenie system("pause"); 1 dotyczy np. wxdev-c++
Zmienne typu char Zmienna typu char odpowiada jednemu znakowi i zajmuje jeden bajt pamięci (może ona służyć także do reprezentowania liczb w zakresie od -128 do 127).
Zmienne typu char Poniższy program wczytuje, a następnie wypisuje dwie litery. #include<iostream> using namespace std; int main( ) { char a, b; //deklarujemy dwie zmienne typu char cout<<"podaj swoje inicjały\n"; //"\n"-przejście do nowej linii cin >> a >> b; //wczytanie zmiennych a, b cout<<"imię zaczyna się od "<<a<<", a nazwisko od "<<b<<".\n"; }
Tablica znaków W poniższej funkcji głównej programu aby wczytać ciąg liter użyjemy tablicy znaków typu char. char IMIE[15]; //15 - maksymalna długość ciągu znaków cout << "Jak masz na imię? \n"; cin >> IMIE; //wczytanie ciągu znaków cout << "Witaj " << IMIE << "! \n";
Zmienne typu string Zamiast deklarować tablicę znaków możemy użyć zmiennej typu string. Niektóre kompilatory wymagają dodania biblioteki string (czyli dopisania w nagłówku #include <string>). Napiszmy jeszcze raz podobny program string imie, adres, dane; cout << "Podaj swoje imię. \n"; cin >> imie; cout << "Podaj swój adres. \n"; cin >> adres; //wpisujemy bez użycia spacji
Zmienne typu string Na koniec wypiszmy dane cout << "imię: " << imie << ", adres: " << adres << endl; (Ten sam efekt wypisania danych uzyskamy pisząc dane="imię: "+imie+", adres: "+adres; cout << dane << endl; //endl = "\n" )
Zmienna typu int Jednym z najbardziej popularnych typów zmiennych jest zmienna typu int. Typ int ma obecnie 2 rozmiar czterech bajtów (32 bitów) i obejmuje liczby całkowite z zakresu od -2147483648 (czyli -2 31 ) do 2147483647. Zmienna unsigned mając 4 bajty reprezentuje liczby od 0 do 2 32 1. Gdybyśmy chcieli działać na jeszcze większych liczbach możemy użyć zmiennej typu long long o rozmiarze ośmiu bajtów. 2 zależnie od kompilatora i od procesora, można to sprawdzić wpisując w programie cout «sizeof(int);
Zmienna typu int Dopiszmy do naszego programu pytanie o wiek. int wiek; int rok=2011; //deklarując zmienną możemy przypisać jej wartość cout << "Ile lat skończyłeś(łaś) lub skończysz w tym roku? \n"; cin >> wiek; rok = rok-wiek; //przypisanie nowej wartości zmiennej rok cout << "Urodziłeś się w " << rok << " roku. \n";
Działania, zakres int-a Oczywiście, +, są standartowymi działaniami. Operacja / daje dla zmiennych całkowitych wynik dzielenia z resztą (działanie na int-ach musi zwracać int-a). Działanie % daje resztę z dzielenia. Łatwo podać przykład dwóch liczb będących w zakresie int-a, których iloczyn bądź suma wychodzą poza ten zakres - wówczas otrzymamy nieprawidłowy wynik.
Kod ASCII Kod ASCII (American Standard Code for Information Interchange) przyporządkowuje liczby z zakresu 0-127 literom i innym symbolom. Duże litery są reprezentowane liczbami od 65 do 90, a małe liczbami od 97 do 122. Wykonując poniższy program można sprawdzić jaką liczbą jest kodowany dany znak.
Kod ASCII Pisząc char znak; cin >> znak; int kod=znak; otrzymamy przyporządkowanie znakowi wpisanemu z klawiatury jego kodu ASCII. Następnie możemy go wypisać cout<<"znak "<<znak<<" jest kodowany liczbą "<<kod<<".";
Zadania Zadanie Napisz program proszący o podanie pięciu liter, a następnie wypisujący je na ekran w odwrotnej kolejności. Zadanie Napisz program pytający o imię, nazwisko oraz miasto zamieszkania, a następnie wypisujący te dane na ekran.
Zadania Zadanie Napisz program proszący o podanie dwóch liczb całkowitych, a następnie wypisujący ich sumę, różnicę, iloczyn oraz wynik dzielenia z resztą pierwszej przez drugą i drugiej przez pierwszą. Zadanie Napisz program pytający o rok i miesiąc urodzenia oraz wypisujący na ekran ilość przeżytych miesięcy. Zadanie Pisząc c=a%b; przypisujemy zmiennej c resztę z dzielenia a przez b. Napisz program obliczjący resztę z dzielenia pierwszej podanej liczby przez drugą bez użycia %.
Zadania Zadanie Napisz program proszący o wpisanie kolejnych trzech znaków z klawiatury i wypisujący ich kod ASCII. Zadanie Napisz program proszący o podanie liczby z zakresu od 97 do 122, a następnie wypisujący dla jakiej litery podana liczba jest kodem.
Instrukcja if Instrukcja warunkowa if umożliwia wykonanie pewnego polecenia lub bloku poleceń przy założeniu, że spełniony jest odpowiedni warunek. Może być postaci if(warunek) instrukcje lub if(warunek) instrukcja1 else instrukcja2 i tak dalej.
Instrukcja if Jeżeli instrukcja składa się z więcej niż jednego polecenia musi cała być wzięta w nawias { }. W warunku często używamy operacji relacji: == równy, > większy, >= większy lub równy, < mniejszy, <= mniejszy lub równy,! = różny oraz operatorów logicznych && oraz, lub,! nieprawda, że.
Podzielność Sprawdźmy podzielność jednej liczby całkowitej przez drugą. int a, b; cout << "Podaj a \n"; cin >> a; cout << "Podaj b \n"; cin >> b; if (a%b>0) cout << "Liczba a nie jest podzielna przez b. \n"; else cout << "Liczba b dzieli a. \n";
Minimum Znajdźmy teraz najmniejszą z trzech różnych liczb. int a, b, c; cout << "Podaj trzy różne liczby \n"; cin >> a >> b >> c; if (a<b && a<c) cout << "Pierwsza podadana liczba jest najmniejsza. \n"; else if (b<c) cout << "Druga podadana liczba jest najmniejsza. \n"; else cout << "Trzecia podadana liczba jest najmniejsza. \n";
Typ zmiennoprzecinkowy W poniższej funkcji użyjemy zmiennej typu float. Zajmuje ona 4 bajty pamięci, ma zakres od 3.4 10 38 do 3.4 10 38 i dokładność 6 cyfr znaczących. Przykładowo zapis 12.3456e+09 oznacza liczbę 12.3456 10 9, zapis 6.54321e-13 oznacza 6.54321 10 13.
Równanie liniowe Aby rozwiązać równanie liniowe ax = b najpierw wczytajemy dane. float a, b, wynik; cout <<"Rozwiążmy równanie liniowe postaci ax=b. \n"; cout << "a="; cin >> a; cout << "b="; cin >> b;
Równanie liniowe Mamy trzy możliwości if (a!=0) { wynik=b/a; //dla typu float / jest zwykłym dzieleniem cout <<"Rozwiazaniem jest b/a=" <<wynik <<endl; } else if (b==0) cout <<"Rozwiązaniem jest każda liczba rzeczywista.\n"; else cout << "Równanie nie ma rozwiązań. \n";
Instrukcja switch W przypadku, gdy alternatywnych warunków jest więcej (i gdy da się je przedstawić za pomocą wartości) warto użyć instrukcji switch. Po wczytaniu danych float a,b; char dzialanie; cout << "Wprowadz dane: <liczba> <dzialanie> <liczba> \n"; cin >> a >> dzialanie >> b; możemy napisać funkcję definiującą wybrane działanie.
Instrukcja switch switch(dzialanie) { case + : cout << a+b << endl; break; case - : cout << a-b << endl; break; case * : cout << a*b << endl; break; case / : if(b==0) cout << "Nie dziel przez zero! \n" else cout << a/b << endl; break; default: cout << "Nieznane działanie" << endl; }
Dokładność Jeśli po uruchomieniu powyższego programu wpiszemy sumowanie bardzo dużej i bardzo małej liczby możemy dostać nieprawdziwy lub niedokładny wynik. Przykładowo sumując 100000 + 0.002 otrzymamy wynik 100000. Sześciocyfrowa dokładność zmiennych typu float nie pozwala otrzymać precyzyjnego wyniku w tym przypadku.
Dokładność Możemy poprawić dokładność używając zmiennej typu double. Ma on 8 bajtów i dokładność 15 cyfr znaczących. Można za jej pomocą reprezentować liczby z zakresu od 1.7e 308 do 1.7e + 308. Aby program wypisywał wynik obliczeń ze zwiększoną dokładnością musimy przed poleceniami wypisywania dodać polecenie cout.precision(15);
Inne działania Jeżeli chcemy poza standardowymi działaniami otrzymać na przykład sin(a), cos(a), log(a) lub a b poleceniem pow(a,b) albo całość z a poleceniem floor(a), musimy dołączyć do programu bibliotekę math.h. Napiszmy funkcję, która dla podanych liczb rzeczywistych a, b sprawdza, czy większe jest a b, czy b a.
include<math.h> float a,b; cout << "Wpisz dwie liczby \n"; cin >> a >> b; if(pow(a,b)>pow(b,a)) cout<<"a^b="<<pow(a,b)<<"<b^a="<<pow(b,a)<<endl; else if(pow(a,b)<pow(b,a)) cout<<"a^b="<<pow(a,b)<<"<b^a="<<pow(b,a)<<endl; else cout<<"a^b="<<pow(a,b)<<"=b^a="<<pow(b,a)<<endl;
Zadania Zadanie Napisz program proszący o podanie liczby całkowitej, a następnie wypisujący jej wartość bezwzględną. Zadanie Napisz (używając instrukcji switch) program pytający o ocenę jaką dostałeś na ostatnim egzaminie i po wczytaniu cyfry wypisujący ocenę pisemnie.
Zadania Zadanie Napisz program proszący o podanie trzech liczb całkowitych, a następnie stwierdzający, która z nich (lub które) jest największa. Zadanie Napisz program proszący o wybranie obliczenia pole koła lub pola trójkąta równobocznego, następnie proszący o podanie średnicy (lub długości boku) i obliczający (z dokładnością 15 cyfr) pole wybranej figury. Zadanie Napisz program proszący o podanie parametrów a, b, c, a następnie rozwiązujący równanie kwadratowe ax 2 + bx + c = 0.
Pętla for Pętla for ma postać for(stan początkowy; warunek; zmiany) instrukcje Przykładowo pętla służąca do wypisania 20 kolejnych liczb parzystych będzie wyglądać następująco int i, n=20; for(i = 0; i < n; i++) //instrukcje będą wykonywane dopóki i<n cout << 2*i << ", "; //i++ oznacza zwiększenie i o 1
Pętla for Napiszmy funkcję proszącą o podanie liczby naturalnej n, a następnie dla liczb i < n sprawdzający, czy i jest dzielnikiem n. int i, n; cout <<"Podaj liczbę n"<<endl; cin >>n; for(i = 2; i < n; i++) { if(n%i==0) cout <<"Liczba "<<i<<" dzieli "<<n<<".\n"; else cout <<"Liczba "<<i<<" nie dzieli "<<n<<".\n"; }
Liczby pierwsze Chcąc dostać odpowiedź na pytanie, czy n jest liczbą pierwszą w powyższym programie zadeklarujemy dodatkowo zmienną odp=0 oraz zmodyfikujemy warunek w następujący sposób if(n%i==0) odp=1; a na koniec napiszemy if(odp==0) cout <<"Liczba "<<n<<" jest pierwsza.\n"; else cout <<"Liczba "<<n<<" nie jest pierwsza.\n";
Ciąg liczb losowych Napiszmy program wczytujący losowy ciąg liczb z zakresu od 1 do 1000, a następnie znajdujący element maksymalny. Aby móc losować liczby poleceniem rand() potrzebujemy (dla niektórych kompilatorów) biblioteki stdlib.h. Aby tablica "wylosowanych" liczb nie była za każdym wykonaniem programu taka sama (początkowa wartość generatora jest za każdym razem taka sama) wstawimy przed pętlą polecenie srand(time(0)); które jako wartość początkową bierze liczbę sekund, która upłynęła od północy 1 stycznia 1970 roku. Potrzebna jest do tego (dla pewnych kompilatorów) biblioteka ctime. (Ponadto w preambule możemy zdefiniować stałą N równą 20 poleceniem #define N 20.)
Maksimum Losowanie ciągu liczb będzie wyglądać następująco int main() { srand(time(0)); int i, max=0; int T[N]; //deklarujemy tablicę (statyczną) for(i = 0; i < N; i++) { T[i] = rand() % (1000); cout << T[i] << endl; } }
Maksimum Dopiszmy pętlę znajdującą element maksymalny. for(i = 0; i < N; i++) if(t[i]>max) max = T[i]; cout << "Maksymalna z powyższych liczb to " << max << endl;
Złożoność Zauważmy, że szukając maksymalnego elementu wykonaliśmy n operacji porównania. Oznacza to, że program ma złożoność obliczeniową O(n) - czas wykonania jest proporcjonalny do liczby n danych wejściowych (rośnie liniowo ze wzrostem liczby elementów).
Złożoność Programy o złożoności O(n) działają dosyć szybko nawet dla dużych n. Dużo wolniej działają programy o złożoności O(n 2 ) (przykładowo dla n = 10000 średnio szybkiemu komputerowi wykonanie programu zajmie około jednej sekundy, czas rośnie tutaj z kwadratem, więc dla n = 40000 wykonanie programu zajmie około 16 sekund, dla n = 100000 - ponad minutę). Dlatego warto, gdy tylko to możliwe, zastępować je programami o złożoności O(n logn).
Zadania Zadanie Napisz program sprawdzający czy podana liczba jest liczbą pierwszą. Zadanie Napisz program liczący silnię zadanej liczby. (Co się stanie, gdy wynik przekroczy zakres inta?) Zadanie Napisz program proszący o podanie liczby miesięcy n, kwoty k oraz oprocentowania rocznego p (z miesięczną kapitalizacją) i liczący jaką kwotę otrzymamy wpłacając k złotych na n miesięcy.
Zadania Zadanie Napisz program proszący o podanie liczby n, a następnie o wpisanie n liczb rzeczywistych i liczący ich sumę. (Czy możemy dodawać liczby bardzo duże do liczb bliskich zeru?) Zadanie Napisz program wczytujący losowo ciąg 1000 liczb naturalnych mniejszych od 10000 i sprawdzający, czy w tablicy znajduje się liczba 1000, a jeśli tak, to na którym miejscu. Zadanie Napisz program wczytujący dla podanej liczby n < 10000 ciąg liczb naturalnych długości n oraz znajdujący równocześnie (jedna pętla) minimalny i maksymalny element tego ciągu. (Jaka jest najmniejsza, a jaka największa ilość porównań, które program wykona?)
Pętla do while Pętla do while ma postać do instrukcje while(warunek); Instrukcje będą wykonywane dopóki spełniony będzie zadany warunek (zatem wykonanie instrukcji powinno między innymi zmieniać wartość zadaną w warunku).
Pętla do while - suma Napiszmy pętlę sumującą kolejne podawane liczby do momentu, gdy suma przekroczy wartość 100.. int a, suma=0; cout << "Wpisuj kolejne liczby. \n"; do { cin >> a; suma+=a; //równoznaczne z suma=suma+a; cout << "suma=" << suma << endl; } while(suma<=100);
Pętla while W powyższym przypadku instrukcja zostanie wykonana co najmniej raz. Jeśli zastosujemy pętlę while zaczyniemy od sprawdzenia warunku, więc możemy ani razu nie wykonać instrukcji. Pętla while jest postaci while(warunek) instrukcje
Pętla while - potęgi Napiszmy funkcję liczącą kolejną potęgę podanej liczby, dopóki jest ona mniejsza niż 1000. int liczba; cout << "Podaj liczbę \n"; cin >> liczba; int potega=liczba, wykladnik=1; while(potega<1000) { cout << liczba <<"^"<< wykladnik << "=" << potega << endl; potega*=liczba; //lub potega = liczba*potega; wykladnik++; }
do while vs while Zauważmy, że gdybyśmy użyli tutaj pętli do while, to w przypadku, gdyby pierwsza podana liczba była większa od 1000, wypisany zostanie pierwsza potęga tej liczby mimo, iż wypisywane miały być tylko potęgi mniejsze od 1000. Pętle do while, while oraz for można stosować zamiennie (w powyższym programie po dokonaniu pewnych poprawek moglibyśmy użyć pętli do while, a nawet for), jednak czasem użycie jednej jest dużo wygodniejsze niż innych.
Wyszukiwanie elementu w tablicy Aby napisać algorytm sprawdzający, czy dany element znajduje się w danej uporządkowanej tablicy liczb, możemy np. sprawdzić po kolei wszystkie elementy tablicy. Algorytm taki ma złożoność pesymistyczną O(n).
Wyszukiwanie binarne Algorytm wyszukiwania binarnego polega na wybraniu środkowego elementu tablicy i sprawdzeniu czy jest on szukanym elementem, a jeśli nie, to czy jest mniejszy czy większy od elementu szukanego. Jeśli jest mniejszy, powtarzamy procedurę dla lewej połowy tablicy, jeśli większy, dla prawej połowy. Ponieważ w każdym kroku zmniejszamy zakres poszukiwań co najmniej dwukrotnie, więc algorytm ma złożoność O(log n) (log rozumiany jest jako logarytm o podstawie 2). Poniższy program sprawdza, czy we wczytanej wcześniej tablicy T takiej, że T[i]<T[i+1] dla i {1,...,N}, znajduje się liczba S.
Wyszukiwanie binarne int l=1, p=n, odp=0, n; while (l<=p && odp==0) { n=(l+p)/2; if (T[n] < S) l=n+1; else if (T[n] > S) p=n-1; else odp = 1; }
Wyszukiwanie binarne Na koniec wypiszemy wynik if(wynik==1) cout<<"ciąg zawiera liczbę 200 \n"; else cout<<"ciąg nie zawiera liczby 200 \n"; Jeśli na koniec wynik=1, to znaczy, że nasz rosnący ciąg stu liczb naturalnych zawiera liczbę 200.
Potęgowanie binarne Najprostszy algorytm obliczający n m polega na wykonaniu m 1 kolejnych mnożeń przez n. Algorytm potęgowania binarnego polega na zastąpieniu działania n m równoważnym działaniem (n 2 ) m/2, czyli podstawieniem n = n*n; m = m/2; gdy wykładnik jest parzysty, lub działaniem n n m 1, czyli podstawieniem wynik = wynik*n; m--; gdy wykładnik jest nieparzysty.
Potęgowanie binarne Procedurę powtarzamy dopóki wykładnik jest różny od zera. Otrzymamy przykładowo 2 10 = 4 5 = 4 4 4 = 4 16 2 = 4 256 = 1024 256 0.
Algorytn Euklidesa Pętlę while zastosujemy także w algorytmie Euklidesa, który służy do obliczania największego wspólnego dzielnika podanych liczb a i b. Polega on na obliczeniu r - reszty z dzielenia a przez b, zastąpieniu a przez b, zaś b przez r oraz w przypadku b > 0, powtórzeniu całej procedury. W momencie, kiedy b = 0, "obecne" a jest największym wspólnym dzielnikiem.
Algorytm Euklidesa int a,b,r ; cout << "Podaj dwie liczby naturalne.\n"; cin >> a >> b; while(b>0) { r=a%b; a=b; b=r; } cout << "NWD(a,b)=" << a << endl;
Zadania Zadanie Napisz program wymnażający kolejne wczytywane liczby rzeczywiste i wypisujący iloczyn na ekran, do momentu, gdy iloczyn przekroczy 30000. Zadanie Napisz program sumujący wczytane liczby rzeczywiste i wypisujący sumę na ekran, do momentu, aż wpisane zostanie zero. Zadanie Napisz program raz za razem proszący o podanie dwóch liczb naturalnych, i sprawdzający, czy pierwsza jest podzielna przez drugą, aż do momentu, gdy po raz drugi pierwsza liczba będzie podzielna przez drugą.
Zadania Zadanie Napisz program wczytujący losowo rosnący ciąg 1000 liczb naturalnych mniejszych od 10000 i sprawdzający, czy w tablicy znajduje się liczba 1000, a jeśli tak, to na którym miejscu. Zadanie Napisz program wczytujący podstawę i wykładnik i liczący binarnie potęgę. Zadanie Napisz program raz za razem proszący o podanie dwóch liczb naturalnych i liczący ich największy wspólny dzielnik do momentu, gdy wynik wyniesie 2 lub 3.
Zadania Zadanie Napisz program proszący o wpisywanie kolejnych ocen i liczący na bieżąco średnią, aż do momentu wpisania zera. Zadanie Napisz program proszący o podanie kwoty początkowej k, oprocentowania rocznego p (z miesięczną kapitalizacją) oraz kwoty wypłacanej co miesiąc m (zaraz po kapitalizacji odsetek) i sprawdzający kiedy opróżnimy konto (o ile nastąpi to przed upływem 10 lat) lub obliczający stan konta po 10 latach.
Sortowanie bąbelkowe Istnieje wiele sposobów na uporządkowanie danego ciągu liczb w kolejności od najmniejszej do największej. Jednym z prostszych jest sortowanie bąbelkowe. Ma ono złożoność O(n 2 ) (później podany zostanie algorytm sortowania o optymistycznej złożoności O(n log n)). Sortowanie bąbelkowe polega na kolejnym porównywaniu dwóch sąsiadujących elementów ciągu i ewentualnym ich zamienianiu tak, aby większy z nich występował jako późniejszy. Po jednokrotnym przeglądnięciu w ten sposób całej tablicy największy element ląduje na końcu. Po n 1 powtórzeniach tej procedury elementy tablicy będą uporządkowane od najmniejszego do największego. Napiszmy funkcję sortującą.
Sortowanie bąbelkowe for(i = 0; i < n-1; i++) for(j = 0; j < n-i; j++) if(t[j] > T[j+1]) swap(t[j],t[j+i]); //zamieniamy wartości miejscami Zauważmy, że w wewnętrznej pętli bierzemy j do n-i. Elementy tablicy od n-i do n są w tym momencie już posortowane.
Sortowanie bąbelkowe Na koniec wypisujemy posortowany ciąg for(i=0; i<n; i++) cout << T[i] << endl;
Sortowanie przez wybór Bardzo podobny jest algorytm sortowania przez wybór. Polega on na znalezieniu najmniejszego elementu tablicy i zamienieniu go z pierwszym elementem, następnie znalezieniu najmniejszego z pozostałych i zamienieniu go z drugim itd.
Przeliczanie systemów liczbowych Aby napisać program przeliczający systemy, przykładowo z dziesiętnego na szesnastkowy, musimy zdefiniować tablicę szesnastu znaków - każdy przypisany jednej cyfrze zadanej liczby. int ODP[7]; //w tę tablicę wpiszemy odpowiedniki cyfr //rozwinięcia szesnastkowego zadanej liczby int liczba, pozycja=0; char CYFRA[]="0123456789ABCDEF"; //czyli CYFRA[0]=0; //... CYFRA[15]=F; (Podobnie możemy definiować tablice int-ów, np. int T[]={3,5,2,6,1} ustala rozmiar tablicy na 5. )
Przeliczanie systemów liczbowych Obliczając resztę z dzielenia naszej liczby przez 16 dostajemy ostatnią cyfrę rozwinięcia, po podzieleniu całkowitym liczby przez 16 powtarzając procedurę otrzymujemy kolejne cyfry, aż wynik całkowitego dzielenia przez 16 wynosi zero. cin >> liczba; while(liczba!=0) { ODP[pozycja] = liczba%16; liczba = liczba/16; pozycja++; }
Przeliczanie systemów liczbowych Teraz możemy wypisać wynik; cout << "Wynik zapisany cyframi szesnastkowymi: \n"; for(i=pozycja; i>0; i--) cout << CYFRA[ODP[i-1]];
Liczby słownie Za pomocą podobnego algorytmu możemy napisać program wyświetlający podaną liczbę słownie (tzn. wypisujący kolejno cyfry tej liczby. Po zdefiniowaniu nazw cyfr string NAPIS[10]={"zero","jeden","dwa","trzy","cztery", "piec","szesc","siedem","osiem","dziewiec"}; cyfry od ostatniej do pierwszej otrzymamy jako reszty z kolejnych dzieleń zadanej liczby przez 10.
Sito Erastotenesa Eratostenes w trzecim wieku przed naszą erą przedstawił następujący algorytm wyszukiwania liczb pierwszych. Wszystkie wielokrotności liczby 2 większe od niej samej oznaczamy jako liczby złożone. Z liczb większych od 2 wybieramy najmniejszą niezaznaczoną jeszcze liczbę (czyli 3) i wszystkie jej wielokrotności większe od niej samej oznaczamy jako złożone (niektóre liczby będą zaznaczane więcej niż raz). Postępując dalej według tej procedury jako liczby złożone oznaczamy wielokrotności 5, 7, 11 it.d. Dla danej liczby n wszystkie liczby mniejsze od n, które w efekcie powyższej procedury nie zostały oznaczone jako złożone, są liczbami pierwszymi.
Sito Erastotenesa Ustalmy liczbę n i wszystkie liczby mniejsze od niej oznaczamy jedynką. cout << "Ile liczb sprawdzić? \n"; cin >> n; int* T; //deklarujemy tablicę T = new int[n]; //definiujemy rozmiar tablicy for(i = 2; i < n; i++) T[i] =1; //kolejne liczby zaznaczamy //jako potencjalnie pierwsze
Tablica jako wskaźnik do pamięci Deklarując zmienną nadajemy nazwę pewnemu obszarowi pamięci, natomiast nazwa tablicy wskazuje dokładnie na miejsce w pamięci, gdzie ta tablica się zaczyna. Jeśli stosujemy dynamiczną alokację zmiennych (czyli rozmiar tablicy definiujemy na etapie wykonywania programu), musimy (dla niektórych kompilatorów) użyć powyższej składni lub napisać int*t=new int[n]. Operator * oznacza, że deklarujemy wskaźnik do pamięci. Operator new przydziela pamięć.
Sito Erastotenesa Zerem oznaczamy wszystkie liczby mniejsze od n, które są złożone, for (i = 2; i <= n; i++) if (T[i]==1) for (j = i; j*i < n; j++) T[i*j] =0; i wypiszmy liczby pierwsze mniejsze od n (czyli te numery, dla których wartość elenentu tablicy jest równa 1). for (i = 2; i < n; i++) if (T[i]==1) cout << i << ", ";
Schemat Hornera Aby obliczyć wartość wielomianu P n (x) = a n x n + a n 1 x n 1 +...a 1 x + a 0 w danym punkcie x należy wykonać n dodawań, n mnożeń przez współczynnik a i oraz n(n 1)/2 mnożeń potrzebnych do podniesienia x na każdym miejscu do odpowiedniej potęgi. Ilość tych działań można zmniejszyć stosując schemat Hornera.
Schemat Hornera Schemat Hornera polega na rozpisaniu wielomianu P n (x) = a n x n + a n 1 x n 1 +...a 1 x + a 0 = x(a n x n 1 + a n 1 x n 2 +...a 1 ) + a 0 = = x(x(a n x n 2 + a n 1 x n 3 +...a 2 ) + a 1 ) + a 0 = = x(x(...x(x a n + a n 1 ) + a n 2 ) +... + a 1 ) + a 0 i obliczeniu jego wartości. Zauważmy, że wówczas wykonane zostanie jedynie n mnożeń oraz n dodawań, zatem zmniejszymy złożoność z O(n 2 ) do O(n).
Schemat Hornera Mając wczytane współczynniki wielomianu a[i] oraz wartość x użyć można w tym celu pętli; s=a[n]; for (int i=n-1; i>=0; i--) s=s*x+a[i];
Zadania Zadanie Napisz program proszący o podanie 10 liczb, a następnie sortujący (metodą bąbelkową) ten ciąg. Zrób to bez użycia polecenia swap Zadanie Napisz program wczytujący dla podanej liczby n losowy ciąg długości n oraz sortujący (przez wybór) ten ciąg. Omów złożoność programu. Zadanie Napisz program zadający pytanie na jaki system od dwójkowego do szesnastkowego zamienić zadaną liczbę zapisaną w systemie dziesiętnym, a następnie wyświetlający wynik w zadanym systemie.
Zadania Zadanie Napisz program wypisujący zadaną liczbę jako ciąg słów oznaczających jej kolejne cyfry. Zadanie Napisz program znajdujący dla zadanej liczby naturalnej najlepsze naturalne przybliżenie jej pierwiastka.
Zadania Zadanie Napisz program sprawdzający, ile jest liczb pierwszych mniejszych od podanej liczby. Zadanie Napisz program, który dla podanej liczby wspólczynników wczyta je po kolei i korzystając ze schematu Hornera policzy wartość wielomianu w zadanym punkcie. (Napisz program liczący wartość wielomianu bez użycia schematu Hornera. Porównaj złożoność tych dwóch programów.)
Macierz Macierz wyrazić możemy dwuwymiarową tablicą. Aby wypisać wcześniej zdefiniowaną macierz A o m kolumnach i n wierszach napiszemy for(i=0; i<n; i++) { for(j=0; j<m; j++) cout<<a[i][j]<<" "; cout<<endl; }
Macierze Aby zdefiniować macierz B jako transponowaną do A wystarczy podstawić B[j][i]=A[i][j] (jeśli A ma rozmiary n m, to B musi mieć rozmiary m n). Aby dodać dwie macierze A, i B (tych samych rozmiarów) wystarczy dla każdych i,j dodać A[i][j]+B[i][j].
Mnożenie macierzy Zdefiniujmy dwie macierze takie, aby liczba kolumn pierwszej z nich była równa liczbie wierszy drugiej. float A[3][4] = {{1,2,0,0}, {1,3,2,0}, {3,0,1,1}}; float B[4][2] = {{1, 2}, {2, 0.6}, {0, 1}, {3, 5}};
Mnożenie macierzy Aby pomnożyć macierze A, i B musimy policzyć odpowiednie sumy iloczynów; float AB[3][2]={{0}}; //deklarujemy macierz AB //na początku na każdym miejscu tablicy są zera for(int i=0; i<3; i++) for(int j=0; j<2; j++) for(int k=0; k<4; k++) AB[i][j] = AB[i][j]+A[i][k]*B[k][j];
Mnożenie macierzy Na koniec wypiszemy wynik: for(int i=0; i<3; j++) { for(int j=0; i<j; i++) cout << AB[i][j] << " "; cout << endl; }
Metoda Gaussa - Jordana Metoda Gaussa - Jordana rozwiązywania układów równań (mających tyle samo zmiennych, co równań) polega na wpisaniu wspólczynników układu w macierz uzupełnioną, a następnie wykonywaniu operacji na wierszach macierzy.
Metoda Gaussa - Jordana Napiszmy program rozwiązujący układ trzech równań z trzema niewiadomymi x + 2y = 3 3x + 6y + z = 5 4x + 2z = 8 Najpierw musimy wpisać w tablicę macierz współczynników, float A[3][4]={{1, 2, 0, 3},{3, 6, 1, 5},{4, 0, 2, 8}};
Metoda Gaussa - Jordana Pierwszy wiersz mnożymy przez stałą, aby na pierwszym miejscu uzyskać jedynkę, nastepnie odejmujemy ten wiersz pomnożony przez odpowiednie stałe od pozostałych wierszy, aby na tym miejscu dostać zera. W następnym kroku drugi wiersz mnożymy przez stałą, aby na drugim miejscu uzyskać jedynkę, nastepnie odejmujemy ten wiersz pomnożony przez odpowiednie stałe od pozostałych wierszy, aby na tym miejscu dostać zera. Podobnie postępujemy dla każdego następnego wiersza.
Metoda Gaussa - Jordana for(k=0; k<3; k++) { for(j=k; j<4; j++) A[k][j] = A[k][j]/A[k][k]; for(i=0; i<3; i++) if(i!= k) for(j=k; j<4; j++) A[i][j] = A[i][j]-A[i][k]*A[k][j]; }
Metoda Gaussa - Jordana Jeśli na miejscu, na którym powinniśmy uzyskać jedynkę znajduje się zero musimy zamienić dany wiersz z którymś z kolejnych, który w tym miejscu nie ma zera - jeśli każdy z nich ma na tym miejscu zero, to układ nie ma jednoznacznego rozwiązania. Jeśli układ ma jednoznaczne rozwiązanie, to po zastosowaniu powyższych przekształceń do wszystkich wierszy otrzymujemy macierz diagonalną jednostkową (+ kolumna rozwiązań).
Metoda Gaussa - Jordana if(a[k][k]==0) { brakrozw=1; for(i=k+1; i<3; i++) if(a[i][k]!=0) { for( j=0; j<4; j++) swap(a[i][j],a[k][j]); brakrozw=0; } }
Metoda Gaussa - Jordana Na koniec możemy wypisać rozwiązanie for(int i=0; i<3; i++) cout << "x(" << i << ")=" << A[i][3] << endl;
Zadania Zadanie Napisz program proszący o podanie rozmiarów macierzy (nie większych niż 5 5), a następnie wypisujący podaną macierz oraz macierz transponowaną. (Aby utworzyć tablicę dwuwymiarową dowolnego zadanego za pomocą zmiennych rozmiaru musielibyśmy napisać cin>>n>>m; float**a=new float*[n]; for( i=0; i<n; ++i) A[i]=new float[m]; Ponieważ w zadaniu rozmiar tablicy jest ograniczony od góry, więc nie musimy stosować tej procedury.)
Zadania Zadanie Napisz program sumujący dowolne dwie macierze o zadanym rozmiarze mniejszym od 5 5. Zadanie Napisz program mnożący dowolne dwie macierze o zadanych rozmiarach mniejszych niż 6 6 lub stwierdzający, że jest to niemożliwe. Zadanie Napisz program, który po wczytaniu współczynników rozwiązuje układ czterech równań liniowych lub stwierdza, że rozwiązanie jest niemożliwe.
Funkcje Funkcja główna nie musi być jedyną funkcją w programie. Często wygodnie jest zdefiniować pewną procedurę, a następnie wywoływać w programie jej wykonanie. Przy pisaniu dłuższych programów może to znacznie ułatwić pracę - program staje się czytelniejszy i każdą funkcję możemy wywoływać w programie dowolną ilość razy z różnymi argumentami. Funkcje umieszczamy w nagłówku - muszą one znajdować się poza funkcją główną programu.
Funkcja void Funkcja może nie zwracać żadnej wartości (funkcja typu void), tylko wykonywać jakąś czynność, np. wypisywanie na ekran. void pisz(int liczba) /*argumentem tej funkcji jest zmienna liczba typu int (funkcja może mieć jeden lub więcej argumentów)*/ { cout << "Podana liczba to " << liczba << endl; } Po zadeklarowaniu zmiennej (np. int a=5;) naszą funkcję wywołamy poleceniem pisz(a);
Funkcja typu int Częto używamy funkcji, które zwracają jakąś wartość. Przykładowo funkcja int wyznacznik(int x, int y, int z, int v) { return(x*v-y*z); } zwraca wyznacznik macierzy 2 2.
Funkcja typu int Teraz możemy w funkcji głównej wczytać dane i wypisać wyznacznik macierzy; int a11,a12,a21,a22; cout << "Podaj dane \n"; cin >> a11 >> a12 >> a21 >> a22; cout << wyznacznik(a11,a12,a21,a22);
Funkcja - algorytm Euklidesa Napiszmy funkcję liczącą największy wspólny dzielnik dwóch liczb. int NWD(int x, int y){ int r; while(y!=0){ r=x%y; x=y; y=r; } return(x); }
Funkcja - algorytm Euklidesa Możemy użyć jej na przykład do skracania ułamków; int d,a,b; do{ cout << "Podaj licznik i mianownik.\n"; cin << a << b; if(b!=0){ d = NWD(a,b); cout << "Wynik=" << a/d << "/" << b/d << endl; } else cout << "Błędne dane.\n"; } while(b!=0)
Zmienne lokalne Zauważmy, że zmienną r użytą w funkcji NWD zadeklarowaliśmy wewnątrz funkcji. Jest to zmienna lokalna, tzn. jest widoczna tylko wewnątrz funkcji. Zmienne deklarowane w funkcji głównej programu także nazywamy zmiennymi lokalnymi. Deklarowanie zmiennych poza jakąkolwiek funkcją (zmienne globalne) jest stosowane, gdy chcemy korzystać z tej zmiennej w różnych funkcjach programu (w innym wypadku wygodniej jest używać zmiennych lokalnych). (Pamięć zarezerwowana dla zmiennej globalnej jest czyszczona przed uruchomieniem programu, zatem zmienna globalna ma po zadeklarowaniu wartość zero, podczas, gdy zmienna lokalna r przed przypisaniem jej wartości przyjmuje pewną losową wartość.)
Metoda połowienia przedziału Metoda połowienia przedziału służy do znajdowania dla danej funkcji ciągłej miejsca zerowego w zadanym przedziale, o ile na jego brzegach funkcja przyjmuje przeciwne wartości. Miejsce zerowe wyznaczane jest z określoną dokładnością.
Metoda połowienia przedziału W nagłówku zdefiniujmy stałą wyznaczającą dokładność rozwiązania oraz funkcję f określającą naszą funkcję matematyczną. const double dokl = 0.001; double f(double x) { return tan(x/100); }
Metoda połowienia przedziału W funkcji głównej musimy wczytać wartości a oraz b i jeśli f(a) i f(b) są przeciwnych znaków, to wybieramy tę połówkę przedziału, na brzegach której wartości są przeciwne i powtarzamy tę procedurę, aż do zbliżenia się wartości funkcji do zera z zadaną dokładnością. do{ s = (a+b)/2; if(f(a)*f(s) < 0) b = s; else a = s; } while(fabs(f(s)) > dokl);//fabs oznacza wartość bezwzględną
Metoda połowienia przedziału Na koniec możemy wypisać wynik cout << "Miejsce zerowe funkcji f to " << s << endl; //f(s)=0
Metoda siecznych Podobny efekt możemy otrzymać stosując metodę siecznych. Możemy ją zastosować jeśli f jest funkcją ciągłą przyjmującą na brzegach przedziału (a,b) przeciwne wartości oraz taką, że f (x) 0.
Metoda siecznych Dla danych a,b punkt c definiujemy punkt przecięcia osi OX z sieczną łączącą (a,f (a)) oraz (b,f (b)), czyli b a c = b f (b) f (b) f (a). Za a podstawiamy wartość b, a za b wartość c i powtarzamy procedurę. Postępujemy tak dopóki f (c) jest większe od ustalonej stałej wyznaczającej dokładność.
Metoda siecznych Przedstawiony powyżej algorytm zapisany w języku C wyglądać będzie następująco do { c = b-f(b)*(b-a)/(f(b)-f(a)); a = b; b = c; } while(fabs(f(c)) > dokl);
Zadania Zadanie Napisz program wczytujący licznik i mianownik i następnie skracający zadany w ten sposób ułamek do momentu, gdy wynik wyniesie 1 2. Zadanie Napisz program znajdujący metodą połowienia przedziału miejsce zerowe dla funkcji f (x) = tg( x 100 ) pomiędzy dowolnymi punktami z dokładnością 0.0001 i wypisujący przybliżone miejsce zerowe oraz wartość funkcji w tym punkcie.
Zadania Zadanie Napisz program znajdujący metodą siecznych miejsce zerowe dla funkcji f (x) = tg( x 100 ) pomiędzy dowolnymi punktami z dokładnością 0.000000001 i wypisujący przybliżone miejsce zerowe. Porównaj ilość kroków, w jakiej ten i poprzedni program znajdować będą miejsce zerowe.
Rekurencja - ciąg Fibinacciego Ważnym aspektem stosowania funkcji jest fakt, iż umożliwiają one rekurencje - tzn. wywołanie samej siebie. Funkcja zwracająca dany wyraz ciągu Fibonacciego wyglądać będzie tak int fib(int n) { if(n<=2) return(1); else return(fib(n-2)+fib(n-1)); }
Ciąg Fibinacciego Po zadeklarowaniu w programie zmiennej k i przypisaniu jej wartości k-ty wyraz ciągu Fibonacciego wypiszemy na ekran poleceniem cout << fib(k). Do zdefiniowania funkcji i jej wywołania możemy użyć tych samych nazw zmiennych lub różnych - w każdym przypadku zmienne te musimy zadeklarować osobno wewnątrz funkcji i osobno w funkcji głównej programu.
Rekurencja - symbol Newtona Wartość symbolu Newtona można policzyć ze wzoru ( ) n k = n! (n k)!k!. Można też skorzystać z własności ( ) ( n k = n 1 ) ( k 1 + n 1 ) k i napisać funkcję rekurencyjną, int newton(int n, int k) { } if(n==k k==0) else return 1; return newton(n-1,k-1)+newton(n-1,k);
Symbol Newtona Po podaniu n oraz k wypiszemy wynik. cout << "(n po k)= " << newton(n,k) << endl;
Funkcja Ackermana Przykładem funkcji rekurencyjnej jest funkcja Ackermana. Można zapisać ją wzorem n + 1 m = 0 F (m,n) = F (m 1,1) m > 0,n = 0. F (m 1,F (m,n 1)) m,n > 0
Funkcja Ackermana Funkcję Ackermana zapiszemy wzorem rekurencyjnym int acker(int m, int n) { if(m==0) return(n+1); else if(n==0) return(acker(m-1,1)); else return(acker(m-1,acker(m,n-1))); } a wywołamy cout << "acker("<<m<<","<<n<<")=" << acker(m,n) << endl;
Rekurencja - algorytm Euklisesa Pseudokod procedury rekurencyjnej obliczającej największy wspólny dzielnik dwóch liczb wygląda następująco euklid(a,b) 1 if b=0 2 then return a 3 else return eukl(b, a mod b)
Rekurencja - algorytm Euklisesa W języku C++ procedura rekurencyjna obliczająca największy wspólny dzielnik przyjmie postać: int euklid(int a, int b) { if(b==0) return a; else return euklid(b,a%b); }
Poprawność algorytmu Euklidesa Zauważmy, że jest to wersja rekurencyjna algorytmu euklidesa (Na każdym kroku za a podstawiane jest b, a za b podstawiane jest a (mod b) dopóki b 0). Ponadto zauważmy, że NWD(a, b) dzieli NWD(b, a(mod)b) oraz NWD(b, a(mod)b) dzieli NWD(a, b), co dowodzi, iż powyższa funkcja rzeczywiście wyznacza największy wspólny dzielnik dwóch liczb.
Sortowanie szybkie Sortowanie szybkie polega na przeniesieniu na początek tablicy elementów mniejszych od pierwszego elementu, a na koniec większych, a następnie powtarzaniu tej czynności dla początkowej i końcowej części tablicy. Rekursja kończy się, gdy kolejny fragment uzyskany z podziału zawiera tylko jeden element.
Sortowanie szybkie void sortuj(int T[],int L,int P){ //T[] <-> *T int i=l, j=p, v=t[l]; do{ while (T[i]<v) i++; while (v<t[j]) j--; if (i<=j){ swap(t[i], T[j]); i++; j--; } } while (i<=j); if (L<j) sortuj(t,l,j); if (i<p) sortuj(t,i,p); }
Zadania Zadanie Napisz program, który dla podanej liczby n wypisuje n pierwszych wyrazów ciągu Fibonacciego. Zadanie Napisz program liczący silnię za pomocą funkcji rekurencyjnej.
Zadania Zadanie Napisz programy liczące dla zadanych n oraz k wartość ( n k) wprost ze wzoru oraz rekurencyjnie. Porównaj szybkość i zakres poprawnego działania obu programów. Zadanie Napisz program wczytujący długość ciągu oraz kolejne elementy ciągu, a następnie porządkujący je zgodnie z algorytmem szybkiego sortowania. Omów pesymistyczną i optymistyczną złożoność tego algorytmu.
Wczytywanie po znaku, ASCII Pisząc j=getc(stdin); przypisujemy zmiennej j wczytany z klawiatury znak (o ile j jest zadeklarowane jako char) lub wartość w kodzie ASCII tego znaku (o ile j jest zadeklarowane jako int). Pamiętamy, że ASCII (American Standard Code for Information Interchange) jest to kod przyporządkowujący liczby z zakresu 0-127 literom, cyfrom i innym znakom.
ASCII, wczytywanie po znaku Poniższy program pozwala na wczytanie ciągu znaków dowolnej długości, a po naciśnięciu klawisza enter (wartość 10 w kodzie ASCII) wypisuje ich wartości w kodzie ASCII. int j; do { j=getc(stdin); cout<<j<<", "; } while(j!=10); //wczytywane są kolejne znaki //i wypisywana jest ich wartość //aż do naciśnięcia ENTER
ASCII - reprezentacja liczb Po wykonaniu powyższego programu możemy zauważyć, że chcąc poprawnie zinterpretować znaki reprezentujące cyfry musimy od reprezantacji znaku odjąć 48.
ASCII Napiszmy program, który wczytuje ciąg znaków, a następnie po kolei wypisuje, czy dany znak jest liczbą czy literą. int j; do { j=getc(stdin); if(j>47&&j<58) cout<<"cyfra \n"; else if((j>64&&j<91) (j>96&&j<123)) cout<<"litera\n"; else cout<<"ani cyfra, ani litera\n"; } while(j!=10);
ASCII - tablica znaków Aby wczytać ciąg znaków i zapamiętać go w tablicy najpierw zadeklarujemy tablicę i pomocnicze zmienne char A[100]; int i=0; Następnie wczytamy tablicę znaków do { i++; A[i]=getc(stdin); } while(a[i]!=10);
ASCII - tablica znaków Aby wypisać wczytany ciąg znaków w odwrotnej kolejności napiszemy for(j=i-1; j>0; j--) cout<<a[j]; cout<<endl;
System dwójkowy Napiszmy program przeliczający podaną liczbę z systemu dwójkowego do dziesiętnego. Najpierw deklarujemy zmienne i wczytujemy ciąg znaków (złożony z zer i jedynek). int A[31]; int i=0, r=0, k; cout<<"podaj liczbę w systemie dwójkowym.\n"; do { i++; A[i]=getc(stdin)-48; } while(a[i]!=-38);
System dwójkowy Następnie obliczamy sumę odpowiednich potęg dwójki pomnożonych przez odpowiednie współczynniki. i--; for(k=i; k>0 ; k--) r+=(a[k]*pow(2,i-k)); cout<<r<<endl;
Duże liczby Napiszmy teraz program sumujący dwie dowolnie duże liczby (wychodzące poza zakres int-a). Na początek wczytamy dwie liczby jako tablice cyfr. int A[100], B[100], i=0, j=0, r=0, k; do { i++; A[i]=getc(stdin)-48; } while(a[i]!=-38);
Duże liczby i podobnie do { j++; B[j]=getc(stdin)-48; } while(b[j]!=-38); Załóżmy najpierw, że obie wczytane liczby są tej samej długości.
Dodawanie pisemne Ich sumę policzymy stosując schemat dodawania pisemnego. A[0]=0; B[0]=0; j--; for(k=j; k>=0 ; k--) { B[k]=B[k]+A[k]+r; if(b[k]>9) { r=1; B[k]=B[k]-10; } else r=0; }
Dodawanie pisemne Na koniec możemy wypisać wynik. if(b[0]!=0) cout<<b[0]; for(k=1; k<=j; k++) cout<<b[k];
Dodawanie pisemne W przypadku, gdy wczytane liczby nie będą tej samej długości możemy przesunąć mniejszą liczbę (tak aby była tej samej długości, co większa) a na początkowych miejscach umieścić zera. Powiedzmy, że pierwsza liczba jest mniejsza. for(k=j-1; k>j-i; k--) A[k]=A[k-j+i]; for(k=j-i; k>0; k--) A[k]=0;
Podzielność Mając wczytaną liczbę w tablicy a[i] możemy sprawdzić jej podzielność przez 3: s=0; for(k=0; k<i ; k++) s+=a[k] if(s%3==0) cout<<"wczytana liczba jest podzielna przez 3.\n"; else cout<<"wczytana liczba nie jest podzielna przez 3.\n";
Zadania Zadanie Napisz program wczytujący ciąg znaków oraz sprawdzający, czy najwięcej wczytano dużych liter, małych liter, czy innych znaków. Zadanie Napisz program wczytujący ciąg znaków oraz liczący sumę cyfr, które się wśród tych znaków znalazły. Zadanie Napisz program wczytujący dwie liczby jako ciągi znaków, a następnie sprawdzający która z nich jest większa.
Zadania Zadanie Napisz program wczytujący trzy liczby jako ciągi znaków, a następnie dodający je pisemnie. Zadanie Napisz program wczytujący dwie liczby - jedną jako ciąg znaków, drugą typu int i mnożący je. Zadanie Napisz program wczytujący liczbę jako ciąg znaków, a następnie sprawdzający, czy jest ona podzielna przez 11.
Wskaźniki Wskaźnik nie przechowuje wartości zmiennej ale, podobnie jak tablica, wskazuje miejsce w pamięci, w którym znajduje się zmienna danego typu. W poniższym przykładzie symbol * pomiędzy int, a wskaz oznacza, że wskaz jest wskaźnikiem do zmiennej typu int (a nie zmienną typu int). int *wsk; //deklaracja wskaźnika wsk=new int; //przydzielenie zmiennej miejsce w pamięci *wsk=5; //zapisanie liczby 5 w tym miejscu pamięci cout << *wsk << endl; delete wsk; //zwolnienie pamięci wskazywanej przez wskaźnik
Wskaźniki Polecenie delete zwalnia miejsce w pamięci, w którym przechowywana jest nasza zmienna - pamięć nie będzie zatem zajmowana przez cały czas wykonywania programu, co ma miejsce przy normalnym deklarowaniu zmiennych.
Wskaźniki int* T; int n, i; cout << "Podaj rozmiar tablicy. \n"; cin >> n; T = new int[n]; cout << "Podaj elementy tablicy \n"; for (i=0; i<n; i++) cin>>t[i]; cout << "Zawartosc tablicy: \n"; for (i=0; i<n; i++) cout << T[i] << ", "; delete [] T;
Aliasy Operator & adresu (referencji) występuje przy deklaracji zmiennej zwanej referencją. W poniższym przykładzie symbol & pomiędzy int, a alias oznacza, że zmienna alias jest typu referencyjnego i podszywa się pod zmienną x typu int. int x = 15; int & alias = x; cout << alias;
Wskaźniki Zamiast operatora * wyłuskania (dereferencji) w poniższym przypadku int x=35; int * wsk=&x; *wsk = 45; cout << x; wygodniej użyć operatora referencji int x=35; int& alias=x; alias = 45; cout << x;
Wskaźniki Funkcja skracająca ułamek postaci int skrac0(int a, int b) { int d=nwd(a,b); a=a/d; b=b/d; } nie będzie działać, gdyż wartości wyliczone wewnątrz funkcji nie są widoczne na zewnątrz.
Wskaźniki Nie możemy też użyć polecenia return w funkcji, gdyż funkcja ma powinna zwracać licznik i mianownik skróconego ułamka. Ponieważ polecenie return może zwracać tylko jedną wartość (pozostałe wartości wyliczone wewnątrz funkcji nie są widoczne na zewnątrz), więc nie osiągniemy tego standardową procedurą. Możemy jednak napisać funkcję używając wskaźników, która zmienia miejsce w pamięci.
Wskaźniki Funkcję skracającą ułamek możemy napisać używając wskaźników. void skrac1(int *a, int *b) { int d=nwd(*a,*b); *a=*a/d; *b=*b/d; } Funkcję taką wywołamy poleceniem skrac1(&licz, &mian). Wynik wypiszemy poleceniem cout << licz << "/" << mian << endl;
Tablice a wskaźniki Zauważmy, że funkcja sortowania szybkiego nie zawiera polecenia zwrócenia wartości return, a jednak zmienia wartości w tablicy. Wynika to stąd, że nazwa tablicy jest wskaźnikiem stałym, a nie nazwą zmiennej.
Referencje Do skracania ułamków możemy użyć także referencji void skrac2(int &a, int &b) { int d=nwd(a,b); a=a/d; b=b/d; } W tym przypadku funkcję wywołujemy poleceniem skrac2(licz, mian).
Struktury Poleceniem struct możemy stworzyć strukturę złożoną ze zmiennych. Stwórzmy strukturę o nazwie autor. struct autor{ string imie, nazwisko, tytul; int rok; }; //deklarujemy zmienne //czyli pola struktury
Struktury Możemy teraz zadeklarować zmienne typu autor oraz nadać im cechy. autor os1, os2; os1.imie = "Thomas"; os1.nazwisko = "Cormen"; os1.tytul = "Wprowadzenie do algorytmow"; os1.rok = 1997; cin >> os2.imie >> os2.nazwisko >> os2.tytul >> os2.rok;