Anda di halaman 1dari 17

Bab3 GreatestCommonDivisor

3.1 Masalah

Untuk bilangan bulat a dan b, pembagi umum terbesar a dan b adalah bilangan bulat terbesar d
sehingga d adalah faktor dari a dan b. Pembagi umum terbesar dinotasikan dengan gcd (a, b). Kami
mengatakan a dan b adalah penyediaan yang relatif prima gcd (a, b) = 1. Dalam bab ini, kami
mengembangkan banyak konsep C ++ dengan mempelajari masalah tertentu yang melibatkan gcd
bilangan bulat.

Berikut adalah masalah klasik: Biarkan n menjadi bilangan bulat positif. Dua bilangan bulat, a
dan b, dipilih secara independen dan seragam dari himpunan {1,2, ..., n}. Biarkan pn menjadi
probabilitas bahwa a dan b relatif prima. Doeslimn → ∞ pn existand, ifso, nilainya?

Komputer tidak dapat menyelesaikan masalah ini bagi kami, tetapi itu dapat membantu
kami merumuskan suatu dugaan. Kami mencoba sejumlah pendekatan termasuk enumerasi
mendalam, menghasilkan pasangan angka secara acak dan mencatat hasilnya, serta penggunaan
Euler's totient.

Urutan pertama bisnis, bagaimanapun, adalah mengembangkan prosedur untuk menghitung


pembagi bersama terbesar dari dua bilangan bulat.

3.2 Penutup pertama

Pada bagian ini kami mengembangkan prosedur yang benar, tetapi sangat tidak efisien,
untuk menghitung pembagi umum terbesar dari dua bilangan bulat. Tujuan kami adalah untuk
memperkenalkan sejumlah konsep C ++ serta untuk membuat prosedur gcd. Prosedur ini
membutuhkan dua bilangan bulat dan mengembalikan pembagi umum terbesar mereka. Kemudian,
kami mengganti prosedur yang tidak efisien ini dengan metode yang jauh lebih efisien. Namun,
sebelum kita mulai, kita perlu membahas sedikit terminologi. Para matematikawan dan pemrogram
komputer menggunakan fungsi kata berbeda. Fungsi seorang
matematikawanmencakupstemachoeexauniquevaluey = f (x). Anggaplah menghitung, katakanlah f
(8) dan hasilnya adalah 17. Kemudian jika kita menghitung f (8) beberapa menit kemudian, kita
dijamin bahwa hasilnya masih 17.

Sebaliknya, untuk programmer C ++, adalah wajar bahwa suatu fungsi dapat mengembalikan
nilai yang berbeda (bahkan dengan argumen yang sama) pada waktu yang berbeda! Ini karena fungsi
C ++ dapat mengakses data di luar argumen mereka; misalnya, ada fungsi C ++ yang melaporkan
waktu saat ini. Jelas, nilai dari fungsi ini berubah dari satu menit ke menit berikutnya.

Sebagai seorang matematikawan, bias saya, tentu saja, untuk penggunaan kata secara
matematis. Oleh karena itu, dalam buku ini, kami menggunakan kata yang berbeda untuk fungsi C
++; kami menyebutnya prosedur. Tata nama ini seharusnya tidak mengecewakan rekan-rekan ilmu
komputer kita, tetapi ketika Anda membaca buku-buku atau dokumentasi lain tentang prosedur C
++, waspadalah bahwa mereka secara diam-diam memilih fungsi.
(Somebooksmayrefertomethodsandwe memperkenalkan terminologi itu nanti.) Dengan masalah
nomenklatur di belakang kami, kami sekarang mengembangkan prosedur untuk menghitung
pembagi umum terbesar.
Kami perlu menyebutkan prosedurnya. Nama file dapat menjadi administrator paling
populer, tetapi tidak ada kehilangan kejelasan dalam menamainya dengan gcd. Kami ingin gcd
menerima dua argumen (input) tipe panjang dan mengembalikan jawaban yang juga tipe panjang.
Kode untuk prosedur gcd ditulis dalam dua file bernama gcd.h dan gcd.cc. File header, gcd.h,
digunakan untuk menggambarkan prosedur (baik dalam C ++ dan dalam bahasa Inggris). File gcd.cc
berisi instruksi aktual untuk menghitung pembagi umum terbesar.

Organisasi ini mirip dengan mendeklarasikan versus variabel penugasan. Deklarasi ini
mengumumkan jenis variabel dan penugasan memberikan variabel sebuah nilai. Demikian juga, file
header mengumumkan jenis prosedurnya (dua masukan panjang dan kembali lama) dan file .cc
memberikan instruksi yang sebenarnya untuk dilakukan.

File gcd.cc tidak memiliki prosedur () utama; the main () ditemukan di file lain. File yang
terakhir termasuk direktif #include "gcd.h".

Algoritma gcd pertama yang kami sajikan sangat tidak efisien. Saat kami mengembangkan
algoritme yang lebih baik, kami mengganti fi le gcd.cc, tetapi fi le gcd.h tidak berubah. File gcd.h
terlihat seperti ini.

Program 3.1: File header gcd.h.

1 #ifndef GCD_H
2 #define GCD_H
3
4 / **
5 * Hitung pembagi umum terbesar dari dua bilangan bulat.
6 * @param yang pertama bilangan bulat
7 * @param b bilangan bulat kedua
8 * @Kembali pembagi umum terbesar a dan b
9 * / 10
11 panjang gcd (panjang, panjang b);
12
13 #endif
Baris 11 adalah baris paling penting dari file ini. Pernyataan

Pembagi Umum Terbesar 33

long gcd (long a, long b);

menyatakan gcd menjadi prosedur. Prosedur ini membutuhkan dua argumen input (bernama a dan
b) yang keduanya bertipe panjang. Yang pertama pada baris ini (di sebelah kiri dari gcd) adalah jenis
kembalinya prosedur; ini menunjukkan bahwa prosedur mengembalikan nilai tipe panjang.

Fitur lain dari file ini: garis 1, 2, dan 13 adalah mekanisme untuk mencegah penyertaan
ganda (lihat diskusi di halaman 26). Baris 4–9 adalah deskripsi prosedur gcd. Deskripsi ini mencakup
kalimat yang menjelaskan prosedur apa yang dilakukan, penjelasan tentang parameter yang
diteruskan ke prosedur, dan penjelasan tentang nilai yang dikembalikan oleh prosedur. Tag @param
dan @return dibaca oleh Doxygen untuk menghasilkan halaman web yang bagus untuk prosedur ini.
Kami siap untuk bekerja pada file gcd.cc. File ini memiliki struktur berikut.

#include "gcd.h"

long gcd (long a, long b) {....... return d; }

Definisi dari prosedur gcd terlihat hampir identik dengan deklarasi dalam file header. Titik
koma pada baris 11 dari gcd.h diganti dengan kurung buka. Penjepit terbuka diikuti oleh instruksi
untuk menghitung pembagi umum terbesar a dan b, dan nilai mereka secara bertahap akan dinamai
kembali d. Pengembalian d; pernyataan menyebabkan nilai dalam d menjadi "jawaban" yang
dikembalikan oleh prosedur ini.

Strategi kami adalah menguji secara berurutan bilangan bulatuntuk melihat pembagi dari a
dan b, dan melacak nilai terbesar yang membagi keduanya.

Ada beberapa hal yang perlu kita khawatirkan terlebih dahulu.

• Apa yang terjadi jika prosedur gcd diberi nilai negatif untuk a atau b?

Tidak ada yang salah dengan membiarkan a atau b menjadi negatif. Bagaimanapun,

gcd (a, b) = gcd (−a, b) = gcd (a, −b) = gcd (−a, −b).

• Apa yang terjadi jika satu (atau keduanya) a atau b nol?

Jika hanya satu dari ini adalah nol, maka tidak ada masalah matematika karena gcd
(a, 0) = | a | asalkan a≠ 0. Namun, gcd (0,0) tidak didefinisikan. Kita perlu memutuskan
apa yang harus dilakukan dalam kasus ini. Kita bisa segera menghentikan program ini (ini
dilakukan oleh pernyataan keluar (1);). Namun, solusi yang lebih baik adalah mencetak a
pesan peringatan dan mengembalikan nilai, katakan nol.

Kita perlu merevisi dokumentasi di gcd.h untuk mencerminkan ini.

Program 3.2: Dokumentasi yang direvisi untuk gcd dalam file header gcd.h.

4 / **
5 * Hitung pembagi umum terbesar dari dua bilangan bulat.
6 * Catatan: gcd (0,0) akan mengembalikan 0 dan mencetak pesan kesalahan.
7 * @param sebuah bilangan bulat pertama
8 * @param b bilangan bulat kedua
9 * @kembalikan pembagi umum terbesar a dan b.
10 * / Sekarang kita bekerja di gcd.cc.

File dimulai sebagai berikut.

Program 3.3: Awal dari file gcd.cc.

1 #include "gcd.h"
2 #include <iostream>
3 menggunakan namespace std;
4
5 long gcd (long a, long b) {
6
7 // jika a dan b sama-sama nol, mencetak kesalahan dan mengembalikan 0
8 jika ((a == 0) && (b == 0)) {
9 cerr << "PERINGATAN: gcd dipanggil dengan kedua argumen sama dengan nol."
10 << endl;
11 kembali 0;
12 }

Kami membutuhkan #include <iostream> on line 2 karena kami mungkin perlu menulis
pesan kesalahan (dalam kasus gcd (0,0) dipanggil). Baris 5 memulai definisi prosedur gcd.

Pertama-tama, periksa tanda peringatan yang berbeda dengan zero; hal ini terjadi8. Struktur
umum dari pernyataan if adalah ini:

if (condition) {
statement;
}
Jika kondisi (ekspresi yang mengevaluasi ke bool) benar, maka pernyataan dalam kurung
kurung akan dieksekusi. Jika tidak (kondisi salah), semua pernyataan dalam kurung kurung tutup
dilewatkan.

Untuk program kami, jika a dan b sama dengan nol, maka kondisinya benar dan dua
pernyataan terlampir dieksekusi. Yang pertama menulis pesan peringatan ke objek bernama cerr.
Objek cerr mirip dengan objek cout. Itu tidak mungkin dilakukan di luar. Namun, komputer
menyediakan dua aliran output, cout dan cerr, dan keduanya menulis ke layar. The cout biasanya
digunakan untuk output standar dan cerr untuk pesan kesalahan.

Pernyataan kedua dikendalikan oleh ini jika kembali 0 ;. Ketika pernyataan ini dieksekusi,
maka prosedur dan nilai yang diperhitungkan kembali, ada program yang tidak dijalankan.

Selanjutnya, kami memastikan bahwa a dan b tidak negatif.

Program 3.4: Memastikan a dan b tidak negatif dalam gcd.cc.

14 // Pastikan a dan b sama-sama non-negatif


15 jika (a <0) {
16 a = -a;
17}
18 jika (b <0) {
19 b = -b;
20}
Kode ini cukup mudah. Jika negatif, diganti dengan -a; dan juga untuk b. Namun, ada sesuatu
yang tidak diketahui. Apakah efek samping memiliki efek samping?
Kenakanpermainmembuattandauntukprosesuregori. Apakah dia mengubah nilai a dan b dalam
prosedur yang disebut gcd?

Jawabannya adalah bahwa tidak ada perubahan yang dilakukan pada nilai apa pun di luar
prosedur gcd; tidak ada efek samping. Alasannya adalah ketika prosedur lain (misalnya, main ())
memanggil gcd, argumennya adalah disalin ke a dan b. Kami mengatakan bahwa prosedur C ++
memanggil berdasarkan nilai; argumennya adalah salinan asli. Sebagai contoh, misalkan main ()
berisi kode ini:

lon x = -10;
long y = 15;
cout << gcd (x, y) << endl;

Ketika gcd dipanggil, thecomputersets a equalto – 10 dan b equalto15; nilai a dan b adalah salinan
pribadi dari nilai-nilai ini. Akhirnya, gcd mencapai baris 16 di mana ia menggantikan dengan -a (yaitu,
menetapkan hingga 10). Namun, x asli di utama tidak terpengaruh oleh ini.

Selanjutnya kita sampai ke inti permasalahan. Kami menguji semua pembagi mungkin dari 1
hingga a dan melihat yang membagi baik a dan b. Ada satu kesalahan kecil. Jika nol, maka
jawabannya harus b. Kami menganggap itu sebagai kasus khusus. Inilah bagian terakhir dari program
ini.

Program 3.5: Bagian terakhir dari program gcd.cc.

22 // jika a adalah nol, jawabannya adalah b


23 jika (a == 0) {24 return b; 25} 26
27 // jika tidak, kami memeriksa semua kemungkinan mulai dari 1 hingga
28
29 d; // d akan memegang jawaban
30
31 untuk (panjang t = 1; t <= a; t ++) {
32 if ((a% t ==) && (b% t == 0)) {
33 d=t;
34 }
35 }
36
37 kembali d;
38}

Baris 23-25 menangani kasus khusus di mana a nol. Setelah itu, kami mendeklarasikan
variabel d untuk menahan jawabannya.
Baris 31–35 melakukan sebagian besar pekerjaan program ini. Kami mulai dengan kata kunci
C ++ baru: untuk. Bentuk umum dari pernyataan ini adalah:

for (initial statement, credit conditions, advance statement) {


statistics that must be performed on each iteration;
}
Pernyataan awal dieksekusi pada saat pertama kali pernyataan untuk pernyataan tercapai.
Dalam kasus kami, pernyataan awal adalah long t = 1 ;. Ini mendeklarasikan variabel bilangan bulat
panjang baru bernama t nilainya 1.
Selanjutnya kondisi pengujian dievaluasi; jika kondisi ini mengevaluasi ke TRUE, pernyataan
yang dilampirkan dalam kurung kurawal dijalankan. Dalam kasus kami, kondisi pengujian adalah t <=
a ;. Selama t kurang dari atau sama dengan, kita lakukan pernyataan yang terlampir pada kurung
kurawal.
Setelah pernyataan tertutup dieksekusi, pernyataan maju dijalankan; dalam kasus kami,
pernyataan itu adalah t ++ ;. Ini berarti bahwa t dinaikkan sebesar 1. Sekarang seluruh proses
berulang. Untuk contoh kita, t sekarang memegang nilai 2. Selama ini kurang dari atau sama dengan,
maka pernyataan tertutup dieksekusi. Kemudian t dimajukan ke 3, lalu ke 4, dan seterusnya, sampai
kondisi uji adalah FALSE. Pada titik itu, untuk loop, jadilah dan jauhkan penjepit yang terbuka; dalam
contoh kami di baris 37 dan pernyataannya adalah kembali d ;.
Dengan kata lain, kode untuk
(long t = 1; t <= a; t ++) {
statement;
}
mengeksekusi pernyataan antara kurung kurawal kali dengan t sama dengan 1, lalu 2, lalu 3, dan
seterusnya, sampai t sama dengan a.
Gaya untuk pernyataan ini umum dalam program. Tentu saja, kita dapat menggunakan
pernyataan for mengundurkan diri melalui nilai-nilai seperti dalam kode ini:
for (long s = n; s> 0; s--) {
pernyataan;
}
Alternatifnya, kita bisa melangkah hanya melalui nilai ganjil indeks:
for (long j = 1; j <= n; j + = 2) {
pernyataan;
}
Untuk prosedur gcd kami, biarkan mengambil nilai-nilai, 1, 2, 3, dan seterusnya, sampai
persis apa yang kita inginkan. Untuk setiap nilai t, kita periksa apakah t adalah pembagi dari a dan b.
Kami melakukan ini dengan bersyarat jika ((a% t ==) && (b% t == 0)). Jika kondisi ini terpenuhi, kita
perbarui d ke nilai t sekarang, dan ini adalah apa yang terjadi pada baris 33.
Setelah loop selesai, nilai yang ditahan di d dikembalikan.
Sekarang setelah program gcd kami selesai, sekarang saatnya untuk mencobanya. Dalam file
terpisah, yaitu kami beri nama gcd-tester.cc, kami menulis prosedur main () sederhana untuk
mencoba kode kami

.
Program 3.6: Program untuk menguji prosedur gcd.

1 #include "gcd.h"
2 #include <iostream>
3 menggunakan namespace std;
4
5 / **
6 * Program untuk menguji prosedur gcd.
7*/
8 int main () {
9 long a, b;
10
11 cout << "Masukkan nomor pertama ->";
12 cin >> a;
13 cout << "Masukkan nomor kedua ->";
14 cin >> b;
15
16 cout << "The gcd of" << a << "dan" << b << "adalah"
17 << gcd (a, b) << endl;
18 return 0;
19}

3.3 Metode Euclid

Program yang kami kembangkan di Bagian 3.2 berfungsi, tetapi ini adalah algoritma yang
lambat dan tidak efisien. Misalkan saja, apakah Anda perlu menjadi pemicu yang paling penting dari
sekitar satu miliar? Ini tidak terlalu besar untuk bilangan bulat panjang, tetapi untuk menemukan
jawabannya, algoritme divisi percobaan berjalan untuk miliaran iterasi. Ada cara yang jauh lebih baik
yang dikembangkan oleh Euclid. Ide utamanya adalah hasil sebagai berikut.

Proposisi3.1. Biarkan a, b menjadi bilangan bulat positif dan biarkan c = mod b. Kemudian gcd (a, b) =
gcd (b, c).

Bukti. Biarkan a, b menjadi bilangan bulat positif dan biarkan c = mod b; yaitu, a = qb + c di mana q,
c∈Zdan 0≤c <b. Perhatikan bahwa jika d adalah pembagi umum a dan b, maka d juga merupakan
pembagi c karena c = a − qb. Sebaliknya, jika d adalah pembagi umum b dan c, maka (becausea = qb
+ c) d juga divis atau dari a . Oleh karena itu ini untuk pembagi umum a dan b adalah sama dengan
himpunan pembagi umum b dan c. Oleh karena itu gcd (a, b) = gcd (b, c).

Kami menggunakan ini untuk mengembangkan suatu algoritma. Misalkan kita ingin
menemukan gcd (80,25). Dengan Proposition 3.1, kami menghitung 80 mod 25 = 5 dan jadi gcd
(80,25) = gcd (25,5). Untuk menemukan gcd (25,5), kami kembali menerapkan Proposition 3.1.
Karena 25 mod 5 = 0, kita memiliki gcd (25,5) = gcd (5,0). Pada titik ini Proposisi 3.1 tidak berlaku
karena 0 tidak positif. Namun, kita tahu bahwa gcd (5,0) = 5 sehingga kita memiliki

gcd (80,25) = gcd (25,5) = gcd (5,0) = 5


dan kami hanya perlu melakukan dua divisi (bukan 25 atau 80).

Pada bagian ini kami menyajikan dua program untuk menghitung pembagi umum terbesar
dengan metode ini. Inilah yang pertama.

Program 3.7: Prosedur rekursif untuk gcd.

1 #include "gcd.h"
2 #include <iostream>
3 menggunakan namespace std;
4
5 panjang gcd (long a, long b) {
6 // Pastikan a dan b sama-sama nonnegative
7 jika (a <0) a = -a;
8 jika (b <0) b = -b;
9
10 // jika a dan b sama-sama nol, cetak kesalahan dan kembalikan 0
11 jika ((a == 0) && (b == 0)) {
12 cerr << "PERINGATAN: gcd dipanggil dengan kedua argumen sama dengan nol."
13 << endl;
14 kembali 0;
15}
16
17 // Jika b bernilai nol, jawabannya adalah
18 jika (b == 0) mengembalikan a;
19 // Jika a adalah nol, jawabannya adalah b
20 jika (a == 0) kembali b;
21
22 panjang c = a% b;
23
24 return gcd (b, c);
25}

Baris 7 dan 8 memastikan bahwa a dan b tidak negatif. Kami menggunakan sintaks yang
sedikit berbeda untuk pernyataan ini. Ketika sebuah if diikuti oleh satu pernyataan, kurung kurawal
dapat dihilangkan. Pernyataan baris tunggal if (a <0) a = -a; setara dengan ini:

if (a <0) {

a = -a;

Baris 11 Periksa untuk melihat apakah argumen keduanya nol; jika ya, kami mengeluarkan
peringatan dan mengembalikan nol.

dan kembali nol.


Baris 18 dan 20 periksa apakah salah satu argumennya nol; jika demikian, argumen lainnya
adalah jawaban yang kita inginkan.

Yang asli kerja prosedur ini ada pada baris 22 dan 24. Jika program mencapai garis 22, maka
kita tahu bahwa a dan b adalah bilangan bulat positif dan Proposisi 3.1berlaku. Kami menghitung c
menjadi mod b, jadi jawaban untuk masalah ini adalah gcd (b, c) dan kami mengembalikannya.

Pertanyaannya adalah: Bagaimana perhitungan gcd (b, c)? Jawabannya adalah bahwa
prosedur gcd memanggil dirinya sendiri. Ini dikenal sebagai rekursi. Jika kita memanggil gcd (80,25),
maka ketika kita mencapai garis 24, c memegang nilai 5. Pada titik ini kita mengeluarkan panggilan
ke gcd (25,5). Panggilan sebelumnya ke gcd (80,25) "ditahan" sambil menunggu hasil dari gcd (25,5).
Bilamana masuk ke dalam tempatpembelianpelabuhanrentang24kemilapankeu25, bequalto5,
andcequalto0. Athirdcalltogcddisediakanuntukpemilihanpengiriman (5.0)
danpemilikkeluaranyangdilakukanlangsung. Selama ini untuk gcd (5,0), wecometo baris 18 (karena b
== 0 mengevaluasi TRUE) dan 5 dikembalikan. Ini diteruskan kembali ke yang kedua dan kemudian
ke panggilan pertama ke gcd dan jawaban terakhir, 5, dikembalikan.

Rekursi adalah ide yang kuat. Ketika itu berlaku, seperti dalam hal ini, itu dapat membuat
kode sangat sederhana. Bahaya utama dalam menggunakan teknik ini adalah rekursi tak berhingga:
jika program tidak memeriksa kondisi batas yang memadai, ia dapat berjalan selamanya tanpa
mengembalikan jawaban. Ini sama dengan mengabaikan kasus dasar dalam bukti dengan induksi.

Contoh klasik rekursi adalah program untuk menghitung faktorial. Di sini adalah gagasan
umum yang disajikan dalam program yang salah.

long factorial (long n) {


return n*factorial(n-1);
}
Untuk menghitung faktorial (5) komputer membuat panggilan ke faktorial (4) yang (jika
semua berjalan dengan baik) mengembalikan nilai 24. Kami kemudian kalikan ini dengan 5 untuk
mendapatkan jawaban kami.

Kesalahannya, bagaimanapun, adalah bahwa faktorial (4) menyebut faktorial (3) yang
memanggil faktorial (2), dan seterusnya selamanya. Kita perlu menangkap proses ini di suatu
tempat, dan tempat yang tepat adalah ketika n adalah nol.

Ini versi yang lebih baik.

long factorial(long n) {
if (n==0) return 1;
return n*factorial(n-1);
}
faktorial panjang (panjang n) {if (n == 0) mengembalikan 1; kembali n * faktorial (n-1); } Ini
merupakan gambaran yang benar dari jari (5), tetapi tidak benar-benar bebas dari perangkap
rekatan terbatas. Cari tahu sendiri masalahnya dan apa yang dapat Anda lakukan untuk
mengatasinya. (Lihat Latihan 3.2.)
Program3.7secara benar, prosedur gcd yang efisien. Pada titik ini, ia akan menjadi
propertomoveontothroblemathand (dijelaskansebelumnya awal bab). Namun, adalah mungkin
untuk membuat program sedikit lebih efisien dan, dengan demikian, kita dapat mempelajari fitur C
++ lainnya: sementara loop.

Terdiri dari kemampuan fisik dalam Program3.7. Yang terakhir adalah bahwa a dan b tidak
negatif dan tidak nol sama sekali dijalankan pada setiap iterasi. Namun, seseorang dapat
membuktikan bahwa ia tidak perlu melaporkannya ke dalam peta ini. Pengujian-pengujian theextra
tidak perlu dilakukan. Juga, komputer perlu melakukan sejumlah pekerjaan sederhana setiap kali
prosedur baru dipanggil. Untuk menghitung yang paling umum Pembagi dua nomor besar dapat
menyebabkan puluhan panggilan ke gcd. Overhead ini bukan masalah serius, tetapi jika kami
berencana untuk memanggil gcd berulang kali, perbaikan kecil bermanfaat.

Kami sekarang menyajikan versi lain dari prosedur gcd yang tidak menggunakan rekursi.

Program 3.8: Prosedur iterasi untuk gcd.

1 #include "gcd.h"
2 #include <iostream>
3 menggunakan namespace std;
4 5 panjang gcd (long a, long b) {
6 // Pastikan a dan b sama-sama nonnegatif
7 jika (a <0) a = -a;
8 jika (b <0) b = -b;
9
10 // jika a dan b sama-sama nol, cetak kesalahan dan kembalikan 0
11 jika ((a == 0) && (b == 0)) {
12 cerr << "PERINGATAN: gcd dipanggil dengan kedua argumen sama dengan nol."
13 << endl;
14 kembali 0;
15}
16
17 panjang new_a, new_b; // tempat untuk menyimpan versi baru a dan b
18
19 / *
20 * Kami menggunakan fakta bahwa gcd (a, b) = gcd (b, c) di mana c = a% b.
21 * Perhatikan bahwa jika b adalah nol, gcd (a, b) = gcd (a, 0) = a. Jika a adalah nol,
22 * dan b tidak, kita mendapatkan% b sama dengan nol, sehingga new_b akan menjadi nol,
23 * maka b akan menjadi nol dan loop akan keluar dengan == 0, yang
24 * adalah apa kami mau.
25 * /
26
27 sedangkan (b! = 0) {
28 new_a = b;
29 new_b = a% b;
30
31 a = new_a;
32 b = new_b; 33}
34
35 mengembalikan a;
36}

Awal dari program ini sama dengan Program 3.7; kami memastikan argumen tidak negatif
dan tidak nol. Pada baris 17 kami menyatakan dua variabel baru: new_a dan new_b. Idenya adalah
membiarkan

a’←b and b’←a mod b

dan melanjutkan dengan a’ dan b’ sebagai pengganti a dan b. Kami terus melakukan ini sampai b
mencapai nol, dan kemudian akan memegang jawabannya. Misalnya, dimulai dengan a = 80 dan b =
25,

Langkah-langkah ini terjadi pada garis 27–33. Pernyataan kontrol sementara. Bentuk umum dari
pernyataan sementara adalah ini:

while (condition) {pernyataan untuk melakukan; }

Ketika sebuah program bertemu pernyataan sementara, ia memeriksa untuk melihat apakah
conditions BENAR atau SALAH. Jika kondisi FALSE, itu akan mengabaikan semua pernyataan yang
terlampir pada kurung kurawal. Namun, jika kondisi TRUE, maka pernyataan di dalam tanda kurung
dieksekusi dan kemudian kita mulai loop lagi, memeriksa untuk melihat apakah kondisi BENAR atau
SALAH. Dengan cara ini, loop dijalankan berulang-ulang sampai saat kondisi yang ditentukan dalam
pernyataan sementara adalah FALSE. Inilah yang kami butuhkan di sini. Selama b bukan nol, kita
mengganti a dan b dengan b dan a% b, masing-masing. Kami meminta bantuan dari variabel
sementara new_a dan new_b untuk melakukan ini. Sementara pernyataan berakhir, yang
memegang pembagi umum terbesar dari a dan b asli, dan kami mengembalikannya pada baris 35.
Sangat menarik untuk dicatat bahwa kami tidak memerlukan dua variabel sementara untuk while.
Pekerjaan berikut juga.

while (b! = 0) {
new_b = a% b; a = b; b = new_b;
}
Meskipun ini benar, itu lebih sulit untuk dipahami daripada loop sementara dalam Program 3.8.
Versi yang sedikit lebih verbose menunjukkan dengan jelas bagaimana a dan b diperbarui dari nilai-
nilai a dan b sebelumnya. Kejelasan sering lebih disukai daripada kepintaran karena kejelasan lebih
mungkin benar.

3.4 Loopingwith untuk, sementara, dan lakukan Kami telah memperkenalkan dua pernyataan
perulangan C ++: untuk dan sementara. Di sini kami memperkenalkan Anda kepada yang ketiga:
lakukan. Ingat bahwa loop sementara terstruktur seperti ini:

while (condition) {pernyataan untuk dieksekusi; }


42 C ++ untuk Matematikawan

Syarat-syarat yang disodorkanadalahpenulisan tertutupyang pernah dilakukan. Ifcondition adalah


FALSE, pernyataan tidak akan pernah dieksekusi. Kebetulan, struktur sementara dapat diganti
dengan for loop sebagai berikut.

untuk (; kondisi;) {pernyataan untuk dieksekusi; } Pernyataan awal dan maju tidak ada, sehingga
tidak ada pengaruhnya. Kami menyebutkan ini sebagian besar sebagai rasa ingin tahu; versi
sementara lebih disukai karena lebih jelas.

Kadang-kadang Anda menemukan bahwa kondisi yang Anda inginkan tidak cukup sampai beberapa
rangkaian instruksi telah dilakukan setidaknya sekali. Sebagai contoh, sebuah
programmembacaakatadarifilterdanmendapatkanpembelianberapa nilai negatif yang ditemui. Kita
perlu membaca setidaknya satu nilai data sebelum kondisi tersebut masuk akal. Untuk situasi seperti
itu, struktur berikut dapat digunakan.

lakukan {pernyataan untuk dieksekusi; } while (kondisi); Pernyataan yang dijalankan dilakukan
setidaknya sekali. Di akhir loop, kondisi diperiksa. Jika TRUE, maka kita kembali ke awal loop untuk
putaran yang lain; jika kondisinya FALSE, loop selesai dan kontrol lolos ke pernyataan berikutnya
setelah loop. Struktur loop do-while tidak digunakan sesering sementara dan untuk loop.

Ada dua pernyataan khusus yang dapat digunakan untuk memodifikasi eksekusi loop: break dan
continue. Perintah break menyebabkan loop untuk segera keluar dengan kontrol melewati ke
pernyataan berikutnya setelah loop. Pertimbangkan kode ini.

untuk (long a = 0; a <100; a ++) {statement1; pernyataan2; jika (x> 0) pecah; pernyataan3;
statement4; } pernyataan5; Jika setelah statement2 variabel x memegang kuantitas positif, kita
melewatkan statement3 dan statement4, dan langsung ke statement5. Perintah break dapat
digunakan dengan while dan melakukan loop juga. Perintah terus mengarahkan komputer untuk
pergi ke akhir loop dan kemudian melakukan apa yang alami pada saat itu. Pertimbangkan kode ini.

untuk (long a = 0; a <100; a ++) {statement1; pernyataan2; jika (x> 0) melanjutkan;

Pembagi Umum Terbesar 43

pernyataan3; statement4;

} pernyataan5; Dalam hal ini, semua 100 iterasi loop berlangsung. Namun, jika selama beberapa
iterasi, setelah kami mengeksekusi pernyataan2, kami menemukan x untuk menahan kuantitas
positif, kami melewatkan kedua pernyataan3 dan pernyataan4. Pada titik ini kita meningkatkan
(karena pernyataan maju adalah ++). Jika a kurang dari 100, yang lain melewati loop dilakukan.

3.5 AnexhaustiveapproachtotheGCDproblem Sekarang saatnya untuk mengatasi masalah dari awal


bab ini. Biarkan pn menjadi probabilitas bahwa dua bilangan bulat, dipilih secara independen dan
seragam dari {1,2, ..., n}, relatif prima. Program berikut menghitung jumlah pasangan (a, b) dengan
1≤a, b≤n yang memenuhi gcd (a, b) = 1 dan membaginya dengan n2.

Program 3.9: Program untuk menghitung pn. 1 #include <iostream> 2 #include "gcd.h" 3
menggunakan namespace std; 4 5 / ** 6 * Temukan probabilitas bahwa dua bilangan bulat dalam {1,
..., n} relatif 7 * prima. 8 * / 9 10 int main () {11 panjang n; 12 cout << "Enter n ->"; 13 cin >> n; 14 15
hitungan panjang = 0; 16 17 untuk (panjang a = 1; a <= n; a ++) {18 untuk (panjang b = 1; b <= n; b ++)
{19 if (gcd (a, b) == 1) {20 count ++; 21} 22} 23} 24 25 cout << ganda (hitungan) / (ganda (n) * ganda
(n)) << endl; 26 kembali 0; 27}

44 C ++ untuk Matematikawan

Kode sangat mudah. Kami meminta pengguna untuk memasukkan n dan mengatur penghitung
(hitungan bernama) ke nol. Tindakan utama terjadi di garis 17-23. Dua bersarang untuk loop
menjalankan melalui semua kemungkinan nilai a dan b dengan 1≤a, b≤n. Pada setiap iterasi, jika a
dan b ditemukan relatif prima, penghitung meningkat satu. Pada akhirnya, kita membagi counter
dengan n2 untuk mendapatkan probabilitasnya. Karena kami ingin jawaban titik mengambang, kami
memberikan istilah yang tepat ke dalam tipe ganda (lihat baris 25).

Bagi program ini, Anda perlu file-file: gcd.h, gcd.cc, dan file ini (misalkan knalpot.cc).
WecaneitherloadallthreeintoanIDEor (onaUNIXsystem) menyimpan ketiganya dalam direktori dan
mengkompilasi dengan perintah seperti

g ++ gcd.cc exhaust.cc -o exhaust dan program disimpan dalam file bernama knalpot. (Lihat
Lampiran A untuk lebih jelasnya.) Berikut ini adalah menjalankan program secara khusus. Masukkan
n -> 100 0,6087 Jadi, p100 = 0,6087. Jika kita menjalankan program untuk berbagai nilai n, kami
menghasilkan hasil berikut. n pn 100 0,6087 500 0,608924 1000 0,608383 5000 0,608037 10000
0,60795 50000 0,607939 Tampaknya limn → ∞ pn ada dan konvergen ke nilai sekitar 0,6079. Ini
akan lebih mudah untuk dilakukan selanjutnya. Namun, thelastentryinthist membutuhkan waktu
yang lama bagi komputer saya untuk menghitung. Apakah ada metode yang lebih efisien? Ada
onemodestmodi fi kationwecanmaketotheprogram. Noticethatwearecomputing nilai yang sama dua
kali dalam banyak hal. Artinya, kami menghitung baik gcd (10,15) dan gcd (15,10). Kita dapat
membuat program kita dua kali lebih cepat dengan hanya menghitung satu dari ini. Juga, kita tidak
perlu repot menghitung gcd (a, a); satu-satunya contoh di mana gcd (a, a) sama dengan satu adalah
ketika a = 1. Ini adalah versi modifikasi dari program ini.

Program 3.10: Program yang sedikit lebih baik untuk menghitung pn. 1 #include <iostream> 2
#include "gcd.h" 3 menggunakan namespace std; 4 5 / ** 6 * Temukan probabilitas bahwa dua
bilangan bulat dalam {1, ..., n} relatif 7 * prima. 8 * /

Pembagi Umum Terbesar 45

9 10 int main () {11 long n; 12 cout << "Enter n ->"; 13 cin >> n; 14 15 hitungan panjang = 0; 16 17
untuk (panjang a = 1; a <= n; a ++) {18 untuk (panjang b = a + 1; b <= n; b ++) {19 if (gcd (a, b) == 1)
{20 hitung ++; 21} 22} 23} 24 count = 2 * hitung + 1; 25 26 cout << double (count) / (double (n) *
double (n)) << endl; 27 mengembalikan 0; 28}

Perhatikan bahwa loop kedua (garis 18) dimulai dengan b = a + 1, sehingga loop bagian dalam
berjalan dari + 1 hingga n. Dengan demikian program menghitung gcd (5,10), tetapi tidak gcd (10,5).
Kita perlu menghitung ganda di bagian akhir untuk memperbaiki. Juga, kami tidak menghitung gcd (t,
t) untuk t apapun, jadi kami menambahkan 1 ke (angka dua kali lipat) di bagian akhir untuk
mengoreksi untuk itu. Koreksi ini ada di baris 24.
Dalam bab-bab berikutnya, kami mendekati masalah menggunakan dua metode lain: algoritma acak
Monte Carlo (memberi kami kesempatan untuk belajar tentang bilangan acak dalam C ++) dan
knalpot yang lebih cerdas menggunakan Euler's totient ϕ (memberi kami kesempatan untuk belajar
tentang array ). Namun, sebelum kita beralih ke pendekatan-pendekatan lain tersebut, lakukanlah
gcd procedurebetterstillandusetanapelayananuntukmendapatkan fitur C ++ tambahan.

3.6 Extendedgcd, callbyreference, dan overloading TheEuclideanalgorith dapat digunakan tidak


hanya untuk menemukan router utama dari dua bilangan bulat a dan b, tetapi juga untuk
mengekspresikan gcd sebagai kombinasi bilangan bulat bilangan bulat a dan b. Yaitu, jika a dan b
adalah bilangan bulat (bukan keduanya nol) maka ada x, y∈Z seperti kapak + oleh = gcd (a, b). Tujuan
kami adalah prosedur yang bisa disebut seperti ini:

d = gcd (a, b, x, y); di mana a, b adalah angka-angka yang diinginkan oleh gcd kita, d adalah gcd (a, b),
dan x, y memiliki properti yang d = axe + oleh. Pada pandangan pertama ini tampaknya mustahil
karena dua alasan. Pertama, kita sudah memiliki prosedur bernama gcd. Tidakkah seharusnya kita
beri nama ini

46 C ++ untuk Matematikawan

lain (seperti extended_gcd)? Kedua, kita dapat memberikan nilai ke prosedur dalam daftar
argumennya, tetapi prosedur tidak dapat mengubah nilai argumen. Fakta ini dikenal sebagai
panggilan berdasarkan nilai dan telah dibahas sebelumnya dalam bab ini (lihat Bagian 3.2). Kabar
baiknya adalah bahwa tidak satu pun dari ini merupakan rintangan yang signifikan. Pertama,
prosedur yang berbeda dua kali mungkin memiliki nama yang sama, tetapi sekarang ada: Prosedur
harus memiliki jenis argumen yang berbeda. Misalnya, kita ingin membuat prosedur untuk (a)
menemukan kemiringan segmen garis dari titik asal ke titik tertentu dan (b) menemukan kemiringan
garis yang menghubungkan dua titik yang diberikan. Dalam beberapa bahasa pemrograman, Anda
akan diminta untuk memberikan nama-nama yang berbeda ini; di C ++, kami dapat menamai
keduanya lereng. Deklarasi untuk prosedur ini (yang kita masukkan dalam header file bernama,
katakanlah, slope.h) adalah ini. kemiringan ganda (double x, double y); kemiringan ganda (double x1,
double y1, double x2, double y2); Yang pertama adalah untuk versi asal-ke-kemiringan prosedur dan
yang kedua untuk versi point-to-point. Karena yang pertama mengambil dua argumen ganda dan
yang kedua mengambil empat argumen ganda, kompiler C ++ mengakui ini sebagai prosedur yang
berbeda. Gejala-gejala dari prosedur-prosedur yang ada (yang ditandai dengan enam tiang kaca)
terlihat seperti ini: kemiringan ganda (double x, double y) {return y / x; }

kemiringan ganda (double x1, double y1, double x2, double y2) {return (y1-y2) / (x1-x2); } Ketika
kami menggunakan nama yang sama untuk prosedur yang berbeda, prosedur ini harus terkait erat
dan melayani tujuan yang sama. Kami ahli matematika sering menggunakan simbol yang sama untuk
dua objek yang terkait erat (tetapi berbeda). Sebagai contoh, jika kita memiliki fungsi f: R → R, maka
f (x) hanya masuk akal ketika x adalah bilangan real. Namun, kami sering memperpanjang notasi ini.
Jika A⊆R, maka f (A) (sama f) berarti {f (x): x∈A}. Dalam C ++, kami mengacu pada kemampuan ini
untuk menamai prosedur yang berbeda dengan nama yang sama dengan overloading. Kedua,
memang benar bahwa C ++ melewatkan data ke prosedur menggunakan panggilan berdasarkan
nilai; ini berarti bahwa kacamata hitam yang digunakan untuk membuat prosedur asli, dan prosedur
yang tidak dapat diubah tidak dapat mengubah nilai aslinya. Misalnya, pertimbangkan prosedur ini.
membatalkan swap (long a, long b) {tmp panjang;
tmp = a; a = b; b = tmp; } Jenis kembalian prosedur tidak berlaku. Ini berarti bahwa prosedur tidak
mengembalikan nilai apa pun. Kode dalam swap mengambil nilai-nilai yang disimpan di a dan b dan
menukarkannya.

Pembagi Umum Terbesar 47

Jika, ketika prosedur ini disebut pegangan 5 dan b memegang 17, maka pada akhirnya, memegang
17 dan b memegang 5. Namun, prosedur ini tidak berpengaruh apapun pada prosedur panggilan.
Misalkan prosedur panggilan berisi kode ini:

panjang a = 5; panjang b = 17; tukar (a, b); cout << "a =" << a << endl; cout << "b =" << b << endl;
Output dari ini adalah sebagai berikut. a = 5 b = 17 Ada tempat untuk menyimpan argumen swap
sothatitis dapat digunakan untuk memodifikasi. Alih-alihmenghindari nilai dari sebuah
totheprocedure, wepassareferenceto a. Untuk melakukan ini, kita tambahkan karakter & ke akhir
nama jenis, seperti:

membatalkan swap (panjang & a, panjang & b) {tmp panjang; tmp = a; a = b; b = tmp; } The &
melakukan ini: Argumen a adalah referensi ke variabel tipe panjang. Tidak hanya tipe panjang, itu
lebih dari satu salinan yang panjang. Sintaksis ini berarti: alih-alih mengirim salinan ke prosedur ini,
kirim variabel itu sendiri (dan bukan klon). Apabilaprodusennya memberikan pilihan yang berbeda
untuk semua, itu harus dilakukan terlebih dahulu dan bukan salinannya. Dengan prosedur swap yang
ditulis ulang, kode

panjang a = 5; panjang b = 17; tukar (a, b); cout << "a =" << a << endl; cout << "b =" << b << endl;
menghasilkan output ini: a = 17 b = 5 Panggilan dengan referensi adalah mekanisme yang kita
butuhkan sehingga prosedur gcd yang baru dapat memberikan tiga jawaban: gcd dikembalikan
melalui pernyataan kembali dan nilai x dan y. Deklarasi untuk prosedur (yang kami tambahkan ke
gcd.h) terlihat seperti ini:

long gcd (long a, long b, long & x, long & y); Pendekatan rekursif untuk memecahkan masalah gcd
yang diperpanjang bekerja dengan baik. Misalkan
wewanttosolvetheextendedgcdproblemforpositivebilangan bulatsaandb. Westartby

48 C ++ untuk Matematikawan

memecahkan masalah gcd yang diperpanjang untuk b dan c di mana c = a mod b. Katakanlah kita
memiliki solusi itu jadi d = gcd (b, c) = bx0 + cy0 untuk beberapa bilangan bulat x0, y0. Kita tahu
bahwa c adalah sisanya ketika kita membagi a oleh b; yaitu, a = qb + c di mana 0≤c <b. Menulis c = a
− qb kita memiliki d = bx0 + cy0 = bx0 + (a − qb) y0 = ay0 + b (x0 − qy0) = ax + + di mana x = y0 dan y =
x0 − qy0. Ide-ide ini membentuk jantung rekursi. Untuk membuat prosedur, kita perlu memeriksa
kasus-kasus khusus (a atau b mungkin nol atau negatif). Kode berikut.

Program 3.11: Kode untuk prosedur gcd yang diperpanjang. 1 gcd panjang (panjang, panjang b,
panjang & x, panjang & y) {2 panjang d; // tempat untuk menyimpan gcd akhir 3 4 // jika b = 0, kita
memiliki d = | a |, x = 1 atau -1, y arbitrary (katakanlah, 0) 5 jika (b == 0) {6 jika (a <0) {7 d = -a; 8 x = -
1; 9 y = 0; 10} 11 lagi {12 d = a; 13 x = 1; 14 y = 0; 15} 16 kembali d; 17} 18 19 // Jika b negatif, berikut
adalah solusi 20 jika (b <0) {21 d = gcd (a, -b, x, y); 22 y = -y; 23 kembali d; 24} 25 26 // jika negatif,
berikut adalah solusi 27 jika (a <0) {28 d = gcd (-a, b, x, y); 29 x = -x; 30 kembali d; 31}
Pembagi Umum Terbesar 49

32 33 // atur rekursi 34 panjang aa = b; 35 bb panjang = a% b; 36 panjang qq = a / b; 37 xx panjang,


yy; 38 39 d = gcd (aa, bb, xx, yy); 40 41 x = yy; 42 y = xx - qq * yy; 43 44 kembali d; 45}

3.7 Latihan 3.1 Gunakan algoritma Euclidean untuk menemukan d = gcd (51,289) dan untuk
menemukan bilangan bulat x dan y sehingga d = 51x + 289y. 3.2 Pada halaman 39 kami
mempresentasikan sebagian besar prosedur yang benar berikut untuk faktorial komputasi. faktorial
panjang (panjang n) {if (n == 0) mengembalikan 1; kembali n * faktorial (n-1); }

Prosedur ini, bagaimanapun, masih memiliki bug. Apa itu? 3.3 Tulis prosedur untuk menghitung
tanda argumennya (fungsi signum). Artinya, prosedur Anda harus menghitung sgnx = +1 jika
x> 0, 0 jika x = 0, dan −1 jika x <0. 3.4 Tulis dua prosedur untuk menghasilkan angka Fibonacci n
ketika angka n adalah diberikan sebagai masukan. Dalam satu prosedur, gunakan for loop dan
hasilkan jawaban menggunakan iterasi. Di versi kedua, gunakan rekursi. Argumen input dan return
value harus bertipe panjang. Gunakan definisi nomor Fibonacci berikut: F0 = F1 = 1, dan Fn = Fn − 1 +
Fn − 2 untuk semua n≥2. Minta proksi Anda untuk membatalkan −1 jika argumen yang digunakan
tidak valid. Jangan khawatir tentang kelebihan arus.

50 C ++ untuk Matematikawan

3.5 TuliskanyangtertamaFibonaksesuaidikembalikandalamPelatihan3.4. Gunakan untuk


mengevaluasi F20, F30, dan F40. Anda harus menemukan bahwa satu versi jauh lebih cepat daripada
yang lain. Jelaskan mengapa. Masukkan dalam penjelasan Anda analisis tentang berapa kali versi
rekursif dari prosedur Anda dipanggil untuk menghitung Fn. 3.6 Tuliskan dua prosedur untuk
dihitung

f (N) =

N∑k=1

1 k2. Perhatikan bahwa untuk N besar, pendekatan iniζ (2) = π2 / 6. Prosedur pertama harus
menghitung jumlah dalam urutan biasa

1+

14

19

+ ••• +

1 N2 dan yang kedua harus menghitung jumlah dalam urutan terbalik 1 N2 + 1 (N − 1) 2 + ••• + 1 4
+1. Dalam kedua kasus, gunakan variabel float untuk semua nilai nyata. Evaluasilah jumlah ini untuk
N = 106 dan laporkan yang memberikan hasil yang lebih baik. (π2 / 6≈1.6449340668482264365.)
Jelaskan. 3.7 Tulis prosedur untuk mengkonversi antara koordinat persegi panjang dan kutub
bernama xy2polar dan polar2xy. Meminjam xy2polar (x, y, r, t); harus taketherectangularcoordinates
(x, y) andsavetheresultingpolarcoordinates; (r, θ) masing-masing adalah r dan t. Polar2xy
theprocedure (r, t, x, y); harus memiliki efek sebaliknya. Pastikan untuk menangani asal dengan cara
yang masuk akal. Tentu saja, Anda perlu fungsi trigonometri untuk menyelesaikan tugas ini; lihat
Apendiks C

(terutama Apendiks C.6.1) di mana Anda dapat menemukan fungsi yang bermanfaat seperti atan2.
3.8 Everypositiveinteghas amultiple yang, ketika diekspresikan dalam basis sepuluh, terdiri
sepenuhnya dari nol dan satu. Tulis prosedur untuk mencari kelipatan n paling sedikit, parameter
input, dari formulir yang diperlukan. Peringatan: Jika tipe1 panjang panjang tersedia di komputer
Anda, gunakan. Yang paling sedikit dari bentuk yang diperlukan mungkin jauh lebih besar dari n.
Sebagai contoh, berapa kelipatan paling sedikit dari 9 yang hanya mengandung nol dan satu? Berapa
kelipatannya yang paling sedikit dari 99? Rancang prosedur Anda untuk mendeteksi arus berlebih
dan kembali −1 jika tidak dapat menemukan beberapa yang diperlukan.

1Atau int64.

Pembagi Umum Terbesar 51

3.9 Tulis prosedur untuk menemukan gcd tiga bilangan bulat formulir

panjang gcd (panjang, panjang b, panjang c);

dan versi perpanjangan formulir

panjang gcd (panjang, panjang b, panjang c, panjang & x, panjang & y, panjang & z);

thatreturnsd = gcd (a, b, c) danpopulatesthelastthreeargumentsdengan nilai x, y, z seperti itu kapak +


oleh + cz = d. Anda dapat menggunakan prosedur gcd yang dikembangkan dalam bab ini sebagai
bagian dari solusi Anda

Anda mungkin juga menyukai