Dostałem takie pytanie
Mam problem z napisaniem programu algorytm b ,nie wiem w jaki sposób wrócić do wcześniejszego kroku ,nie rozumiem w jaki sposób mam wykorzystać schemat blokowy do napisania kodu.
Ciężka sprawa, bo jest to pytanie z gruntu tych najbardziej podstawowych.
Popatrzmy raz jeszcze na algorytm:
Algorytm B
- Przyjmij \(k \leftarrow 0\), a następnie powtarzaj operacje: \(k \leftarrow k+1\), \(u \leftarrow u/2\), \(v \leftarrow v/2\) zero lub więcej razy do chwili gdy przynajmniej jedna z liczb \(u\) i \(v\) przestanie być parzysta.
- Jeśli \(u\) jest nieparzyste to przyjmij \(t \leftarrow -v\) i przejdź do kroku 4. W przeciwnym razie przyjmij \(t \leftarrow u\).
- (W tym miejscu \(t\) jest parzyste i różne od zera). Przyjmij \(t \leftarrow t/2\).
- Jeśli \(t\) jest parzyste to przejdź do 3.
- Jeśli \(t > 0\), to przyjmij \(u \leftarrow t\), w przeciwnym razie przyjmij \(v \leftarrow -t\).
- Przyjmij \(t \leftarrow u-v\). Jeśli \(t \not = 0\) to wróć do kroku 3. W przeciwnym razie algorytm zatrzymuje się z wynikiem \(u \cdot 2^k\).
Proszę spokojnie na niego spojrzeć i zastanowi się czy rozumie Pan/Pani (rozumie w sensie potrafi wykonać za pomocą kartki i ołówka) wszystkie polecenia.
Jeżeli tak, proszę algorytm przeliczyć dla następujących danych
\(u = 258\) i \(v=640\).
15 minut przerwy
Wyszło? Proszę sprawdzić czy wynik jest poprawny (to znaczy czy otrzymana liczba rzeczywiście jest NWD dla podanych liczb).
5 minut przerwy
Wyszło?
Jeżeli nie raz jeszcze (ale tym razem dla \(u=121\; v=22\)).
Zakładam, ze w końcu wyjdzie.
Teraz bierzemy się za programowanie
Najpierw krok 1.
Przyjmij \(k \leftarrow 0\), a następnie powtarzaj operacje: \(k \leftarrow k+1\), \(u \leftarrow u/2\), \(v \leftarrow v/2\) zero lub więcej razy do chwili gdy przynajmniej jedna z liczb \(u\) i \(v\) przestanie być parzysta.
Wszystkie zmienne są całkowite. W algorytmie występują zmienne: \(u\), \(v\), \(k\), \(t\) , więc deklarujemy je:
1 |
int u, v, w, k; |
Nadajemy wartości początkowe zmiennym u
, v
i k
:
1 2 3 |
u = 258; v = 640; k = 0; |
Wymienione w tym punkcie operacje należy wykonywać do chwili gdy przynajmniej jedna z nich przestanie być parzysta1. Czyli warunkiem rozpoczęcia i kontynuowania pętli jest parzystość obu liczb.
Do dyspozycji mamy dwa podstawowe rodzaje pętli:
while
(najpierw sprawdza czy warunek jest spełniony, później wykonuje operacje);do
—while
(wchodzi do pętli, wykonuje operację i sprawdza czy warunek jest spełniony).
Wybieramy oczywiście while
(proszę się zastanowić czemu?).
Teraz jak sprawdzić czy liczba jest parzysta? Najprościej podzielić przez dwa i sprawdzić jaka jest reszta. Do wyliczania reszty z dzielenia służy operator %
.
Zatem będzie to jakoś tak:
1 2 3 4 5 6 |
while (u % 2 == 0 && v % 2 == 0) { u = u / 2; v = v / 2; k = k + 1; } |
Chyba jest to jasne: tak długo jak u jest parzyste i v jest parzyste wykonuj…
Po wyjściu z pętli jedna (lub obie) z liczb u
, v
jest nieparzysta.
Teraz krok 2.
Jeśli \(u\) jest nieparzyste to przyjmij \(t \leftarrow -v\) i przejdź do kroku 4. W przeciwnym razie przyjmij \(t \leftarrow u\).
To jest chyba dosyć proste. Użyjemy poleceń if
(jeżeli) i goto
(przejdź do kroku).
1 2 3 4 5 6 7 8 9 |
if (u % 2 == 1) { t = -v; goto krok4; } else { t = u; } |
Z instrukcji if
wychodzimy albo za pomocą polecenia goto krok4
albo po przejściu przez gałąź else
. Jak wychodzimy do kroku 4 to nie wiemy jaka liczba jest w t
(parzysta czy nie). Po przejściu przez else
w t
jest wartość parzysta (czemu?).
Kroki 3 i 4:
Rozpatruję razem bo są ze sobą związane.
Przyjmij \(t \leftarrow t/2\). Zaprogramujemy to w oczywisty sposób. (Dodaję etykietę krok3
żeby można było do tego kroku wrócić):
1 2 |
krok3: t = t / 2; |
Jeżeli \(t\) jest parzyste to wróć do kroku 3 (znowu dodam etykietę krok4
)
1 2 3 |
krok4: if (t % 2 == 0) goto krok3; |
Krok 5
Jeśli \(t > 0\), to przyjmij \(u \leftarrow t\), w przeciwnym razie przyjmij \(v \leftarrow -t\).
To jest łatwe. Użyję instrukcji if
1 2 3 4 |
if (t > 0) u = t; else v = -t; |
Krok 6
Przyjmij \(t \leftarrow u-v\). Jeśli \(t \not = 0\) to wróć do kroku 3. W przeciwnym razie algorytm zatrzymuje się z wynikiem \(u \cdot 2^k\).
Pierwsza część oczywista:
1 |
t = u - v; |
teraz polecenie if
1 2 |
if (t != 0) goto krok3; |
tu się chwilkę zatrzymajmy. wartości u
i v
powstaję w kroku poprzednim (5) z wartości t
, gdy przestanie ona być parzysta, więc na pewno są liczbami nieparzystymi; różnica dwu liczb nieparzystych jest parzysta, zatem spokojnie możemy przejść do kroku 3 gdzie będziemy tę wartość dzielili przez dwa.
Gdy t
jest równe zero — skończyliśmy obliczenia i pozostaje całkiem skomplikowana rzecz do zrobienia: wyliczenie wartości \(u \cdot 2^k\). Język C nie zna operatora potęgowania. Mamy do dyspozycji trzy rozwiązania:
- Wypisać wartości
u
ik
i powiedzieć użytkownikowi żeby sobie sam to wyliczył. - Użyć funkcji
pow(
\(\alpha\),
\(\beta\))
, która potrafi dla wartości typudouble
wyliczyć \(\alpha^\beta\) (uwzględniając wszystkie matematyczne ograniczenia). - Napisać prosty program w pętli mnożący
u
„przez siebie”k
razy:
1 2 3 4 5 6 |
int wynik = 1; while (k > 0) { wynik = wynik * u; k = k - 1; } |
(Gdy k
jest zero — \(2^k\) to 1; czyli chyba jest dobrze.)
Teraz możemy wydrukować ostateczny wynik:
1 |
printf("NWD wynosi %d\n", u * wynik); |
Mam nadzieję, że ten przydługawy opis nieco rozjaśnia. Słowny opis algorytmu przerobiłem na program w C. Gdyby wcześniej wyrysować schemat blokowy łatwiej byłoby zidentyfikować pętle.
W algorytmie tym powrót realizowany jest w kroku 4 (przejdź do kroku 3) i w kroku 6 (znowu przejdź do kroku 3). Użyłem polecenia goto
. I to są dwie pętle realizowane przez algorytm.
- Ma to sens, gdyż wykonywane jest dzielenie przez dwa; i chcemy dzielić przez dwa wyłącznie liczby parzyste.↩