Łańcuchy znakowe
Transkrypt
Łańcuchy znakowe
Łańcuchy znakowe Martyna Stańczyk Katarzyna Więckiewicz Płock, 17 kwietnia 2014 Wyszukiwanie palindromów Przez palindrom (ang. palindrome) rozumiemy łańcuch znakowy s, który czyta się tak samo w obu kierunkach. Przykład: ABBCBBA – jest palindromem ABBCABA – nie jest palindromem Palindromy pojawiają się w genetyce (łańcuchy DNA, RNA), w tekstach, muzyce, matematyce, geometrii, fizyce itd. Stąd duże zainteresowanie informatyków w efektywnych algorytmach ich znajdowania. W badaniach genetycznych często szuka się tzw. przybliżonych palindromów (ang. aproximate palindromes), tzn. palindromów, w których do k-znaków może być błędnych, czyli nie pasujących do dokładnego palindromu (ang. exact palindrome). Takie palindromy występują w łańcuchach DNA, w których wystąpiły różnego rodzaju błędy genetyczne. Problemem palindromów przybliżonych nie zajmujemy się w tym opracowaniu. Wyszukiwanie palindromów Wprowadźmy symbol sR, który oznacza łańcuch znakowy o odwróconej kolejności znaków w stosunku do łańcucha s. Przykład: s = ABCD sR = DCBA Łańcuch s jest palindromem, jeśli da się rozłożyć na dwa podłańcuchy w i wR wg poniższego schematu: s = wwR – palindrom parzysty (ang. even palindrome), lub s = wXwR – palindrom nieparzysty (ang. odd palindrome), gdzie X jest dowolnym symbolem alfabetu. Przykład: ABCDDCBA – palindrom parzysty → ABCD + DCBA ABCDADCBA – palindrom nieparzysty → ABCD + A + DCBA Zauważ, iż zgodnie z tą definicją palindromem jest każdy łańcuch pusty – rozkłada się na dwa puste podłańcuchy – oraz każdy łańcuch jednoliterowy – rozkłada się na znak X i dwa puste podłańcuchy. Ponieważ są to przypadki trywialne, w poniższym zadaniu wprowadzono zastrzeżenie, iż wyszukiwane palindromy muszą być co najmniej dwuznakowe. Wyszukiwanie palindromów Problem: W łańcuchu s znaleźć wszystkie palindromy o długości większej od 1. Rozwiązanie nr 1 Pierwszy algorytm wyszukiwania palindromów jest algorytmem naiwnym. Rozkłada on dany łańcuch znakowy s na wszystkie możliwe podłańcuchy p o długości nie mniejszej niż 2 znaki i sprawdza następnie, czy dadzą się przedstawić w postaci wwR lub wXwR. Sprawdzenie polega na porównywaniu znaków od początku i od końca podłańcucha. W tym celu wykorzystuje się dwa indeksy. Jeden z nich ustawia się na pierwszym znaku podłańcucha p, a drugi na ostatnim. Następnie porównujemy wskazywane przez te indeksy znaki podłańcucha p. Jeśli znaki są różne, to podłańcuch p nie jest palindromem. Jeśli porównywane znaki są równe, to indeksy przesuwamy – lewy w prawo, a prawy w lewo. Jeśli indeksy się miną, to zachodzi jedna z dwóch równości: p = wwR, lub p = wXwR W takim przypadku p jest palindromem. Wyszukanie wszystkich palindromów zawartych w łańcuchu s proponowaną metodą posiada sześcienną klasę złożoności obliczeniowej O(n3), gdzie n jest długością łańcucha s. Wyszukiwanie palindromów Algorytm naiwny wyszukiwania palindromów Wejście: s – łańcuch tekstowy. Wyjście: Wszystkie palindromy zawarte w łańcuchu s. Elementy pomocnicze: i,j - indeksy znaków w łańcuchu s, i,j N N - długość łańcucha s, n iP – prawy indeks, iP N iL – lewy indeks, iL N N LISTA KROKÓW K01: n ← |s| K02: Dla i = 0,1,...,n - 2, wykonuj K03...K10 K03: Dla j = i+2, i+3,...,n1: wykonuj K04...K10 K04: iL ← i ; lewy indeks na pierwszym znaku podsłowa K05: iP ← j - 1 ; prawy indeks na ostatnim znaku podsłowa K06: Dopóki iL < iP wykonuj K07...K09 ; sprawdzamy, czy podsłowo jest palindromem K07: Jeśli s[iL] ≠ s[iP], to następny obieg pętli K03 K08: iL ← iL + 1 ; lewy indeks przesuwamy w prawo K09: iP ← iP - 1 ; a prawy w lewo K10: Pisz s[i,j] ; wyprowadzamy znaleziony palindrom K11: Zakończ ; przeglądamy łańcuch s Program Program generuje 40 znakowy łańcuch zbudowany ze znaków {A,B,C,D}, wyszukuje w nim wszystkie palindromy i wypisuje je z odpowiednim przesunięciem. • Rozwiązanie nr 2 • Drugie podejście do rozwiązania problemu wyszukiwania wszystkich palindromów w łańcuchu znakowym s opiera się na własnościach palindromów. Przedstawiony tutaj algorytm został opracowany w 1975 przez Glenna Manachera z Computer Center and Department of Information Engineering, University of Illinois, Chicago, IL. Do opisu algorytmu Manachera wprowadzimy kilka nowych pojęć. • Niech pP będzie palindromem parzystym o postaci pP = wwR, gdzie w jest niepustym podłańcuchem. Niech pN będzie palindromem nieparzystym o postaci pN = wXwR. • • Promieniem rp palindromu p będziemy nazywali długość podsłowa w, czyli rp = |w| • Palindrom parzysty pP posiada zawsze długość |pP| = 2rp. Palindrom nieparzysty pN posiada zawsze długość |pN| = 2rp + 1. • Środkiem palindromu p jest pozycja is = rp – jest to pozycja pierwszego znaku za słowem w (można również definiować środek palindromu jako pozycję ostatniego znaku podsłowa w, lecz sądzę, iż nasz sposób jest lepszy, gdyż nie wymaga wprowadzania żadnych zmian dla palindromów nieparzystych) Dla palindromu parzystego środek wypadnie na pierwszym znaku wR, natomiast dla palindromu nieparzystego środek wypadnie na znaku X: • • pP[rp] = wR[0] pN[rp] = X • Algorytm Manachera nie wyznacza wszystkich palindromów, jak robi to algorytm naiwny, lecz maksymalne palindromy, których środki występują na kolejnych pozycjach znakowych przeszukiwanego łańcucha s. Dzięki takiemu podejściu redukujemy złożoność obliczeniową fazy przeszukiwania łańcucha s. Mając maksymalny palindrom możemy bez problemów wyznaczyć zawarte w nim podpalindromy. Wykorzystujemy tutaj własność symetrii palindromu: • Przykład: rp palindrom parzysty palindrom nieparzysty 4 ABCDDCBA ABCDADCBA 3 BCDDCB BCDADCB 2 CDDC CDADC 1 DD DAD • Dla danego łańcucha s algorytm Manachera tworzy tablicę dwuwymiarową R.: • • R[0,...] – promienie palindromów parzystych R[1,...] – promienie palindromów nieparzystych • • Indeksy tych tablic określają kolejne pozycje znakowe w łańcuchu s, natomiast elementy tablic zawierają maksymalne promienie palindromów o środkach na danej pozycji znakowej. • Używając w odpowiedni sposób tablicy R oraz własności symetrii palindromu algorytm Manachera wykorzystuje sprytnie informację o wcześniej wyznaczonych promieniach palindromów maksymalnych do wyszukiwania następnych palindromów. Otóż po wyznaczeniu promienia rp palindromu na pozycji i-tej w łańcuchu s, sprawdzane są promienie palindromów na kolejnych pozycjach poprzedzających pozycję i-tą w obszarze podsłowa w – tutaj algorytm wymaga dwóch wersji – osobnej dla palindromów parzystych i osobnej dla nieparzystych. Zasada jest identyczna dla obu wersji. Rozważmy zatem możliwe przypadki (dla palindromu parzystego): • Na pozycji i - k, k = 1,2,...,rp, promień palindromu wynosi 0 – czyli nie istnieje palindrom o środku na pozycji i - k. Skoro tak, to przez symetrię wnioskujemy, iż na pozycji lustrzanej i + krównież nie będzie żadnego palindromu. Pozycja i + k możne zostać pominięta przy dalszym wyszukiwania palindromów. • Na pozycji i - k jest palindrom o promieniu r < rp - k. Taki palindrom w całości zawiera się wewnątrz rozważanego palindromu i co więcej, nie styka się z jego brzegiem. Poprzez symetrię wnioskujemy, iż na pozycji i + k również musi występować taki sam palindrom, którego już dalej nie da się rozszerzyć. Pozycji i + k nie musimy już dalej sprawdzać. • Na pozycji i - k jest palindrom o promieniu r > rp - k. Taki palindrom wykracza z lewej strony poza obszar rozważanego palindromu. Na pozycji i + k znajduje się palindrom o promieniu r = rp- k i palindromu tego nie da się już rozszerzyć. Wyjaśnienie tego faktu jest bardzo proste – gdyby palindrom na pozycji i + k posiadał większy promień niż wyliczone r, to również z uwagi na symetrię przeglądany palindrom posiadałby promień większy od rp, a przecież jest to palindrom maksymalny. Pozycję i + k również możemy pominąć. • Pozostał ostatni przypadek – na pozycji i - k występuje palindrom o promieniu r = rp - k. Taki sam palindrom musi być na pozycji i + k, jednakże w tym przypadku palindrom ten może być rozszerzalny. Pozycję i + k musimy zatem sprawdzić na obecność palindromu o promieniu większym od r. Z powyższych rozważań otrzymujemy następujący algorytm działający w czasie liniowym O(n): Algorytm Manachera wyszukiwania palindromów Wejście: s – łańcuch tekstowy. Wyjście: Wszystkie palindromy zawarte w łańcuchu s. Elementy pomocnicze: j - indeksuje wymiar tablicy R. Wartość 0 dotyczy palindromów parzystych, wartość 1 dotyczy palindromów nieparzystych, j N k-zmienna pomocnicza do indeksowania środków palindromów wewnętrznych, k rp - wyznaczany promień palindromu maksymalnego, rp n- długość łańcucha s, n N N N R- tablica dwuwymiarowa, pierwszy wymiar określa rodzaj palindromu, drugi wymiar zawiera indeksy od 0 do n, R N LISTA KROKÓW K01: n ← |s| ; obliczamy długość łańcucha s K02: s ← w1 + s + w2 ; na początku i na końcu s dodajemy wartowników, w1 ≠ w2 K03: Dla j = 0,1 wykonuj K04...K17 ; wyznaczamy promienie palindromów parzystych i nieparzystych K04: R[j,0] ← 0 ; pierwszy promień jest zawsze zerowy K05: i←1 ; ustawiamy i na pierwszym znaku s za wartownikiem w1 K06: rp ← 0 ; początkowy promień palindromu jest zerowy K07: Dopóki i ≤n, wykonuj K08...K17 ; przechodzimy przez kolejne środki palindromów K08: Dopóki s[i - rp - 1] = s[i + j + rp], wykonuj rp ← rp + 1 ;wyznaczamy promień maksymalnego palindromu o środku na pozycji i-tej K09: R[j,i] ← rp ; wyliczony promień zapamiętujemy w tablicy R K10: k←1 ; przeglądamy promienie wcześniejszych palindromów wewnętrznych K11: Jeśli R[j,i - k] = rp - k, to idź do K16 ; sprawdzamy ostatni przypadek K12: Jeśli k ≥ rp, to idź do K16 ; musimy być wewnątrz palindromu K13: R[j,i + k] ← min(R[j,i - k],rp - k) ; wyznaczamy promień lustrzanego palindromu wewnętrznego K14: k←k+1 ; przechodzimy do następnego palindromu wewnętrznego K15: Idź do K11 K16: rp ← max(rp - k,0) ; wyznaczamy promień początkowy palindromu K17: i←i+k ; pomijamy wyliczone palindromy lustrzane K18: s ← s[1:n] ; usuwamy wartowników w1 i w2 z łańcucha s K19: Dla i = 1,2,...,n wykonuj K20...K21 ; wyprowadzamy kolejne palindromy parzyste i nieparzyste K20: Dla j = 0,1 wykonuj K21 K21: Dla rp = R[j,i], R[j,i ]- 1,...,1 pisz s[i - rp - 1:i + 2rp + j] K22: Zakończ Program Program generuje 40 znakowy łańcuch zbudowany ze znaków {A,B,C,D}, wyszukuje w nim wszystkie palindromy i wypisuje je z odpowiednim przesunięciem. Szyfr Cezara Szyfr Cezara • Szyfrowanie tekstów (ang. text encryption) ma na celu ukrycie ważnych informacji przed dostępem do nich osób niepowołanych. Historia kodów szyfrujących sięga czasów starożytnych. Już tysiące lat temu egipscy kapłani stosowali specjalny system hieroglifów do szyfrowania różnych tajnych wiadomości. Szyfr Cezara (ang. Ceasar's Code lub Ceasar's Cipher) jest bardzo prostym szyfrem podstawieniowym (ang. substitution cipher). Szyfry podstawieniowe polegają na zastępowaniu znaków tekstu jawnego (ang. plaintext) innymi znakami przez co zawarta w tekście informacja staje się nieczytelna dla osób niewtajemniczonych. Współcześnie szyfrowanie stanowi jedną z najważniejszych dziedzin informatyki – to dzięki niej stał się możliwy handel w Internecie, funkcjonują banki ze zdalnym dostępem do kont, powstał podpis elektroniczny oraz bezpieczne łącza transmisji danych. Przykładów zastosowania jest bez liku i dokładne omówienie tej dziedziny wiedzy leży daleko poza możliwościami tego artykułu. • Szyfr Cezara został nazwany na cześć rzymskiego imperatora Juliusza Cezara, który stosował ten sposób szyfrowania do przekazywania informacji o znaczeniu wojskowym. Szyfr polega na zastępowaniu liter alfabetu A...Z literami leżącymi o trzy pozycje dalej w alfabecie: • Tek st jaw ny A B C D E F G H I J K L M N O P Q R S T U V W X Y Z Szy fr Cez ara D E F G H I J K L M N O P Q R S T U V W X Y Z A B C Ostatnie trzy znaki X, Y i Z nie posiadają następników w alfabecie przesuniętych o trzy pozycje. Dlatego umawiamy się, iż alfabet "zawija się" i za literką Z następuje znów litera A. Teraz bez problemu znajdziemy następniki X → A, Y → B i Z → C. • Przykład: • Zaszyfrować zdanie: NIEPRZYJACIEL JEST BARDZO BLISKO. • Poszczególne literki tekstu jawnego zastępujemy literkami szyfru Cezara zgodnie z powyższą tabelką kodu. Spacje oraz inne znaki nie będące literami pozostawiamy bez zmian: • • NIEPRZYJACIEL JEST BARDZO BLISKO QLHSUCBMDFLHO MHVW EDUGCR EOLVNR • • Deszyfrowanie tekstu zaszyfrowanego kodem Cezara polega na wykonywaniu operacji odwrotnych. Każdą literę kodu zamieniamy na literę leżącą o trzy pozycje wcześniej w alfabecie. Szyfr Cezara Szyfr Cezara A B C D E F G H I J K L M N O P Q R S T U V W X Y Z Tekst jawny X Y Z A B C D E F G H I J K L M N O P Q R S T U V W Podobnie jak poprzednio trzy pierwsze znaki szyfru Cezara nie posiadają bezpośrednich odpowiedników liter leżących o trzy pozycje wcześniej, ponieważ alfabet rozpoczyna się dopiera od pozycji literki D. Rozwiązaniem jest ponowne "zawinięcie" alfabetu tak, aby przed literą A znalazły się trzy ostatnie literki X, Y i Z. Do wyznaczania kodu literek przy szyfrowaniu i deszyfrowaniu posłużymy się operacjami modulo. Operacja modulo jest resztą z dzielenia danej liczby przez moduł. Wynik jest zawsze mniejszy od modułu. U nas moduł będzie równy 26, ponieważ tyle mamy liter alfabetu. Jeśli c jest kodem ASCII dużej litery alfabetu (rozważamy tylko teksty zbudowane z dużych liter), to szyfrowanie kodem Cezara polega na wykonaniu następującej operacji arytmetycznej: c = 65 + (c - 62) mod 26 Deszyfrowanie polega na zamianie kodu wg wzoru: c = 65 + (c - 42) mod 26 Algorytm szyfrowania tekstu kodem Cezara Wejście: Łańcuch tekstowy s Wyjście: Łańcuch tekstowy s zaszyfrowany kodem Cezara Elementy pomocnicze: i-indeks, i N kod(x)- zwraca kod litery x znak(x)- zmienia kod x na odpowiadający mu znak ASCII LISTA KROKÓW K01: Dla i=0,1,…|s|-1 wykonuj K02…K03 ;przeglądamy kolejne znaki tekstu K02: Jeśli s[i]<”A” v s[i] > ”Z”, to następny obieg pętli K01 ;pomijamy znaki nie będące literami A...Z K03: s[i]<- znak (65+(kod(s[i])-62)mod26) ;szyfrujemy szyfrem Cezara K04: Pisz s K05: Zakończ Program Program odczytuje wiersz znaków. Zamienia litery małe na duże i szyfruje kodem Cezara wyświetlając wynik. Algorytm deszyfrowania tekstu zaszyfrowanego kodem Cezara Wejście: Łańcuch tekstowy s zaszyfrowany kodem Cezara Wyjście: Tekst jawny Elementy pomocnicze: i-indeks, i N kod(x)- zwraca kod litery x znak(x)- zmienia kod x na odpowiadający mu znak ASCII LISTA KROKÓW K01: Dla i=0,1,…|s|-1 wykonuj K02…K03 ;przetwarzamy kolejne znaki tekstu K02: Jeśli s[i]<”A” v s[i] > ”Z”, to następny obieg pętli K01 ;pomijamy znaki nie będące literami A...Z K03: s[i]<- znak (65+(kod(s[i])-42)mod26) ;deszyfrujemy K04: Pisz s K05: Zakończ Program Program odczytuje wiersz znaków zaszyfrowanych szyfrem Cezara, deszyfruje je i wypisuje tekst jawny. Szyfr przestawieniowy Szyfr przestawieniowy (ang. transposition cifer) polega na zamianie położenia znaków tworzących tekst, przez co wiadomość staje się nieczytelna dla niewtajemniczonego odbiorcy. W zaszyfrowanym tekście znajdują się wszystkie znaki tekstu jawnego. Zaczniemy od najprostszych szyfrów przestawieniowych. Przestawianie dwóch sąsiednich liter W tym rodzaju szyfru tekst dzielimy na pary znaków. Następnie w każdej parze zamieniamy ze sobą litery, Przykład: tekst = ALA MA KOCURKA BURKA I PIESKA BIESKA pary = AL A MA K OC UR KA B UR KA I P IE SK A BI ES KA zamiana = LA A AM K CO RU AK B RU AK I P EI KS A IB SE AK szyfr = LA AAMK CORUAKB RUAKI P EIKS AIBSEAK Tekst da się podzielić na pary, jeśli zawiera parzystą liczbę znaków. W przeciwnym razie ostatnia para jest niepełna. W takiej niepełnej parze liter oczywiście nie zamieniamy miejscami. Zwróć uwagę, iż ten szyfr jest symetryczny. Jeśli poddamy szyfrowaniu tekst poprzednio zaszyfrowany, to otrzymamy z powrotem tekst jawny. Algorytm szyfrowania przestawieniowego ze zamianą liter w parach Wejście Łańcuch tekstowy s, który zawiera tekst poddawany szyfrowaniu lub deszyfrowaniu Wyjście: Zaszyfrowany łańcuch s Elementy pomocnicze: i- indeks, i N LISTA KROKÓW K01: i <- 0 rozpoczynamy od pierwszego znaku K02: Dopóki i+1 <|s| wykonuj K03…K04 K03: s[i]<-> s[i+1] zamieniamy znaki w parze K04: i <- i+2 przesuwamy się do następnej pary znaków K05: Zakończ Program Program wczytuje wiersz tekstu, szyfruje go przez zamianę liter w parach i wyświetla wynik. Dodawanie dużych liczb Problem Dodać do siebie dwie dowolnie duże, dodatnie liczby całkowite, przedstawione w postaci łańcucha cyfr. Problem rozwiążemy w sposób szkolny (profesjonalne algorytmy wymagają innego podejścia). Dodawane liczby musimy wyrównać do ostatnich cyfr: 21638626396236623668969866232198 95832808595775579737342988203408934789797363 Dodawanie rozpoczniemy od ostatnich cyfr łańcuchów. Stosujemy przy tym poznane w szkole podstawowej zasady dodawania dwóch liczb. Dodajemy ostatnie cyfry. W łańcuchu wynikowym umieszczamy ostatnią cyfrę wyniku. Natomiast pierwsza cyfra wyniku staje się przeniesieniem do następnej pozycji: 1 21638626396236623668969866232198 + 95832808595775579737342988203408934789797363 1 W następnym kroku dodajemy do siebie dwie kolejne cyfry oraz przeniesienie. Do łańcucha wynikowego wpisujemy na przedostatniej pozycji ostatnią cyfrę wyniku, a pierwsza cyfra wyniku staje się przeniesieniem na dalszą pozycję. 1 21638626396236623668969866232198 + 95832808595775579737342988203408934789797363 61 Jeśli w jednym z łańcuchów skończą się zbyt wcześnie cyfry, to przyjmujemy, że posiada on resztę cyfr równych 0. Sprowadza się to wtedy do dodawania przeniesień do pozostałych cyfr drugiego łańcucha. Gdy wszystkie cyfry zostaną przetworzone, a przeniesienie ma wartość większą od 0, to umieszczamy je na początku łańcucha wynikowego jako pierwszą cyfrę wyniku Przy dodawaniu cyfr musimy pamiętać, że są one przechowywane w łańcuchach w postaci kodów ASCII: Cyfra 0 1 2 3 4 5 6 7 8 9 Kod ASCII 48 49 50 51 52 53 54 55 56 57 Dlatego w celu otrzymania wartości cyfry należy od jej kodu odjąć 48, a przy otrzymywaniu kodu znaku z wartości cyfry należy do niej dodać 48. Algorytm dodawania dwóch dowolnie dużych, nieujemnych liczb całkowitych Wejście: s1,s2 – dodawane łańcuchy z cyframi Wyjście: s3 – łańcuch wynikowy, który zawiera cyfry sumy Elementy pomocnicze: p – przeniesienie z poprzedniej pozycji, p є Z w – wynik dodawania, w є N i,j – indeksy w łańcuchach s1 i s2, i,j є Z k – licznik pętli, k є Z n – długość krótszego z łańcuchów s1 i s2, n є Z kod(x) – zwraca kod znaku x znak(x) – zamienia kod x na odpowiadający mu znak ASCII Lista kroków: K01: i ← |s1| ; wyznaczamy długości łańcuchów K02: j ← |s2| K03: n ← i ; w n wyznaczamy długość krótszego z łańcuchów K04: Jeśli j < i, to n ← j K05: p ← 0 ; zerujemy przeniesienie K06: s3 ← "" ; zerujemy łańcuch wyniku s K07: Dla k = 1,2,...n: wykonuj K08...K12 ;przebiegamy wstecz przez cyfry łańcuchów K08: w ← kod(s1[i]) + kod(s2[j]) + p - 96 ; obliczamy sumę cyfr i przeniesienia. 96 = 2 x 48 K09: i←i-1 ; ; cofamy indeksy o 1 pozycję dla kolejnego obiegu K10: j←j-1 K11: p ← w div 10 ; obliczamy przeniesienie do następnej pozycji K12: s3 ← znak((w mod 10) + 48) + s3 ; cyfrę sumy dołączamy do wyniku K13: Dopóki i > 0 wykonuj K14...K17 ; jeśli w s pozostały cyfry K14: w ← kod(s1[i]) + p - 48 ; to dodajemy do nich tylko przeniesienia K15: i←i-1 K16: p ← w div 10 K17: s3 ← znak((w mod 10) + 48) + s3 K18 Dopóki j > 0 wykonuj K19...K22 ; to samo dla s K19: w ← kod(s2[j]) + p - 48; K20: j←j-1 K21: p ← w div 10 K22: s3 ← znak((w mod 10) + 48) + s3 K23 Jeśli p > 0, to s3 ← znak(p + 48) + s3 jeśli jest przeniesienie, to jest ono pierwszą cyfrą wyniku K24: Jeśli s3 = "", to s3 = "0" ; jeśli wynik nie zawiera cyfr, to s i s były puste. K25: Zakończ ; wynik dodawania w s 3 1 2 1 3 2 Mnożenie dużych liczb Problem 1 Pomnożyć dowolnie dużą nieujemną liczbę całkowitą przez nieujemną liczbę całkowitą względnie małą, np. 32 bitową Naszym zadaniem jest znalezienie iloczynu: W=a*b gdzie: a – liczba duża, b-liczba mała Liczba mała b rozkłada się na sumę potęg liczby 2 mnożonych przez kolejne bity bi liczby b: b=b31231+b30+230+....+b222+b121+b020 Teraz możemy zapisać: W=a* (b31231+b30+230+....+b222+b121+b020) W=a*b31231+a*b30+230+....+a*b222+a*b121+a*b020 Na pierwszy rzut oka otrzymany wzór wydaje się mało przydatny. Jednakże pozory mylą. Zapiszmy to nieco inaczej: W=b31231a+b30+230a+....+b323a+b222+b121a+b020a W=b31231a+b30+230a+....+b38a+b24a+b12a+b0a W powyższym wzorze bi to i-ty bit mniejszej liczby. Natomiast kolejne iloczyny 2ia bardzo łatwo oblicza się dynamicznie za pomocą dodawania, ponieważ, co łatwo zauważyć, każdy kolejny iloczyn jest dwa razy większy od poprzedniego.: a0=a a1=2a=a0+a0 a2=4a=a1+a1 a3=8a=a2+a2 ….................... a30=230a=a29+a29 a31=231a=a30+a30 Iloczyny dodajemy do wyniku W, jeśli odpowiedni bit bi jest równy 1. W całym tym postępowaniu wykonywane są tylko dodawania dużych liczb, które zostały opisane w poprzednim rozdziale. Bity z liczby b możemy łatwo wydzielać za pomocą operacji koniunkcji i przesuwów Algorytm mnożenia dowolnie dużej liczby nieujemnej przez małą liczbę nieujemną Wejście: a – duża liczba jako łańcuch znakowy b – mała liczba, b N Wyjście: є w – łańcuch wynikowy, który zawiera cyfry iloczynu ab a – zawartość nieokreślona b – zawiera zero Elementy pomocnicze: dodaj(x,y) – dodaje dwie duże liczby x i y jako łańcuchy i zwraca wynik jako łańcuch Lista kroków: K01: w ← "0" ; zerujemy wynik K02: Jeśli (b and 1) = 1, to w ← dodaj(w,a) ; jeśli bit bi = 1, to dodaj ai do w K03: b ← b shr 1 ; przesuń bity w b o jedną pozycję w prawo K04: Jeśli b = 0, to zakończ ; reszta bitów jest zerowa, kończymy K05: a ← dodaj(a,a) ; oblicz kolejne ai K06: Idź do K02 ; kontynuuj pętlę Problem 2 Obliczyć wartość iloczynu dwóch dowolnie dużych nieujemnych liczb całkowitych. Postąpimy zgodnie z algorytmem "szkolnym" (profesjonalne algorytmy wymagają innego podejścia). Liczby zapisujemy jedna nad drugą – umówmy się, że dłuższą liczbę zapisujemy u góry, a krótszą na dole: x 95832808595775579737342988203408934789797363 21638626396236623668969866232198 Następnie tworzymy kolejne iloczyny częściowe górnej liczby przez cyfry liczby dolnej. Iloczyny te są przesunięte w lewo zgodnie z pozycją mnożącej cyfry: x 95832808595775579737342988203408934789797363 21638626396236623668969866232198 + 766662468766204637898743905627271478318378904 862495277361980217636086893830680413108176267 0 958328085957755797373429882034089347897973630 0 191665617191551159474685976406817869579594726 000 ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... 574996851574653478424057929220453608738784178 00000000000000000000000000000 958328085957755797373429882034089347897973630 00000000000000000000000000000 191665617191551159474685976406817869579594726 0000000000000000000000000000000 2073690341706041464574292083419814736706959241205382993813776933584 Algorytm mnożenia dwóch dowolnie dużych nieujemnych liczb całkowitych Wejście: a,b – liczby podane w postaci łańcuchów znakowych Wyjście: w – łańcuch wynikowy, który zawiera cyfry iloczynu ab Elementy pomocnicze: c – łańcuch pomocniczy dla iloczynu częściowego z – łańcuch zawierający dodawane zera na końcu iloczynów częściowych n – zawiera liczbę cyfr w b, n є N i – wskazuje pozycję cyfr w b przez które mnożymy a, i єN dodaj(x,y) – dodaje dwie duże liczby x i y jako łańcuchy i zwraca wynik jako łańcuch mnóż(x,y) – zwraca łańcuch z iloczynem liczby dużej x jako łańcucha znakowego i liczby małej y, y є N kod(x) – zwraca kod znaku x. PROGRAMY