Ł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