2. Eukleideen algoritmi 2.1 Suurimman yhteisen tekijän tehokas laskutapa Tässä luvussa tarkastellaan annettujen lukujen suurimman yhteisen tekijän etsimistä tehokkaalla tavalla. Erinomaisen käyttökelpoinen menetelmä on Eukleideen algoritmi, joka on ikivanha, mutta ei vähääkään vanhanaikainen laskutapa. Nykyisen tietotekniikan aikakautena sen merkitys sovelluksissa on vain entisestään kasvanut. Olkoot a ja b positiivisia kokonaislukuja ja a b. Kirjoitetaan luku a jakoalgoritmin mukaiseen muotoon a = q b + r, 0 r < b, (ks. Lause 1.1). Jokainen lukujen a ja b yhteinen tekijä, sanokaamme t, on luvun b ja jakojäännöksen r = a - q b yhteinen tekijä ja myös päinvastoin. Todellakin, jos a = t a' ja b = t b', missä t, a', b' Z +, niin (2.1) r = a - q b = (t a') q (t b') = t (a' q b'), ts. t on luvun r tekijä ja oletuksen mukaan myös luvun b tekijä. Toisaalta, jos b = t b' ja r = t r', missä t, b', r' Z +, niin (2.2) a = q b + r = q (t b') + (t r') = t (q b' + r'), ts. t on luvun a tekijä ja oletuksen mukaan myös luvun b tekijä. Merkitään d = syt(a, b). Edellä nähdyn nojalla d b ja d r, joten d syt(b, r). Vastaavasti, kuten kohdassa (2.2) nähtiin, syt(b, r) syt(a, b), joten d = syt(a, b) = syt(b, r), kun a = q b + r, 0 r < b. Jos r = 0 (ja siis a = q b), saadaan tulos syt(a, b) = b. Olkoon r 0. Jatketaan nyt samalla tavalla lukujen b ja r kanssa. Kirjoitetaan siis a = q 1 b + r 1, b = q 2 r 1 + r 2, missä 0 r 2 < r 1. Vastaavasti kuten edellä nähdään, että syt(a, b) = syt(b, r 1 ) = syt(r 1, r 2 ). Jatketaan menettelyä kunnes toinen argumentti r n-1 on ensimmäisen argumentin r n-2 tekijä (näin käy lopulta, koska r 1 > r 2 > r 3 > > r n-2 > r n-1 > r n = 0). Luku r n-1 on täsmälleen etsitty lukujen a ja b suurin yhteinen tekijä, ts. r n-1 = syt(a, b). Tämä algoritmi on nimeltään Eukleideen algoritmi (Euclid's Algorithm) ja se on erittäin nopea tapa laskea syt(a, b). Huomattavaa on, että syt(a, b) löytyy ilman lukujen a ja b tekijöihinjakoa (jota suurten lukujen kyseessä ollen voi käytännössä olla mahdotonta löytää, ks. RSA-haasteluvut vuoteen 2007 saakka käytä netissä hakusanaa: The RSA Challenge Numbers).
Salakirjoitus 2 Algoritmi 2.1 Yksinkertainen Eukleideen algoritmi syöte while b > 0 do tulosta a a, b positiivisia kokonaislukuja, a b begin aseta r = jakojäännös, kun a on jaettu luvulla b (siis r saadaan jakoyhtälöstä a = q b + r, 0 r < b) aseta a = b aseta b = r end Käyttämällä Mathematica-funktioita While, Floor ja Print, yllä esitetty algoritmi voidaan ohjelmoida seuraavasti. Viimeinen nollasta eroava jakojäännös on sama kuin Algoritmin 2.1 lopussa tulostama luku a, joka puolestaan on syötteenä annettujen lukujen suurin yhteinen tekijä: a = 2345; b = 987; While[b 0, r = a - Floor[a / b] * b; {a, b} = {b, r}; Print[{a, b}]] {987, 371} {371, 245} {245, 126} {126, 119} {119, 7} {7, 0} Tämä algoritmi voitaisiin implementoida myös näin: Clear[f]; f[{a_, b_}] := {b, r = a - Floor[a / b] * b}; NestWhileList[f, {2345, 987}, Last[#] 0 &] {{2345, 987}, {987, 371}, {371, 245}, {245, 126}, {126, 119}, {119, 7}, {7, 0}} Tarkastellaan vielä kahta yksityiskohtaista esimerkkiä. Esimerkki 2.1 Laske lukujen 129 ja 15 suurin yhteinen tekijä.
Salakirjoitus 3 Ratkaisu. Käytetään jakoyhtälöä a = q b + r, 0 r < b, toistuvasti. Lisäksi syt(a, b) = syt(b, r) = syt(r, r') =.... Siispä Eukleideen algoritmin toiminta näyttää seuraavalta: 129 = 8 * 15 + 9 syt (129, 15) = syt (15, 9) 15 = 1 * 9 + 6 syt (15, 9) = syt (9, 6) 9 = 1 * 6 + 3 syt (9, 6) = syt (6, 3) 6 = 2 * 3 + 0 syt (6, 3) = syt (3, 0) = 3 Viimeinen nollasta eroava jakojäännös = 3 = syt(129, 15). Esimerkki 2.2 Laske lukujen 2345 ja 123 suurin yhteinen tekijä. Ratkaisu. Tässä saadaan tulokseksi syt = 1. Tämä tulos löydetään Eukleideen algoritmilla seuraavasti: 2345 = 19 * 123 + 8 123 = 15 * 8 + 3 8 = 2 * 3 + 2 3 = 1 * 2 + 1 2 = 2 * 1 + 0 Viimeinen nollasta eroava jakojäännös = 1 = syt(2345, 123). Harjoituksia 11 (a) Määritä d = syt(1233, 63). Välivaiheet tulee merkitä selkeästi näkyviin Tätä harjoitusta jatketaan vielä seuraavassa kohdassa 2.2 (esitä d muodossa d = s 1233 + t 63, missä s, t Z). 12 (a) Määritä syt(2333, 187). Tätä harjoitusta jatketaan vielä seuraavassa kohdassa 2.2 (esitä d muodossa d = s 2333 + t 187, missä s, t Z). 2.2 Lineaarikombinaatio syt(a, b) = u a + v b Salakirjoituksissa lasketaan usein hyvin suurten lukujen, sanokaamme a, jakojäännöksillä - modulo jokin luku b. Tällaisten suurten lukujen a käänteisluvuilla (modulo b) on ensiarvoisen keskeinen merkitys, kuten tullaan näkemään. Lauseen 1.4 mukaan on olemassa kertoimet u ja v Z joiden avulla syt(a, b) voidaan esittää muodossa syt(a, b) = u a + v b. Siinä tapauksessa, että syt(a, b) = 1, kuten oli edellä Esimerkissä 2.2, tälle lineaarikom binaatiolle pätee u a + v b = 1. Tällöin saadaan u a = ( v) b + 1, ts. u a 1 (modulo b) ja siis u = a -1 (modulo b). Näin ollen lineaarikombinaatiota 1 = syt(a, b) = u a + v b voidaan suoraan käyttää hyväksi käänteisalkioiden
Salakirjoitus 4 a -1 (modulo b) laskemisessa. Edellä esiintynyt merkintä x y (mod m) tarkoittaa, että m jakaa kokonaislukujen x ja y erotuksen x - y, ts. x - y = q m, eräälle q Z. Tällöin sanotaan, että x ja y ovat keskenään kongruentteja modulo m. Kongruensseja käsitellään laajemmin Kappaleessa 3. Kun Lauseen 1.4 mukaiset kertoimet u ja v halutaan löytää, ts. esittää syt(a, b) muodossa syt(a, b) = u a + v b, voidaan Algoritmia 2.1 muuntaa alla esitetyllä tavalla. Huomaa, että jättämällä pois symboleja u i ja v i sisältävät rivit, tämä (laajennettu) algoritmi palautuu yksinkertaiseen Eukleideen algoritmiin, jossa syt(a, b) = syt(r -1, r 0 ) = syt(r 0, r 1 ) = syt(r 1, r 2 ) = = syt(r n-1, r n ) = syt(r n-1, 0) = r n-1. Algoritmin 2.2 oikeellisuuden todistus löytyy Liitteestä 1, jossa esitetään yksityiskohtaiseisesti tähän suoraviivaiseen tapaan johtavat tarkastelut. Liitteessä 1 nähdään lisäksi, että jakoalgoritmin tuottama jakojäännös r i voidaan esittää jokaisella askeleella muodossa u i a + v i b. Viimeisen jakojäännöksen r n ollessa nolla, on viimeinen nollasta eroava jakojäännös r n-1 juuri etsitty lineaarikombinaatio d = syt(a, b) = r n-1 = u a + v b. Algoritmi todistetaan matemaattisella induktiolla ja siitä esitetään käsinlaskuesimerkkejä. Liitteestä 1 löytyvät myös perustelut matriisien kertolaskun käytölle lineaarikombinaation laskemisessa. Lisäksi liitteestä 1 löytyy kaikki välivaiheet esittävä Mathematica-ohjelma, jota Mathematica-ympäristön käyttäjät voivat kokeilla. Tuon ohjelman voi myös muuttaa jossakin toisessa ohjelmointiympäristössä toimivaksi koodiksi. Algoritmi 2.2 Laajennettu Eukleideen algoritmi syöte a b > 0 alkuarvot r -1 = a; r 0 = b; u -1 = 1; u 0 = 0; v -1 = 0; v 0 = 1; n = 0; while r n > 0 do begin aseta n = n + 1; tulosta r n-2 = q n r n-1 + r n, r n-1 > r n 0; aseta u n = u n-2 - q n u n-1 ; aseta v n = v n-2 - q n v n-1 ; end aseta u = u n-1 ; v = v n-1 ; (2.2) syt(a, b) = r n-1 = u a + v b Todistus: Katso Liite 1 (Laajennettu Eukleideen algoritmi suoraviivainen tapa). Vaikka lukijaa kannustetaankin perehtymään Liitteeseen 1, emme vaadi tällä salakirjoitusten peruskurssilla siinä esitettyjä perusteluja hallittavaksi loppukokeessa. Esitämme nyt kuitenkin yhden esimerkin tuossa liitteessä kirjoitetun laajennettueukleides-ohjelman toiminnasta (vrt. Esimerkki 2.1 yllä):
Salakirjoitus 5 laajennettueukleides[129, 15] Laajennettu Eukleideen algoritmi toimii seuraavasti: a = 129, b = 15 r -1 =129=q 1 *r 0 +r 1 =8*15+9; r 1 =9=u 1 a +v 1 b = (1)*129 + (-8)*15 r 0 =15=q 2 *r 1 +r 2 =1*9+6; r 2 =6=u 2 a +v 2 b = (-1)*129 + (9)*15 r 1 =9=q 3 *r 2 +r 3 =1*6+3; r 3 =3=u 3 a +v 3 b = (2)*129 + (-17)*15 r 2 =6=q 4 *r 3 +r 4 =2*3+0; r 4 =0=u 4 a +v 4 b = (-5)*129 + (43)*15 Viimeinen nollasta eroava jakojäännös = 3 = syt(129, 15). Lineaarikombinaatio: 3 = u a + v b = (2)*129 + (-17)*15. Lasketaan tämä lineaarikombinaatio nyt käsin toisella, aluksi ehkä helpommalla, tavalla. Ainakin tämä alhaalta ylöspäin etenevä laskutapa tulee hallita. Siis jompikumpi tapa kuuluu kurssin keskeisiin vaatimuksiin. Tavan - suoraviivainen ylhäältä alas (yllä), tai alhaalta ylös (alla) - voi valita itse sen mukaan kumpi tuntuu luontevalta. Esimerkki 2.3 Laske lukujen 129 ja 15 suurin yhteinen tekijä ja esitä se muodossa u*129 + v*15. Esimerkissä 2.1 laskimme jo, että viimeinen nollasta eroava jakojäännös = 3 = syt(129, 15). Toistamme tässä vielä nämä laskut: (rivi 1) 129 = 8 * 15 + 9 (rivi 2) 15 = 1 * 9 + 6 (rivi 3) 9 = 1 * 6 + 3 (rivi 4) 6 = 2 * 3 + 0 Lähdemme nyt liikkeelle riviltä 3, jossa on viimeinen nollasta eroava jakojäännös (syt = 3). Saadaan: 3 = 9-1*6 (rivi 3) Nyt riviltä 2 saadaan ratkaistuksi edeltävä jakojäännös 6 = 15-1*9. Sijoittamalla tämä laskematon lauseke (15-1*9) luvun 6 paikalle yllä, saadaan: 3 = 9-1*6 (rivi 3) = 9-1*(15-1*9) (rivi 2) = -1*15 + 2*9 (sievennys) Riviltä 1 saadaan jakojäännös 9 = 129-8*15. Sijoittamalla lauseke (129-8*15) luvun 9 paikalle edellä
Salakirjoitus 6 saatuun viimeiseen muotoon, saadaan: 3 = 9-1*6 (rivi 3) = 9-1*(15-1*9) (rivi 2) = -1*15 + 2*9 (sievennys) = -1*15 + 2*(129-8*15) (rivi 1) = 2*129 + (-17)*15 (sievennys) Siis kysytty lineaarikombinaatio on 3 = syt(129, 15) = u a + v b = 2*129 + (-17)*15. Huomattakoon vielä, että yllä sievennys tarkoitti sitä, että kootaan yhteen (so. esitetään yhden kertoimen avulla) jatkossa ylemmiltä riveiltä löytyvät jakojäännökset (3, 6, 9) tai luvut b ja a (15, 129). Esitettyjä kertolaskuja ei tule laskea loppuun, koska silloin menetettäisiin lausekkeiden rakenne. Laskemisen kannalta näitä lukuja käsitellään siis symboleina tai merkkijonoina. Samaa ajattelutapaa kannattaa käytää myös, jos kirjoittaa tämän algoritmin ohjelman muotoon symbolisen laskennan ohjelmointiympäristössä. Esimerkki 2.4 Laske lukujen 2345 ja 123 suurin yhteinen tekijä ja esitä se muodossa u*2345 + v*123. Esimerkissä 2.2 laskimme jo, että viimeinen nollasta eroava jakojäännös = 1 = syt(2345, 123). Toistamme vielä nämä laskut: (rivi 1) 2345 = 19 * 123 + 8 (rivi 2) 123 = 15 * 8 + 3 (rivi 3) 8 = 2 * 3 + 2 (rivi 4) 3 = 1 * 2 + 1 (rivi 5) 2 = 2 * 1 + 0 Laskemme nyt kysytyn lineaarikombinaation seuraavasti: 1 = 3-1*2 (rivi 4) = 3-1*(8-2*3) (rivi 3) = -1*8 + 3*3 (sievennys) = -1*8 + 3*(123-15*8) (rivi 2) = 3*123-46*8 (sievennys) = 3*123-46*(2345-19*123) (rivi 1) = -46*2345 + (3 + (-46)(-19))*123 (sievennys) = -46*2345 + 877*123 (sievennys) Siis kysytty lineaarikombinaatio on 1 = syt(2345, 123) = u a + v b = (-46)*2345 + 877*123. Mathematicassa tämä laajennettu Eukleideen algoritmi on standardi funktio ExtendedGCD. Lukujen suuruusjärjestyksestä käyttäjän ei tarvitse huolehtia: a = 963; b = 4320; ExtendedGCD[a, b] {9, {-157, 35}}
Salakirjoitus 7 Todellakin 9 = syt(963, 4320) = 157*963 + 35*4320. Harjoituksia 11 Määritä syt(1233, 63) (ks. T11(a)) ja esitä se muodossa s 1233 + t 63, missä s, t Z. Välivaiheet tulee merkitä selkeästi näkyviin. Huom. syt(1233, 63) laskettiin aiemmin tehtävässä 11(a). 12 Määritä syt(2333, 187) ja esitä se muodossa s 2333 + t 187, missä s, t Z. Huom. syt(2333, 187) laskettiin aiemmin tehtävässä 12(a). 12' Lisäharjoitus. Valitse itse luvut a ja b. Laske valitsemillasi luvuilla d = syt(a, b) ja esitä d muodossa d = s a + t b, missä s, t Z. 2.3 Eukleideen algoritmin kompleksisuus Päätetään tämä kappale Eukleideen algoritmin kompleksisuuden tarkasteluun. Olkoon a > b 1. Verrataan algoritmin toiminnan nopeutta luvun b arvoon. Algoritmin toiminta on hitaimmillaan silloin, kun jakojäännös r k pienenee kussakin askeleessa r k-2 = q k r k-1 + r k mahdollisimman vähän. Tällaiseen tilanteeseen joudutaan, jos r k-2 = r k-1 + r k aina kun 2 k n - 1, ts. jos jokaisella askeleella (ensimmäistä askelta a = q 1 b + r 1 lukuunottamatta) osamäärä q k saa arvon 1 edellyttäen, että se on ylipäätään mahdollista. Tällöin on myös voimassa r n-2 = 2 r n-1, kun r n = 0. Toisin sanoen, sellainen luvun a pienin arvo (a > b 1), jolle syt(a, b):n laskeminen vie n - 2 askelta (so. riviä) saadaan, kun a = F n ja b = F n-1, missä jono (F i ) i 0 on kuuluisa Fibonacci-lukujono, joka määritellään asettamalla F 0 = 0, F 1 = 1 ja F i+2 = F i+1 + F i aina kun i 0. Jonon alku on siis: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89,.... Alla Mathematican toiminta kohdistuu toistuvasti listoihin, jotka koostuvat kahdesta peräkkäisestä Fibonacci-luvusta (tässä käytetään Nest-funktiota). Näin saadaan menetelmä Fibonacci-lukujen laskemiseksi. Tässä esimerkissä lasketaan F 99 ja F 100, jotka ovat jo hyvin suuria lukuja: f[{u_, v_}] := {v, u + v} n = 99; Nest[f, {0, 1}, n] {218 922 995 834 555 169 026, 354 224 848 179 261 915 075} Voidaan käyttää myös Mathematican sisäänrakennettua funktiota: Fibonacci. Fibonacci[100] 354 224 848 179 261 915 075 Tarkastellaan vielä yllä esitettyä analyysiä kirjoittamalla Mathematicalla funktio sytiteroinnit. Siinä käytetään jakojäännöksen laskemiseen valmista funktiota Mod:
Salakirjoitus 8 sytiteroinnit[n_integer?positive, m_integer?positive] := Module[ { a = n, b = m, r, t = 0}, While[ b > 0, r = Mod[a, b]; {a, b, t} = {b, r, t + 1}]; t] n = 100; sytiteroinnit[fibonacci[n], Fibonacci[n - 1]] 98 Siis tosiaankin syt(a, b):n laskeminen vie tapauksessa n = 100 juuri nuo aiemmin mainitut n - 2 askelta, kun a = F n ja b = F n-1. Kokeillaan vielä tapausta n = 10. Tässäkin tapauksessa Eukleideen algoritmin toiminta päättyy 8 askeleessa, ts. tarvitaan 8 riviä. Tässä Fibonacci-luvut ovat seuraavat: {Fibonacci[10], Fibonacci[9]} {55, 34} Eukleideen algoritmi päättyy rivillä numero 8: (rivi 1) 55 = 1 * 34 + 21 (rivi 2) 34 = 1 * 21 + 13 (rivi 3) 21 = 1 * 13 + 8 (rivi 4) 13 = 1 * 8 + 5 (rivi 5) 8 = 1 * 5 + 3 (rivi 6) 5 = 1 * 3 + 2 (rivi 7) 3 = 1 * 2 + 1 (rivi 8) 2 = 2 * 1 + 0 Asettamalla F n = c f n Fibonacci-lukujen määrittelyrelaatiossa F i+2 = F i+1 + F i, saadaaan f 2 = f + 1, jonka nollakohdat ovat: 1± 5 2. Esitetään ilman todistusta seuraava yläraja Eukleideen algoritmin kompleksisuudelle. Se voidaan todistaa induktiolla luvun a suhteen (erottelemalla tapaukset b a f Lause 2.3 Eukleideen algoritmin kompleksisuus ja a f < b < a). Olkoot a ja b positiivisia kokonaislukuja, a > b 1 ja olkoon f = 1 + 5. Tällöin 2 Eukleideen algoritmin tarvitsema iteraatioiden lukumäärä syt(a, b):n laskemiseksi on korkeintaan 1 + log f b. Testataan tätä vielä yhdellä suhteellisen suurella luvun b arvolla:
Salakirjoitus 9 a = Fibonacci[1000]; b = Fibonacci[999]; sytiteroinnit[a, b] Ceiling[Log[(1 + Sqrt[5]) / 2, b]] 998 998 Ja vielä yhdellä suhteellisen pienellä luvun b arvolla: a = Fibonacci[1000]; b = 100; sytiteroinnit[a, b] Ceiling[Log[(1 + Sqrt[5]) / 2, b]] 3 10 Alkuperäisessä Mathematica Notebookissa on mukana automaattisesti alustettavia laskentakoodeja, joista alla on esimerkkinä funktio Eukleides. Tässä etsitään Fibonacci-lukujen suurinta yhteistä tekijää:
Salakirjoitus 10 Eukleides[ a = Fibonacci[30], b = Fibonacci[29] ] Tässä syt = 1. Tulos nähdään Eukleideen algoritmilla seuraavasti: 832 040 = 1 * 514 229 + 317 811 514 229 = 1 * 317 811 + 196 418 317 811 = 1 * 196 418 + 121 393 196 418 = 1 * 121 393 + 75 025 121 393 = 1 * 75 025 + 46 368 75 025 = 1 * 46 368 + 28 657 46 368 = 1 * 28 657 + 17 711 28 657 = 1 * 17 711 + 10 946 17 711 = 1 * 10 946 + 6765 10 946 = 1 * 6765 + 4181 6765 = 1 * 4181 + 2584 4181 = 1 * 2584 + 1597 2584 = 1 * 1597 + 987 1597 = 1 * 987 + 610 987 = 1 * 610 + 377 610 = 1 * 377 + 233 377 = 1 * 233 + 144 233 = 1 * 144 + 89 144 = 1 * 89 + 55 89 = 1 * 55 + 34 55 = 1 * 34 + 21 34 = 1 * 21 + 13 21 = 1 * 13 + 8 13 = 1 * 8 + 5 8 = 1 * 5 + 3 5 = 1 * 3 + 2 3 = 1 * 2 + 1 2 = 2 * 1 + 0 Viimeinen nollasta eroava jakojäännös = 1 = syt(832 040, 514 229). Käytännössä Eukleideen algoritmi toimii yleensä paljon nopeammin, kuin mitä Lauseen 2.3 yläraja antaisi aiheen odottaa.