WYKŁAD 3 (13 MARZEC 2014) LICZBY CAŁKOWITE I RZECZYWISTE Bartosz Łakomy i Dariusz Dobiesz SPIS TREŚCI: Liczby parzyste i nieparzyste Liczby podzielne lub niepodzielne przez zadane podzielniki NWD – algorytm Euklidesa Liczby względnie pierwsze Najmniejsza wspólna wielokrotność Liczby pierwsze – generacja przez sprawdzanie podzielności Liczby pierwsze – generacja sitem Eratostenesa LICZBY PARZYSTE I NIEPARZYSTE Problem: W przedziale całkowitym <a,b> wyszukaj wszystkie liczby parzyste. Liczby parzyste: W wielu algorytmach musimy wygenerować liczby parzyste z zadanego przedziału <a,b> liczb całkowitych. Aby rozwiązać to zadanie wyznaczymy pierwszą liczbę w przedziale <a,b>, która spełnia kryterium. Następnie w pętli sprawdzamy, czy wyznaczona liczba mieści się w przedziale <a,b>. Pętlę kontynuujemy, aż wygenerowana liczba wyjdzie poza przedział <a,b>. Ponieważ granice przedziału a i b mogą być dowolnymi liczbami całkowitymi, musimy najpierw znaleźć najmniejszą liczbę parzystą z przedziału <a,b>. 1. Jeśli a jest parzyste, to najmniejszą liczbą parzystą w tym przedziale będzie właśnie a. 2. Jeśli a nie jest parzyste, to najmniejszą liczbą parzystą będzie a + 1. Parzystość a sprawdzimy badając resztę z dzielenia a przez 2. Jeśli reszta jest zerowa, to a jest liczbą parzystą. Jeśli a nie jest liczbą parzystą, to: 1. Reszta wynosi 1 dla a > 0 2. Reszta wynosi -1 dla a < 0 Z powyższego wnioskujemy, iż pierwszą liczbę parzystą w przedziale całkowitym <a,b> otrzymamy następująco: i=a Jeśli reszta z dzielenia a przez 2 jest różna od 0, to zwiększ i o 1. Następna liczba parzysta jest zawsze o 2 większa. Podsumowując otrzymujemy algorytm generacji liczb parzystych w przedziale całkowitym <a,b>: ALGORYTM GENERACJI LICZB PARZYSTYCH -Wejście a – początek przedziału, a ∈ Z b – koniec przedziału, b ∈ Z, a < b -Wyjście: Kolejne liczby parzyste zawarte w przedziale <a,b> -Zmienna pomocnicza i – przebiega przez kolejne liczby parzyste w przedziale <a,b>, i ∈ Z -Lista kroków K01: i←a ;obliczamy pierwszą liczbę parzystą K02: Jeśli a mod 2 ≠ 0, to i ← i + 1 K03: Dopóki i ≤ b, wykonuj kroki K03...K04 ;generujemy liczby w przedziale <a,b> ;wyprowadzamy liczbę parzystą K04: Pisz i K05: i ← i + 2 ;następna liczba parzysta K06: Zakończ parzyste PROGRAM #include <iostream> using namespace std; int main() { int a,b,i; cin >> a >> b; i = a; if(a % 2) i++; while(i <= b) { cout << i << " "; i += 2; } cout << endl; return 0; } Program spodziewa się w pierwszym wierszu liczb a i b. W kolejnych wierszach wyświetla liczby parzyste zawarte w przedziale <a,b>. Liczby nieparzyste: Liczby nieparzyste generujemy w identyczny sposób: wyznaczamy pierwszą liczbę nieparzystą w przedziale <a,b>, a kolejne są o 2 większe. Jeśli a jest nieparzyste, to pierwsza liczba nieparzysta jest równa a, w przeciwnym razie jest o 1 większa. Poniższy program czyta krańce przedziału a, b i wyświetla wszystkie kolejne liczby nieparzyste zawarte w tym przedziale. ZADANIE Napisz program który spodziewa się w pierwszym wierszu liczb a i b. W kolejnych wierszach wyświetla liczby nieparzyste zawarte w przedziale <a,b>. Analogicznie do poprzedniego zadania. PROGRAM #include <iostream> using namespace std; int main() { int a,b,i; cin >> a >> b; i = a; if(a % 2 == 0) i++; while(i <= b) { cout << i << " "; i += 2; } cout << endl; return 0; } LICZBY PODZIELNE LUB NIEPODZIELNE PRZEZ ZADANE PODZIELNIKI Problem nr 1: W przedziale <a,b> liczb całkowitych wyszukać wszystkie liczby podzielne przez liczby z zadanego zbioru P. Generujemy wszystkie kolejne liczby z przedziału <a,b> i sprawdzamy, czy dzielą się bez reszty przez liczby z zadanego zbioru. Jeśli tak, wyprowadzamy je na wyjście. ALGORYTM WYZNACZANIA LICZB PODZIELNYCH PRZEZ ZADANE CZYNNIKI Wejście a – początek przedziału, a ∈ Z b – koniec przedziału, b ∈ Z, a < b n – liczba podzielników, n ∈ N P – tablica, której kolejne elementy są podzielnikami. Elementy ∈ Z. Numeracja elementów od zera. Wyjście: Kolejne liczby z przedziału <a,b> podzielne przez podzielniki w P Zmienne pomocnicze: i – przebiega przez kolejne liczby w przedziale <a,b>, i ∈ Z j – przebiega przez numery kolejnych podzielników w P, j ∈ N Lista kroków: K01: Dla i = a,a+1,...,b wykonuj K02...K03 ;przechodzimy przez kolejne liczby z przedziału <a,b> K02: Dla j = 0,1,...,n-1 wykonuj K03 ;sprawdzamy, czy liczba i dzieli się przez podzielniki. K03: Jeśli i mod P[j] = 0, to: pisz i następny obieg pętli K01 ;z tablicy P[]. Jeśli tak, wyprowadzamy i, przerywamy pętlę K02 K04: Zakończ PROGRAM #include <iostream> using namespace std; const int MAXP = 1000; int main() { int a,b,i,j,n,P[MAXP]; cin >> a >> b >> n; for(i = 0; i < n; i++) cin >> P[i]; for(i = a; i <= b; i++) for(j = 0; j < n; j++) if(i % P[j] == 0) { cout << i << " "; break; } cout << endl; return 0; } Program spodziewa się w pierwszym wierszu liczb a i b. W drugim wierszu należy podać n = 1...1000, a następnie w n wierszach kolejne podzielniki. Przykładowe dane: -100 100 3 5 12 17 LICZBY PODZIELNE LUB NIEPODZIELNE PRZEZ ZADANE PODZIELNIKI Problem nr 2: W przedziale <a,b> liczb całkowitych wyszukać wszystkie liczby niepodzielne przez żadną z liczb z zadanego zbioru P. Każdą liczbę sprawdzamy na podzielność przez podzielniki z P. Jeśli któryś z nich dzieli liczbę, to przechodzimy do następnej liczby w <a,b>. Jeśli żaden nie dzieli liczby, liczbę wyprowadzamy na wyjście. ALGORYTM WYZNACZANIA LICZB NIEPODZIELNYCH PRZEZ ZADANE LICZBY Wejście a – początek przedziału, a ∈ Z b –koniec przedziału, b ∈ Z, a < b n –liczba podzielników, n ∈ N P –tablica, której kolejne elementy są podzielnikami. Elementy ∈ Z. Numeracja elementów od zera. Wyjście: Kolejne liczby z przedziału <a,b> niepodzielne przez podzielniki w P. Zmienne pomocnicze: i – przebiega przez kolejne liczby w przedziale <a,b>, i ∈ Z j – przebiega przez indeksy podzielników w P, j ∈ N Lista kroków: K01: Dla i = a,a+1,...,b wykonuj kroki K02...K04 ;pętla przebiegająca przez kolejne liczby z <a,b> K02: Dla j = 0,1,...,n-1 wykonuj krok K03 ;pętla sprawdzająca podzielność przez dzielniki z P K03: Jeśli i mod P[j] = 0, to następny obieg pętli K01 ;jeśli jakiś dzielnik dzieli i, przechodzimy do następnej liczby K04: Pisz i ;jeśli żaden dzielnik nie dzieli i, wyprowadzamy je K05: Zakończ PROGRAM #include <iostream> using namespace std; const int MAXP = 1000; int main() { int a,b,i,j,n,P[MAXP]; bool t; cin >> a >> b >> n; for(i = 0; i < n; i++) cin >> P[i]; for(i = a; i <= b; i++) { t = true; for(j = 0; j < n; j++) if(i % P[j] == 0) { t = false; break; } if(t) cout << i << " "; } cout << endl; return 0; } Program spodziewa się w pierwszym wierszu liczb a i b. W drugim wierszu należy podać n = 1...1000, a następnie w n wierszach kolejne podzielniki. Przykładowe dane: -100 100 4 2 3 5 7 NWD – ALGORYTM EUKLIDESA Problem: Dla danych dwóch liczb naturalnych a i b znaleźć największą liczbę naturalną c, która dzieli bez reszty liczbę a i dzieli bez reszty liczbę b. Liczba c o powyższej własności nosi nazwę NWD – największego wspólnego dzielnika a i b (ang. GCD – greatest common divisor). NWD znajdujemy za pomocą znanego algorytmu Euklidesa, będącego jednym z najstarszych algorytmów, ponieważ pojawił się on w dziele Elementy napisanym przez Euklidesa około 300 p.n.e. Właściwie Euklides nie podał algorytmu dla liczb, lecz dla dwóch odcinków. Chodziło w nim o znalezienie wspólnej miary (czyli odcinka jednostkowego), która mogłaby posłużyć do zmierzenia obu danych odcinków – wspólna miara odkłada się w każdym z odcinków całkowitą liczbę razy. Rozwiązanie 1: Euklides wykorzystał prosty fakt, iż NWD liczb a i b dzieli również ich różnicę. Zatem od większej liczby odejmujemy w pętli mniejszą dotąd, aż obie liczby się zrównają. Wynik to NWD dwóch wyjściowych liczb. Algorytm Euklidesa Wejście: a, b – liczby naturalne, których NWD poszukujemy, a, b ∈ N Wyjście: NWD liczb a i b Lista kroków: K01: Dopóki a ≠ b wykonuj krok K02 Jeśli a < b, to b ← b - a inaczej a←a-b zrównają K02: K03: Pisz a K04: Zakończ ;od większej liczby odejmujemy mniejszą aż się ;wtedy dowolna z nich jest NWD Start Wczytaj a,b nie a<b a -= b tak b -= a Wypisz wynik koniec PROGRAM #include <iostream> using namespace std; int main() { unsigned int a,b; cin >> a >> b; while(a != b) if(a < b) b -= a; else a -= b; cout << a << endl; return 0; } Program odczytuje z pierwszego wiersza dwie liczby a i b, a następnie wypisuje w następnym wierszu ich NWD. Żadna z liczb a i b nie może wynosić 0 – wtedy różnica nie zmienia większej z nich i program działa w nieskończoność. Zadanie: Popraw kod z lewej strony tak żeby 0 nie stanowiło problemu #include <iostream> using namespace std; int main() { unsigned int a,b; cin >> a >> b; if ((a==0)||(b==0)) { cout << "NWD = 0" << endl; } while(a != b) if(a < b) b -= a; else a -= b; cout << a << endl; return 0; } Rozwiązanie 2: Pierwsze rozwiązanie problemu znajdowania NWD jest złe z punktu widzenia efektywności. Wyobraźmy sobie, iż a jest równe 4 miliardy, a b jest równe 2. Pętla odejmująca będzie wykonywana dotąd, aż zmienna a zrówna się ze zmienną b, czyli w tym przypadku 2 miliardy razy – trochę dużo. Tymczasem można wykorzystać operację reszty z dzielenia. Mniejszą liczbę można odjąć od większej liczby tyle razy, ile wynosi iloraz całkowity tych liczb. Po odejmowaniu pozostaje reszta z dzielenia – a Euklides właśnie zauważył, iż NWD dzieli również różnicę danych liczb, czyli: NWD(a,b) = NWD(a mod b,b) Ponieważ reszta zawsze jest mniejsza od dzielnika, wymieniamy a z b, a b z a mod b. Jeśli otrzymamy wynik b = 0, to w a jest ostatni dzielnik dzielący bez reszty różnicę. Algorytm Euklidesa Wejście a, b – liczby naturalne, których NWD poszukujemy, a, b ∈ N Wyjście: NWD liczb a i b Zmienne pomocnicze t – tymczasowo przechowuje dzielnik, t ∈ N Lista kroków: K01: Dopóki b ≠ 0 wykonuj kroki K02...K04 K02: t←b ;zapamiętujemy dzielnik K03: b ← a mod b dzielnikiem ;wyznaczamy resztę z dzielenia, która staje się K04: a←t ;poprzedni dzielnik staje teraz się dzielną K05: Pisz a ;NWD jest ostatnią dzielną K06: Zakończ PROGRAM #include <iostream> using namespace std; int main() { unsigned int a,b,t; cin >> a >> b; while(b) { t = b; b = a % b; a = t; } cout << a << endl; return 0; } Program odczytuje z pierwszego wiersza dwie liczby a i b, a następnie wypisuje w następnym wierszu ich NWD. LICZBY WZGLĘDNIE PIERWSZE Liczba naturalna m jest względnie pierwsza (ang. coprime) z liczbą naturalną n wtedy i tylko wtedy, gdy NWD(m,n) = 1. Definicja ta oznacza, iż liczby n i m nie posiadają wspólnych podzielników za wyjątkiem 1. Problem: W przedziale <a,b> liczb naturalnych wyszukać wszystkie liczby względnie pierwsze (ang. relatively prime integers) z zadaną liczbą p. Rozwiązanie: Przechodzimy przez kolejne liczby w przedziale <a,b>. Dla każdej liczby obliczamy algorytmem Euklidesa jej NWD z liczbą p. Jeśli wynikiem będzie 1, to obie liczby są względnie pierwsze, zatem wyprowadzamy liczbę z przedziału na wyjście. Algorytm wyznaczania liczb względnie pierwszych Wejście a – początek przedziału, a ∈ N b – koniec przedziału, b ∈ N p – liczba służąca do wyznaczenia w przedziale <a,b> liczb z nią względnie pierwszych, p ∈ N Wyjście: liczby z przedziału <a,b>, które są względnie pierwsze z liczbą p Zmienne pomocnicze: i – przebiega przez kolejne liczby w przedziale <a,b>. i ∈ N t – tymczasowo przechowuje dzielnik w algorytmie Euklidesa, t ∈ N ax – zmienna dla algorytmu Euklidesa. ax ∈ N bx – zmienna dla algorytmu Euklidesa. bx ∈ N Lista kroków: K01: Dla i = a,a+1,...,b wykonuj kroki K02...K08 ;przechodzimy przez kolejne liczby z przedziału <a,b> K02: ax ← i ;zmienne dla algorytmu Euklidesa K03: bx ← p K04: Dopóki bx ≠ 0 wykonuj kroki K05...K07 ;wyznaczamyNWD(i,p) K05: t ← bx K06: bx ← ax mod bx K07: ax ← t K08: Jeśli ax = 1, to pisz i ;NWD(i,p) = 1, zatem i jest względnie pierwsze z p K09: Zakończ PROGRAM #include <iostream> using namespace std; int main() { unsigned int a,b,p,ax,bx,i,t; cin >> a >> b >> p; for(i = a; i <= b; i++) { ax = i; bx = p; while(bx) { t = bx; bx = ax % bx; ax = t; } if(ax == 1) cout << i << " "; } cout << endl; return 0; } Program odczytuje z pierwszego wiersza trzy liczby a, b i p a następnie wypisuje wszystkie liczby w przedziale <a,b> względnie pierwsze z p. LICZBY PIERWSZE – GENERACJA PRZEZ SPRAWDZANIE PODZIELNOŚCI Problem: Znaleźć n kolejnych liczb pierwszych Liczba naturalna p jest liczbą pierwszą - posiadającą dokładnie dwa różne podzielniki: 1 i siebie samą. Bierzemy kolejne liczby naturalne poczynając od 2. Wybraną liczbę naturalną p próbujemy dzielić przez liczby od 2 do p-1. Jeśli żadna z tych liczb nie jest podzielnikiem p, to liczba p jest pierwsza. ALGORYTM WYZNACZANIA LICZB PIERWSZYCH PRZEZ SPRAWDZANIE PODZIELNOŚCI Wejście n- liczba określająca ile liczb pierwszych należy wygenerować , n ∈ N Wyjście n kolejnych liczb pierwszych Zmienne pomocnicze lp- zlicza kolejno wygenerowane liczby pierwsze. lp∈N p- kolejno testowane liczby naturalne. p ∈N d- kolejne dzielniki. d ∈N Lista kroków: K01:lp ← 0 ;zerujemy licznik liczb pierwszych K02: p ←2 ;generację rozpoczynamy od 2 K03: Dopóki lp<n, wykonuj kroki K04…K08 ;pętla generacji liczb pierwszych K04: Dla d=2,3…,p-1, wykonuj krok K05 ;pętla sprawdzania podzielności przez p i d K05: Jeśli p mod d=0,idś do K08 ;jeśli p dzieli się przez d, to nie jest pierwsza K06: Pisz p ;p jest pierwsze K07: lp ←lp+1 ;zwiększamy licznik wygenerowanych liczb pierwszych K08:p ←p+1 ;przechodzimy do kolejnej liczby , kandydata K09: Zakończ PROGRAM #include <iostream> using namespace std; int main() { unsigned int n,lp,p,d; bool t; cin >> n; lp = 0; p = 2; while(lp < n) { t = true; for(d = 2; d < p; d++) if(p % d == 0) { t = false; break; } if(t) { cout << p << " "; lp++; } p++; } cout << endl; return 0; } Program w pierwszym wierszu czyta liczbę n i w następnych wierszach wypisuje n kolejnych liczb pierwszych. LICZBY PIERWSZE – SITO ERATOSTENESA Sito Eratostenesa jest algorytmem dwuprzebiegowym. Najpierw dokonuje on eliminacji liczb złożonych ze zbioru zaznaczając je w określony sposób, a w drugim obiegu przegląda zbiór ponownie wyprowadzając na wyjście liczby, które nie zostały zaznaczone. Na tym polu można dokonywać różnych optymalizacji. W pierwszym podejściu zastosujemy tablicę wartości logicznych S. Element S[i] będzie odpowiadał liczbie o wartości i. Zawartość S[i] będzie z kolei informowała o tym, czy liczba i pozostała w zbiorze (S[i] = true) lub została usunięta (S[i] = false). Rozwiązanie : Najpierw przygotowujemy tablicę reprezentującą zbiór liczbowy wypełniając ją wartościami logicznymi true. Odpowiada to umieszczeniu w zbiorze wszystkich liczb wchodzących w zakres od 2 do n. Następnie z tablicy będziemy usuwali kolejne wielokrotności początkowych liczb od 2 do pierwiastka całkowitego z n w pisując do odpowiednich elementów wartość logiczną false. Na koniec przeglądniemy zbiór i wypiszemy indeksy elementów zawierających wartość logiczną true – odpowiadają one liczbom, które w zbiorze pozostały. Za pierwszą wielokrotność do wyrzucenia ze zbioru przyjmiemy kwadrat liczby i. Przyjrzyj się naszemu przykładowi. Gdy wyrzucamy wielokrotności liczby 2, to pierwszą z nich jest 4 = 22. Następnie dla wielokrotności liczby 3 pierwszą do wyrzucenia jest 9 = 32, gdyż 6 zostało wyrzucone wcześniej jako wielokrotność 2. Dla 5 będzie to 25 = 52, gdyż 10 i 20 to wielokrotności 2, a 15 jest wielokrotnością 3, itd. Pozwoli to wyeliminować zbędne obiegi pętli usuwającej wielokrotności. ALGORYTM SITA ERATOSTENESA Wejście n – liczba określająca górny kraniec przedziału poszukiwania liczb pierwszych, n N, n > 1 Wyjście: Kolejne liczby pierwsze w przedziale od 2 do n. Zmienne pomocnicze S – tablica wartości logicznych. S[i] {false,true}, dla i = 2,3,...,n. g –zawiera granicę wyznaczania wielokrotności. g N i –przebiega przez kolejne indeksy elementów S[i]. i N w –wielokrotności wyrzucane ze zbioru S, w N Lista kroków: K01: Dla i = 2,3,...,n wykonuj S[i] ← true ; zbiór początkowo zawiera wszystkie liczby K02:g ← [√n] ; obliczamy granicę eliminowania wielokrotności K03:Dla i = 2,3,...,g wykonuj kroki K04...K08 ; w pętli wyrzucamy ze zbioru wielokrotności i K04:Jeśli S[i] = false, to następny obieg pętli K03 ; sprawdzamy, czy liczba i jest w zbiorze K05: w ← i2 ; jeśli tak, wyrzucamy jej wielokrotności K06: Dopóki w ≤ n wykonuj kroki K07...K08 ; ze zbioru K07: S[w] ← false K08:w ← w + i ; następna wielokrotność K09:Dla i = 2,3,...,n wykonuj krok K10 ; przeglądamy zbiór wynikowy K10:Jeśli S[i] = true, to pisz i ; wyprowadzając pozostałe w nim liczby K11:Zakończ PROGRAM #include <iostream> #include <cmath> using namespace std; int main() { unsigned int g,i,n,w; bool * S; cin >> n; S = new bool[n + 1]; for(i = 2; i <= n; i++) S[i] = true; g = (unsigned int)sqrt(n); for(i = 2; i <= g; i++) if(S[i]) { w = i * i; while(w <= n) { S[w] = false; w += i; } } for(i = 2; i <= n; i++) if(S[i]) cout << i << " "; cout << endl; delete [] S; return 0; } Program w pierwszym wierszu czyta liczbę n i w następnych wierszach wypisuje kolejne liczby pierwsze zawarte w przedziale od 2 do n. NAJMNIEJSZA WSPÓLNA WIELOKROTNOŚĆ Problem: Dla danych liczb naturalnych a i b znaleźć najmniejszą liczbę naturalną c, która jest podzielna bez reszty przez a i przez b. Liczba naturalna c o takich własnościach nosi nazwę NWW – najmniejszej wspólnej wielokrotności liczb a i b (ang. the least common multiple of a and b). Sposób obliczania NWW jest bardzo prosty: NWW(a,b) = a × b NWD(a,b) Jeśli liczby a i b są względnie pierwsze, to NWD(a,b) = 1. Wtedy NWW(a,b) = a × b. ALGORYTM WYZNACZANIA NAJMNIEJSZEJ WSPÓLNEJ WIELOKROTNOŚCI Wejście a,b– liczby, których NWW poszukujemy, a,b N Wyjście: NWW – najmniejsza wspólna wielokrotność liczb a i b. Zmienne pomocnicze ab – zapamiętuje iloczyn a i b. ab N t – tymczasowo przechowuje dzielnik w algorytmie Euklidesa, t N Lista kroków: K01: ab ← a × b ; zapamiętujemy iloczyn a i b K02: Dopóki b ≠ 0 wykonuj kroki K03...K05 ; algorytmem Euklidesa znajdujemy NWD(a,b) K03: t←b K04: b ← a mod b K05: a←t K06: ab ← ab div a ; obliczamy NWW K07: Pisz ab K08: Zakończ PROGRAM #include <iostream> using namespace std; int main() { unsigned long long a,b,t,ab; cin >> a >> b; ab = a * b; while(b) { t = b; b = a % b; a = t; } ab /= a; cout << ab << endl << endl; return 0; } Program odczytuje z pierwszego wiersza liczby a i b. W następnym wierszu wypisuje NWW(a,b).