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 parzyste w przedziale <a,b> K04: Pisz i ;wyprowadzamy liczbę parzystą K05: i i + 2 ;następna liczba parzysta K06: Zakończ
#include <iostream> using namespace std; int main() int a,b,i; PROGRAM Program spodziewa się w pierwszym wierszu liczb a i b. W kolejnych wierszach wyświetla liczby parzyste zawarte w przedziale <a,b>. cin >> a >> b; i = a; if(a % 2) i++; while(i <= b) cout << i << " "; i += 2; cout << endl; return 0;
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.
#include <iostream> using namespace std; int main() int a,b,i; PROGRAM 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 i, przerywamy pętlę K02 K04: Zakończ ;z tablicy P[]. Jeśli tak, wyprowadzamy
#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]; PROGRAM 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 for(i = a; i <= b; i++) for(j = 0; j < n; j++) if(i % P[j] == 0) cout << i << " "; break; cout << endl; return 0;
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
#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 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 K02: Jeśli a < b, to b b - a inaczej a a - b ;od większej liczby odejmujemy mniejszą aż się zrównają K03: Pisz a ;wtedy dowolna z nich jest NWD K04: Zakończ
Start Wczytaj a,b nie a<b tak a -= b b -= a Wypisz wynik koniec
PROGRAM #include <iostream> using namespace std; int main() unsigned int a,b; 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ść. cin >> a >> b; while(a!= b) if(a < b) b -= a; else a -= b; cout << a << endl; return 0; 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 ;wyznaczamy resztę z dzielenia, która staje się dzielnikiem K04: a t ;poprzedni dzielnik staje teraz się dzielną K05: Pisz a ;NWD jest ostatnią dzielną K06: Zakończ
#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 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
#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 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
#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 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.
Wejście ALGORYTM WYZNACZANIA NAJMNIEJSZEJ WSPÓLNEJ WIELOKROTNOŚCI 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 znajdujemy NWD(a,b) ; algorytmem Euklidesa 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; Program odczytuje z pierwszego wiersza liczby a i b. W następnym wierszu wypisuje NWW(a,b). cin >> a >> b; ab = a * b; while(b) t = b; b = a % b; a = t; ab /= a; cout << ab << endl << endl; return 0;