Σχεδίαση και Ανάλυση Αλγορίθμων

Κεφάλαιο 4Βασικοί Αλγόριθμοι

Στο κεφάλαιο αυτό θα εξετάσουμε μερικούς γνωστούς αλγορίθμους και δομές δεδομένων και θα χρησιμοποιήσουμε τα εργαλεία που παρουσιάσθηκαν στα προηγούμενα κεφάλαια, αλλά θα γνωρίσουμε και αλγορίθμους των οποίων η ανάλυση είναι ad hoc και δεν στηρίζεται στις προηγούμενες τεχνικές. Πιο συγκεκριμένα, θα εξετασθούν εναλλακτικοί αλγόριθμοι για τον υπολογισμό του μέγιστου κοινού διαιρέτη και των αριθμών Fibonacci, για το πρόβλημα των πύργων του Hanoi, καθώς και για τον υπολογισμό της δύναμης, των συνδυασμών και του πολλαπλασιασμού πινάκων.

4.1 Εύρεση Μέγιστου Κοινού Διαιρέτη

Ας ξεκινήσουμε από τα εύκολα. Στη συνέχεια, δίνεται ένας πολύ απλός αλγόριθμος για την εύρεση του Μέγιστου Κοινού Διαιρέτη (ΜΚΔ).

     function gcd(m,n)
1.   if m<n then i <-- m else i <-- n
2.   while (m mod i <> 0) or (n mod i <> 0) do
3.      i <-- i-1
4.   return i

Ο αλγόριθμος δέχεται στην είσοδο δύο ακέραιους m και n, βρίσκει το μικρότερο και τον τοποθετεί στη μεταβλητή i (εντολή 1). Στη συνέχεια, δοκιμάζουμε διαδοχικά τιμές μία-μία, ξεκινώντας από το i και ελαττώνοντας προς μικρότερους αριθμούς. Η εντολή 2 είναι ένας βρόχος με δύο ελέγχους που εκτελούν διαιρέσεις και μπορεί να θεωρηθεί ότι το κόστος των ελέγχων αυτών είναι φραγμένο από επάνω (δηλαδή χωρίς να υπάρχουν άλλα κρυφά κόστη). Επομένως, δεχόμαστε την εντολή 3 ως βαρόμετρο, οπότε πλέον τίθεται το ερώτημα ποιός είναι ο αριθμός των επαναλήψεων. Ο αριθμός αυτός εξαρτάται από τη διαφορά μεταξύ του μικρότερου αριθμού μεταξύ των m και n αφ’ενός και του ΜΚΔ αφ’ετέρου. Στη χειρότερη περίπτωση o ΜΚΔ ισούται με τη μονάδα, οπότε ισχύει ότι η πολυπλοκότητα του αλγορίθμου αυτού είναι γραμμική Ο(min(m,n)).

Ο προηγούμενος αλγόριθμος δόθηκε απλώς για να τον απορρίψουμε στη συνέχεια, καθώς είναι γνωστό ότι ο αλγόριθμος του Ευκλείδη είναι αποτελεσματικότερος.

     function euclid(m,n)
1.   while m>0 do
2.      t <-- n mod m
3.      n <-- m
4.      m <-- t
5.   return n

Πρόταση.  
Η πολυπλοκότητα του αλγορίθμου του Ευκλείδη είναι λογαριθμική. 

Απόδειξη.  
Πρώτα θα αποδείξουμε ότι για δύο ακεραίους m,n (όπου nm) ισχύει: nmodm<n/2. Διακρίνουμε δύο περιπτώσεις:

  • αν m>n/2, τότε 1n/m<2nmodm=n-m<n-n/2=n/2.

  • αν mn/2, τότε nmodm<mn/2.

Ξαναγυρίζουμε στον Ευκλείδη. Έστω ότι k είναι ο συνολικός αριθμός των επαναλήψεων στο βρόχο while. Έστω ότι ni,mi οι τιμές των μεταβλητών n,m στην έξοδο από την i-οστή επανάληψη του βρόχου (όπου ik). Είναι ευνόητο ότι mi1 αλλά mk=0. Αν n0 και m0 είναι οι αρχικές τιμές των μεταβλητών n και m, τότε στη συνέχεια οι τιμές των μεταβλητών n,m έχουν ως εξής:

ni=mi-1 (4.1)
mi=ni-1modmi-1 (4.2)

Προφανώς ισχύει ni>mi για κάθε i. Έτσι, για κάθε i2 προκύπτει:

mi=ni-1modmi-1<ni-1/2=mi-2/2 (4.3)

Έστω ότι το k είναι περιττός αριθμός και ισχύει k=2d+1. Επομένως:

mk-1<mk-3/2<mk-5/4<<m0/2d (4.4)

Όμως, ισχύει mk-11, οπότε

m02dlogm0dlogm0(k-1)/2k1+2logm0 (4.5)

Φθάσαμε στη μισή απόδειξη. Η άλλη μισή αφορά στην περίπτωση όπου το k είναι άρτιος αριθμός. Η απόδειξη αυτή είναι παρόμοια και στηρίζεται στη σχέση m1=n0modm0<m0. Έτσι καταλήγουμε ότι η πολυπλοκότητα του Ευκλείδειου αλγορίθμου για την εύρεση του ΜΚΔ είναι λογαριθμική Ο(logm).

4.2 Υπολογισμός Αριθμών Fibonacci

Στο Κεφάλαιο 1 δόθηκαν μερικά βασικά στοιχεία για τους αριθμούς Fibonacci δεύτερης τάξης. Με βάση αυτόν τον αναδρομικό ορισμό μπορούμε να δώσουμε έναν αναδρομικό αλγόριθμο, όπως ο επόμενος fib1 που δέχεται μη αρνητικούς ακέραιους αριθμούς n.

     function fib1(n)
1.   if n<=1 then return n
2.   else return fib1(n-1)+fib1(n-2)

Συχνά η χρήση αναδρομής διευκολύνει τον προγραμματιστή στην υλοποίηση και τον έλεγχο του προγράμματος. Αν και πολλές φορές η αναδρομή φαίνεται ως πιο φυσικός τρόπος προγραμματισμού, ωστόσο πρέπει να χρησιμοποιείται με μέτρο. Μεταξύ ενός απλού επαναληπτικού προγράμματος και ενός αναδρομικού προγράμματος προτιμάται το πρώτο. Ο λόγος είναι ότι κάθε κλήση ενός υποπρογράμματος (δηλαδή διαδικασίας ή συνάρτησης) έχει χρονικό κόστος μη αμελητέο. Έτσι, το κέρδος σε χρόνο προγραμματισμού δημιουργεί απώλεια σε χρόνο εκτέλεσης. Επομένως, αν ο χρόνος απόκρισης είναι κρίσιμος τότε είναι βέβαιο ότι θα πρέπει να προτιμηθεί η επαναληπτική μέθοδος.

Η καλύτερη απόδειξη για την ισχύ των ανωτέρω προκύπτει μελετώντας βαθύτερα τη συνάρτηση fib1. Η συνάρτηση αυτή υπολογίζει τις ίδιες τιμές πολλές φορές. Για παράδειγμα, κατά τον υπολογισμό του fib1(4) γίνονται οι κλήσεις fib1(3) και fib1(2). Ομοίως κατά τον υπολογισμό του fib1(3) καλείται για δεύτερη φορά η fib1(2). Με το σκεπτικό αυτό η fib1(2) θα κληθεί 2 φορές, η fib1(1) θα κληθεί 3 φορές, ενώ η fib1(0) θα κληθεί 2 φορές. Οι κόμβοι του δένδρου του επόμενου σχήματος δείχνουν τα ορίσματα όλων των κλήσεων που θα εκτελεσθούν κατά την κλήση της fib1(4).

Σχήμα 4.1: Σειρά κλήσεων για την fib1(4).

Από το σχήμα αυτό φαίνεται ότι τα φύλλα αντιστοιχούν στους δύο τύπους των βασικών κλήσεων με ορίσματα 1 και 0, αντίστοιχα. Καθώς F4=3, έπεται ότι το δένδρο θα έχει 3 φύλλα που αντιστοιχούν στις 3 βασικές κλήσεις fib1(1) και μερικά ακόμη φύλλα (αυτή τη στιγμή άγνωστος ο αριθμός τους) που θα αντιστοιχούν στις βασικές κλήσεις fib1(0). Γενικεύοντας, είναι ευνόητο ότι η κλήση του fib1(n) μπορεί να παρασταθεί από ένα δυαδικό δένδρο που στα φύλλα θα έχει Fn κλήσεις της fib1(1). Προφανώς, το πλήθος των κόμβων του δένδρου παριστά το συνολικό αριθμό κλήσεων της fib1. Στη συνέχεια, δίνονται δύο εναλλακτικοί τρόποι υπολογισμού αυτού του συνολικού αριθμού κλήσεων.

Πρόταση.
Ο αριθμός των κλήσεων για την εύρεση του n-οστού αριθμού Fibonacci, Fn, ισούται με 2Fn+1-1.

Απόδειξη.
Η απόδειξη θα γίνει με διπλή επαγωγή. Σχεδιάζοντας απλά σχήματα, όπως το προηγούμενο, παρατηρούμε ότι για τον υπολογισμό του F1, ο αριθμός των κλήσεων είναι 1=2F2-1. Επίσης, για τον υπολογισμό του F2, ο αριθμός των κλήσεων είναι 3=2F3-1. Υποθέτουμε ότι η πρόταση ισχύει για n=k-1 και n=k, οπότε θα αποδείξουμε ότι ο αριθμός των κλήσεων για τον υπολογισμό του Fk+1 ισούται με 2Fk+2-1. Το δένδρο των κλήσεων που παριστά τις κλήσεις για τον υπολογισμό αυτό, θα αποτελείται από μία ρίζα και δύο υποδένδρα, που θα αντιστοιχούν στις κλήσεις για τον υπολογισμό των αριθμών Fk και Fk-1. Με βάση τις προηγούμενες υποθέσεις έπεται ότι το πλήθος των κόμβων του τελευταίου δένδρου ισούται με (2Fk+1-1)+(2Fk-1)+1. Από αυτή τη σχέση προκύπτει το ζητούμενο με απλή άλγεβρα.  

Πρόταση.
Κατά την εύρεση του αριθμού Fibonacci Fn εκτελούνται οι εξής κλήσεις: F1 φορές η κλήση Fn, F2 φορές η κλήση Fn-1, F3 φορές η κλήση Fn-2, , Fn-1 φορές η κλήση F2, Fn φορές η κλήση F1, και Fn-1 φορές η κλήση F0.  

Απόδειξη.
Η απόδειξη θα γίνει με διπλή επαγωγή. Σχεδιάζοντας απλά σχήματα, όπως το προηγούμενο, παρατηρούμε ότι για n=1, δηλαδή για τον υπολογισμό του F1, ο αριθμός των κλήσεων είναι F1=1. Επίσης, για n=2, δηλαδή για τον υπολογισμό του F2, η κατανομή των κλήσεων έχει ως εξής: η F2 θα κληθεί F1=1 φορά, η F1 θα κληθεί F2=1 φορά και η F0 θα κληθεί F1=1 φορά. Υποθέτουμε ότι η πρόταση ισχύει για n=k-1 και n=k. Πρέπει να αποδειχθεί για n=k+1. Κατά τον υπολογισμό του Fk+1 θα εκτελεσθούν όλες οι κλήσεις που θα εκτελούνταν για τον υπολογισμό του Fk συν όλες οι κλήσεις που θα εκτελούνταν για τον υπολογισμό του Fk-1 συν ένα. Με απλή παράθεση των επιμέρους κλήσεων και κατάλληλες προσθέσεις προκύπτει η αλήθεια της πρότασης.

Πρόταση.
Ο αριθμός των κλήσεων για την εύρεση του αριθμού Fibonacci Fn ισούται με 2Fn+1-1.

Απόδειξη.
Με βάση την προηγούμενη πρόταση αρκεί να αποδειχθεί ότι:

i=1nFi+Fn-1=2Fn+1-1 (4.6)

Έπειτα από κατάλληλη απλοποίηση προκύπτει ότι ισοδύναμα αρκεί να αποδειχθεί το εξής:

i=1nFi=Fn+2-1 (4.7)

Η σχέση αυτή θα αποδειχθεί επαγωγικά. Προφανώς η σχέση ισχύει για n=1. Υποθέτουμε ότι ισχύει για n=k, οπότε θα αποδείξουμε ότι ισχύει για n=k+1, δηλαδή ότι ισχύει η σχέση:

i=1k+1Fi=Fk+3-1 (4.8)

Ξεκινώντας από το αριστερό σκέλος έχουμε:

i=1k+1Fi=i=1kFi+Fk+1=Fk+2-1+Fk+1=Fk+3-1 (4.9)

Επομένως, φθάσαμε μέσα από δύο επαγωγικούς δρόμους στο ίδιο συμπέρασμα, δηλαδή ότι η πολυπλοκότητα του αλγορίθμου αυτού είναι Θ(Fn).

Το ίδιο συμπέρασμα ως προς την πολυπλοκότητα μπορεί να προκύψει και με μία ακόμη απόδειξη που δίνουμε στη συνέχεια. Ο λόγος της έμφασης στο σημείο αυτό είναι ότι παρόμοια προβλήματα θα αντιμετωπίσουμε στη συνέχεια του μαθήματος αυτού αλλά και σε επόμενα μαθήματα και, επομένως, θα ισχύουν τα ίδια συμπεράσματα.

Έστω ότι με G(n) συμβολίζουμε τον αριθμό των κλήσεων που θα εκτελεσθούν για τον υπολογισμό του fib1(n). Προφανώς ισχύει:

G(n)=G(n-1)+G(n-2)+1 (4.10)

με αρχικές συνθήκες G(0)=G(1)=1. Η εξίσωση αυτή είναι μία περίπτωση μη ομογενούς γραμμικής αναδρομικής εξίσωσης και θα μπορούσε να επιλυθεί κατά τα γνωστά. Εδώ θα ακολουθήσουμε ένα διαφορετικό δρόμο αξιοποιώντας την ομοιότητα των συναρτήσεων Fn και G(n). Ας υποθέσουμε ότι η G(n) εξαρτάται γραμμικά από τη συνάρτηση Fn. Δηλαδή ισχύει:

G(n)=aFn+b (4.11)

όπου οι a,b είναι σταθερές που πρέπει να υπολογισθούν. Επειδή G(0)=G(1)=1 και F0=F1=1, έπεται ότι a+b=1. Από τη σχέση αυτή και τις αναδρομικές εξισώσεις G(n) και Fn έπεται ότι b=-1 άρα a=2. Συνεπώς:

G(n)= 2Fn-1 (4.12)

Επομένως, φθάνουμε στο πρόβλημα της εύρεσης της πολυπλοκότητας της ίδιας της συνάρτησης Fn. Για να βρούμε την πολυπλοκότητα του αλγορίθμου, αρκεί να επιλύσουμε την επόμενη αναδρομική εξίσωση που βασίζεται στην ίδια τη φιλοσοφία του ορισμού των αριθμών Fibonacci:

T(n)=T(n-1)+T(n-2)+2 (4.13)

όπου το 2 παριστά το σταθερό κόστος της εντολής 1 και το κόστος της πρόσθεσης στην εντολή 2. Από την ίδια τη μορφή της αναδρομής δημιουργείται η βεβαιότητα ότι η λύση της αναδρομικής αυτής εξίσωσης είναι Θ(Fn)=Θ(ϕn). Είναι δυνατόν με δημιουργική επαγωγή να βρεθούν 3 σταθερές a,b και c, έτσι ώστε για κάθε ακέραιο n να ισχύει: aFnT(n)bFn-c, σχέση που πιστοποιεί την ανωτέρω πολυπλοκότητα.

Στη συνέχεια παρουσιάζεται η επόμενη συνάρτηση fib2 για τον υπολογισμό του n-οστού αριθμού Fibonacci, η οποία σε αντίθεση με την fib1, είναι επαναληπτική και δεν εκτελεί περιττούς επανα-υπολογισμούς. Η ανάλυσή του είναι σχετικά προφανής. Μέσα στο βρόχο for (εντολή 2) εκτελούνται 2 καταχωρίσεις (εντολή 3) με κόστος φραγμένο από επάνω. Έτσι, για λόγους απλοποίησης, θεωρούμε μοναδιαίο κόστος εντός του βρόχου. Ο βρόχος είναι απλός (δηλαδή όχι φωλιασμένος), οπότε η πολυπλοκότητά του είναι γραμμική Θ(n). Για να φθάσουμε στο τελευταίο συμπέρασμα, και πάλι για λόγους απλοποίησης, δεν εξετάσαμε το μέγεθος των τελεσταίων, αν δηλαδή αρκούν 4 χαρακτήρες για την αποθήκευσή τους ή όχι.

     function fib2(n)
1.   i <-- 1; j <-- 0;
2.   for k <-- 1 to n do
3.      j <-- i+j; i <-- j-i
4.   return j

Στη συνέχεια, δίνεται μία τρίτη μέθοδος, fib3, για τον υπολογισμό των αριθμών Fibonacci. Και στην περίπτωση αυτή αγνοούμε το θέμα του μεγέθους των τελεσταίων για να προχωρήσουμε ευκολότερα στην ανάλυση του αλγορίθμου. Η εντολή 1 έχει κόστος φραγμένο από επάνω, όπως επίσης και το εσωτερικό του βρόχου while αποτελείται από εντολές με συνολικό κόστος φραγμένο από επάνω. Επομένως, θεωρώντας ως βαρόμετρο τις εντολές εντός του βρόχου αρκεί να προσδιορίσουμε το πλήθος m των επαναλήψεων του βρόχου.

     function fib3(n)
1.   i <-- 1; j <-- 0; k <-- 0; h <-- 1;
2.   while n>0 do
3.      if n mod 2 = 1 then
4.         t <-- j*h; j <-- i*h+j*k+t; i <-- i*k+t
5.      t <-- h*h; h <-- 2*k*h+t;
6.      k <-- k*k+t; n <-- n div 2
7.   return j

Πρόταση.  
Η πολυπλοκότητα της συνάρτησης fib3 είναι O(logn). 

Απόδειξη.  
Έστω ότι ni είναι η τιμή της μεταβλητής n κατά την έξοδο από τον i-οστό βρόχο. Προφανώς για τις οριακές συνθήκες ισχύει n1=n/2 και nm=0, αλλά γενικότερα για 2tm ισχύει:

nt=nt-12nt-12 (4.14)

Aπό τα ανωτέρω προκύπτει:

ntnt-12nt-24n12t-1n2t (4.15)

Από τη σχέση αυτή απομονώνουμε τους όρους:

nmnm-12n2m (4.16)

Καθώς ισχύει nm=0, ενώ οι τιμές nt είναι ακέραιες, έπεται ότι nm-1=2 ή 1. Θέτοντας nm-1=2 στη δεξιά ανισοϊσότητα της ανωτέρω σχέσης προκύπτει ότι: mlogn. Επομένως, η πολυπλοκότητα του αλγορίθμου είναι λογαριθμική Ο(logn).

4.3 Οι Πύργοι του Hanoi

Το puzzle των πύργων του Ανόι διατυπώθηκε από το Γάλλο μαθηματικό Francois-Edouard-Anatole Lucas (1842-1891), ο οποίος το «έντυσε» με το γνωστό μύθο, ότι ο θεός τοποθέτησε στη γη 3 διαμαντένιους πύργους και 64 χρυσά δακτυλίδια διαφορετικού μεγέθους μεταξύ τους. Αρχικά, λοιπόν, τα 64 δακτυλίδια ήταν τοποθετημένα σε έναν πύργο κατά σειρά μεγέθους: κάτω τα μεγάλα και επάνω τα μικρά δακτυλίδια. Ο σκοπός των καλόγερων ενός γειτονικού μοναστηριού ήταν να μεταφέρουν τα δακτυλίδια από το αρχικό πύργο σε ένα διπλανό με την ίδια σειρά μεγέθους, δηλαδή κάτω τα μεγάλα και επάνω τα μικρά δακτυλίδια. Η μόνη επιτρεπόμενη κίνηση για ένα δακτυλίδι είναι να μεταφερθεί από έναν πύργο σε έναν άλλο, χωρίς όμως ποτέ να τοποθετείται ένα μεγαλύτερο δακτυλίδι επάνω από ένα μικρότερο. Σύμφωνα με το μύθο, όταν οι καλόγεροι ολοκληρώσουν τη διαδικασία μεταφοράς, τότε θα έρθει το τέλος του κόσμου. Σκοπός του μύθου είναι να δηλώσει τη δυσκολία του εγχειρήματος. Ένα παράδειγμα επίλυσης του puzzle για τρεις πύργους και τρία δαχτυλίδια απεικονίζεται στο Σχήμα 4.2. Στο συγκεκριμένο παράδειγμα απαιτούνται συνολικά επτά κινήσεις για να μεταφερθούν τα δαχτυλίδια από τον πύργο «Α» στον πύργο «Γ».

Σχήμα 4.2: Puzzle των πύργων του Ανόι για τρεις πύργους και τρία δαχτυλίδια

Ο αλγόριθμος και η ανάλυσή του στηρίζεται στο εξής σκεπτικό. Για να μεταφέρουμε τα m ανώτερα (δηλαδή τα m μικρότερα) δακτυλίδια από έναν πύργο i σε έναν πύργο j, πρώτα θα τοποθετήσουμε τα m-1 στον πύργο 6-i-j, κατόπιν το m-οστό δακτυλίδι στον j-οστό πύργο και, τέλος, τα m-1 δακτυλίδια από τον πύργο 6-i-j στον πύργο j. (Για τις μεταβλητές ισχύουν οι σχέσεις: 1i,j3,ij και m1.) Η επόμενη διαδικασία περιγράφει αλγοριθμικά το σκεπτικό αυτό. Η λύση στο πρόβλημα δίνεται με την κλήση της διαδικασίας hanoi(64,1,2).

     procedure hanoi(m,i,j)
1.   if m>0 then
2.      hanoi(m-1,i,6-i-j)
3.      write(i,"-->",j)
4.      hanoi(m-1,6-i-j,j)

Πρόταση.  
Η πολυπλοκότητα της διαδικασίας hanoi είναι Θ(2n). 

Απόδειξη.  
Θεωρούμε την εντολή 3 (write) ως το βαρόμετρο, οπότε η πολυπλοκότητα του προηγούμενου αναδρομικού αλγορίθμου εκφράζεται από την αναδρομική εξίσωση:

T(m)={1ανm=12T(m-1)+1ανm>1 (4.17)

Η εξίσωση αυτή μπορεί να γραφεί ως μία γραμμική μη ομογενής αναδρομική εξίσωση:

tn-2tn-1= 1 (4.18)

για n1 και t0=0, από όπου κατά τα γνωστά προκύπτει η χαρακτηριστική εξίσωση:

(x-2)(x-1)= 0 (4.19)

Οι ρίζες της χαρακτηριστικής εξίσωσης είναι 1 και 2, οπότε η γενική λύση είναι της μορφής:

tn=c11n+c22n (4.20)

Χρησιμοποιούμε την τελευταία αναδρομική εξίσωση, για να βρούμε μία δεύτερη αρχική συνθήκη:

t1= 2t0+1= 1 (4.21)

Επιλύοντας το σύστημα δύο εξισώσεων με δύο αγνώστους, που προκύπτει από τις αρχικές συνθήκες, έχουμε:

0 = c1+c2
1 = c1+2c2

από όπου προκύπτει ότι c1=-1,c2=1 και επομένως η λύση της αναδρομής είναι tn=2n-1. Έτσι, καταλήγουμε ότι η πολυπλοκότητα της μεθόδου είναι εκθετική Θ(2n).

Πράγματι, έχει υπολογισθεί ότι για να επιτευχθεί ο σκοπός πρέπει να εκτελεσθούν τουλάχιστον 18.446.744.073.709.551.615 κινήσεις.

4.4 Υπολογισμός Δύναμης

Είναι γνωστό ότι μερικές γλώσσες προγραμματισμού δεν προσφέρουν τελεστή για τον υπολογισμό της δύναμης (για παράδειγμα, η Pascal). Εξάλλου στην κρυπτογραφία η ύψωση μεγάλων αριθμών σε δυνάμεις είναι συχνή πράξη. Στις περιπτώσεις αυτές, ο προγραμματιστής πρέπει να υλοποιήσει μία αντίστοιχη συνάρτηση.

Στη συνέχεια, δίνεται ένα επιπρόσθετο παράδειγμα χρήσης αναδρομικής και επαναληπτικής μεθόδου για τον υπολογισμό της ακέραιας δύναμης ενός πραγματικού αριθμού ab. Οι επόμενες συναρτήσεις power1 και power2 είναι δύο πρώτες απλές προσεγγίσεις στο θέμα. Σε σχέση με την πρώτη που είναι επαναληπτική, παρατηρούμε ότι βασικά αποτελείται από έναν απλό βρόχο for (εντολή 2) που περιέχει μία μόνο εντολή καταχώρισης, η οποία είναι και το βαρόμετρο. Εύκολα προκύπτει από τα όρια του βρόχου ότι η πολυπλοκότητα της μεθόδου είναι γραμμική Θ(b).

     function power1(a,b)
1.   power <-- 1;
2.   for i <-- 1 to b do power1 <-- power1*a
3.   return power1

     function power2(a,b)
1.   if b=0 then return 1
2.   else if b=1 then return a
3.   else return a*power2(a,b-1)

Σε σχέση με τη δεύτερη που είναι αναδρομική, ισχύουν τα ίδια συμπεράσματα, δηλαδή έχουμε και πάλι γραμμική επίδοση Θ(b). Αυτό μπορεί να αποδειχθεί επιλύοντας την αναδρομική εξίσωση:

T(n)={0ανb1T(n-1)+1ανb>1 (4.22)

Είναι δυνατόν να επιτύχουμε κάτι καλύτερο;

Παρατηρούμε ότι αν υπολογισθεί το a2 με έναν πολλαπλασιασμό, τότε το a4 μπορεί να υπολογισθεί μόνο με έναν επιπλέον υπολογισμό (δηλαδή a4=a2*a2) αντί για άλλους δύο (όπως a4=a2*a*a). Επομένως, γενικεύοντας προκύπτει ότι αν το b είναι άρτιος αριθμός, τότε ab=ab/2*ab/2, αλλιώς ab=a(b-1)/2*a(b+1)/2. Συνεχίζοντας την engineering προσπάθειά μας για βελτίωση της πολυπλοκότητας και με βάση την ιδέα αυτή μπορούμε να παρουσιάσουμε την επόμενη αναδρομική συνάρτηση power3 που στηρίζεται στην αρχή του Διαίρει και Βασίλευε.

     function power3(a,b)
1.   if b=0 then return 1
2.   else if b=1 then return a
3.   else return power3(a,b/2)*power3(a,(b+1)/2)

Αν προσέξουμε καλύτερα αυτή τη συνάρτηση θα παρατηρήσουμε ότι δεν είναι ιδιαίτερα αποτελεσματική, γιατί δεν αποφεύγει την εκτέλεση περιττών κλήσεων. Για παράδειγμα, για τον υπολογισμό του a5, γίνονται οι εξής κλήσεις κατά σειρά: power3(a,5), power3(a,2), power3(a,1), power3(a,1), power3(a,3), power3(a,1), power3(a,2), power3(a,1), power3(a,1). Γιατί γίνονται αυτές οι συγκεκριμένες κλήσεις και μάλιστα με αυτή τη σειρά, μπορεί να γίνει κατανοητό αν εκτελέσουμε μία διάσχιση κατά βάθος (dfs) στο επόμενο δένδρο, όπου σε κάθε κόμβο εμφανίζεται η τιμή του εκθέτη b.

Σχήμα 4.3: Σειρά κλήσεων για την power3(a,5).

Όπως και στην περίπτωση της συνάρτησης fib1, κατά παρόμοιο τρόπο και στο δένδρο αυτό, λοιπόν, παρατηρούμε ότι ο αριθμός των κλήσεων είναι υπερβολικός. Μάλιστα, στη βάση της αναδρομής για εκθέτη ίσο με 1, γίνονται b=5 κλήσεις. Η επόμενη πρόταση δίνει το συνολικό αριθμό κλήσεων που εκτελούνται για τον υπολογισμό της δύναμης μέσω της power3.

Πρόταση.
Για κάθε μη κενό δυαδικό δένδρο, αν n0 είναι ο αριθμός των φύλλων και n2 είναι ο αριθμός των κόμβων βαθμού 2, τότε ισχύει:

n0=n2+1 (4.23)

Απόδειξη.
Αν n1 είναι το πλήθος των κόμβων βαθμού 1 και n ο συνολικός αριθμός κόμβων, τότε ισχύει:

n=n0+n1+n2 (4.24)

Εκτός από τη ρίζα για κάθε κόμβο υπάρχει μία σύνδεση που καταλήγει στον κόμβο αυτό. Άρα, αν k είναι ο συνολικός αριθμός συνδέσεων, τότε ισχύει:

n=k+1 (4.25)

Κάθε σύνδεση, όμως, ξεκινά είτε από κόμβο βαθμού 1, είτε από κόμβο βαθμού 2. Συνεπώς, ισχύει:

k=n1+2n2 (4.26)

Από τις τρεις αυτές σχέσεις προκύπτει η αλήθεια της πρότασης. Βέβαια, στην περίπτωση του δένδρου μας γνωρίζουμε ότι ισχύει n1=0 αλλά η απόδειξη έχει μείνει σκοπίμως γενική.

Συνεπώς, από την πρόταση αυτή προκύπτει ότι εφόσον ο αριθμός των φύλλων ισούται με b, έπεται ότι ο συνολικός αριθμός των κόμβων/κλήσεων ισούται με 2b-1 (διαπιστώνεται με απλή παρατήρηση στο σχήμα, αλλά αποδεικνύεται ακόμη και επαγωγικά). Άρα, η μέθοδος Διαίρει και Βασίλευε δεν κατάφερε να εκμεταλλευθεί την ιδέα που είπαμε προηγουμένως, καθώς και πάλι η πολυπλοκότητα είναι Θ(b), ενώ δεν πρέπει να μας διαφεύγει από την προσοχή και η κρυμμένη σταθερά που ισούται με 2. Ο λόγος είναι ότι, ενώ υπολόγισε το a2 και το a1, δεν τα αποθήκευσε ώστε να τα χρησιμοποιήσει αργότερα. Η επόμενη αναδρομική συνάρτηση power4 αξίζει να προσεχθεί, γιατί εφαρμόζει ακριβώς αυτό το σκεπτικό.

     function power4(a,b)
1.   if b=0 then return 1
2.   else if (b mod 2)=0 then return power4(a*a,b div 2)
3.   else return power4(a*a,b div 2)*a

Πρόταση.
Η πολυπλοκότητα της power4 είναι λογαριθμική Ο(logb).

Απόδειξη.
Αν θεωρήσουμε ότι βαρόμετρο είναι οι εκτελούμενοι πολλαπλασιασμοί, τότε προκύπτει η επόμενη σχέση:

T(n)={0ανn=0T(n/2)+1ανnmod 2=0T((n-1)/2)+2ανnmod 2=1 (4.27)

όπου θεωρούμε το χειρότερο ενδεχόμενο (δηλαδή να χρειάζονται δύο πολλαπλασιασμοί για να υποδιπλασιάσουμε την τάξη του προβλήματος) και καταλήγουμε στην επόμενη μορφή της:

T(n)=T(n/2)+2 (4.28)

Η επίλυση αυτής της αναδρομικής εξίσωσης είναι πολύ εύκολη υπόθεση, όπως εξετάσθηκε σε προηγούμενο κεφάλαιο. Θα μπορούσαμε να χρησιμοποιήσουμε τη μέθοδο της χαρακτηριστικής εξίσωσης ή το γενικό θεώρημα. Αντ’αυτών θα χρησιμοποιήσουμε τη μέθοδο της αντικατάστασης. Έτσι, διαδοχικά έχουμε ότι:

T(n) = T(n/2)+2
T(n/2) = T(n/4)+2
T(1) = T(0)+2

Συνεπώς, αντικαθιστώντας προκύπτει ότι:

T(n)= 2logn (4.29)

οπότε η πολυπλοκότητα της συνάρτησης power4 είναι λογαριθμική Ο(logb).

Το πρόβλημα αυτό για τον υπολογισμό της δύναμης ήταν μία καλή ευκαιρία για να μελετήσουμε αρκετές εναλλακτικές λύσεις και να διαπιστώσουμε τα πλεονεκτήματα και τα μειονεκτήματα της κάθε μίας από αυτές. Στη συνέχεια, θα εξετάσουμε έναν ακόμη αλγόριθμο power5 που βασίζεται στο δυναμικό προγραμματισμό, ο οποίος υποθέτει ότι υπάρχει μία δομή πίνακα store.

     function power5(a,b);
1.   store[1] <-- a; i <-- 1; exponent <-- 1;
2.   while (exponent<b) do
3.      i <-- i+1; exponent <-- 2*exponent;
4.      store[i] <-- store[i-1]*store[i-1]
5.   index <-- 0; power5 <-- 1
6.   while (index<b) do
7.      if (index+exponent<=b) then
8.         power5 <--  power5 * store[i];
9.         index <--  index + exponent
10.     exponent <--  exponent/2; i <-- i-1
11.  return power5

Η συνάρτηση αυτή κατ’αρχάς υπολογίζει όλες τις δυνάμεις του a με εκθέτη 1, 2, 4, 8 κοκ και τις αποθηκεύει σε ένα πίνακα store μεγέθους logb θέσεων. Στη συνέχεια, επιλέγει αυτές που χρειάζεται, για να υπολογίσει το ab. Για παράδειγμα, για τον υπολογισμό του 319 θα προκύψει ο Πίνακας 4.1, όπου στην επάνω γραμμή εμφανίζεται η τιμή του exponent, δηλαδή του εκθέτη που θα πρέπει να υψώσουμε τη βάση 3 για να μας δώσει το αντίστοιχο περιεχόμενο του πίνακα. Επομένως, τελικά, για τον υπολογισμό του 319 θα πολλαπλασιάσουμε το περιεχόμενο της 1ης, τη 2ης και της 5ης θέσης του πίνακα, επειδή 319=316+2+1.

1 2 4 8 16
3 9 81 6561 43046721
Πίνακας 4.1: Υπολογισμός δύναμης

Ποιά είναι η πολυπλοκότητα της μεθόδου; Για να απαντήσουμε αυτή την ερώτηση αρκεί να προσέξουμε τον ψευδοκώδικα της συνάρτησης. Στο πρώτο βρόχο while (εντολή 2) εκτελούμε logb επαναλήψεις, ενώ το εσωτερικό του δεύτερου βρόχου while (εντολή 6) επαναλαμβάνεται λιγότερο από logb φορές. Επομένως, η πολυπλοκότητα είναι της τάξης Θ(logb).

Είναι δυνατόν να βελτιώσουμε ακόμη περισσότερο την υλοποίηση της προηγούμενης ιδέας, έτσι ώστε να μην υπολογίζουμε δυνάμεις που δεν θα χρειασθούμε στη συνέχεια. Κλείνουμε, λοιπόν, την παράγραφο αυτή με την επόμενη συνάρτηση power6 που είναι επαναληπτική, στηρίζεται στην ίδια φιλοσοφία με τη συνάρτηση power5 και έχει την ίδια πολυπλοκότητα. Ωστόσο, είναι αναμενόμενο να είναι ταχύτερη από την προηγούμενη, πρώτον γιατί είναι επαναληπτική και όχι αναδρομική, δεύτερον γιατί δεν χρησιμοποιεί τον επιπλέον πίνακα, και τρίτον γιατί δεν θα περιέχει την κρυμμένη σταθερά 2.

     function power6(a,b)
1.   i <-- b div 2; j <-- b mod 2;
2.   if j<>0 then power6 <-- a
3.   while i<>0 do
5.      a <-- a*a; j <-- i mod 2; i <-- i div 2;
7.      if j<>0 then power6 <-- power6 * a
8.   return power6

4.5 Υπολογισμός Συνδυασμού

Στην παράγραφο αυτή θα εξετάσουμε ένα απλό υπολογιστικό πρόβλημα που δίνει τη δυνατότητα να αναπτύξουμε σταδιακά μία engineering προσέγγιση βελτιώνοντας διαδοχικά αναδρομικούς και επαναληπτικούς αλγορίθμους.

Από το Κεφάλαιο 2.1 γνωρίζουμε τις εξής σχέσεις για το συνδυασμό των n αντικειμένων ανά k :

(nk)=n!k!(n-k)!=(nn-k) (4.30)
(nk)=(n-1k)+(n-1k-1)=(n-1k-1)nk (4.31)

και, επομένως, ο υπολογισμός του συνδυασμού ανάγεται στον υπολογισμό των παραγοντικών. Ας αρχίσουμε, λοιπόν, από την εξέταση του παραγοντικού με σκοπό να φθάσουμε στην αποτελεσματικότερη υλοποίηση. Στη συνέχεια, δίνονται δύο εκδοχές, μία αναδρομική και μία επαναληπτική.

     function factorial1(n);
1.   if n=0 then return 1
2.   else return n*factorial1(n-1)

     function factorial2(n);
1.   product <-- 1;
2.   for i <-- 2 to n do product <-- i*product;
3.   return product

Είναι εύκολο να διαπιστώσουμε ότι θεωρητικά οι δύο εκδοχές είναι ισοδύναμες, καθώς διακρίνονται από γραμμική πολυπλοκότητα Θ(n). Ωστόσο, εξίσου ευνόητο είναι ότι πρακτικά η επαναληπτική εκδοχή είναι ταχύτερη σύμφωνα με όσα έχουμε αναφέρει προηγουμένως. Στη συνέχεια, ερχόμαστε στο ζητούμενο, στη μελέτη του υπολογισμού των συνδυασμών.

Η επόμενη διαδικασία comb1 είναι αναδρομική και στηρίζεται στην ιδιότητα των συνδυασμών, με την οποία ο υπολογισμός ενός συγκεκριμένου συνδυασμού ανάγεται στον υπολογισμό απλούστερων. Η διαδικασία αυτή είναι αναποτελεσματική, καθώς πραγματοποιούνται επανα-υπολογισμοί πολλών συνδυασμών.

     function Comb1(n,k);
1.   if (k=0) or (k=n) then return 1
2.   else return Comb1(n-1,k-1)+Comb1(n-1,k)

Για παράδειγμα, κατά τον υπολογισμό του συνδυασμού (42) θα προκύψουν οι κλήσεις για τον υπολογισμό των συνδυασμών (32) και (31). Κατά τον υπολογισμό των δύο τελευταίων συνδυασμών θα προκύψουν δύο κλήσεις για τον υπολογισμό του συνδυασμού (21). Το Σχήμα 4.4 παρουσιάζει μία δενδρική μορφή με κόμβους που εσωκλείουν τα ορίσματα όλων των κλήσεων που θα πραγματοποιηθούν για τον υπολογισμό του συνδυασμού των 4 αντικειμένων ανά 2. Ανάλογο σχήμα είχαμε κατασκευάσει και κατά την εξέταση των συναρτήσεων fib1 και power2. Η πολυπλοκότητα της εκδοχής comb1 είναι Θ((nk)). Για την ακρίβεια, ο αριθμός των κόμβων κάθε αντίστοιχου δένδρου είναι 2(nk)-1.

Σχήμα 4.4: Κλήσεις για τον υπολογισμό του comb1(4,2).

Από αυτό το σημείο εκκίνησης θα προχωρήσουμε σε κομψότερες λύσεις. Το πρώτο μέλημά μας είναι να απαλλαγούμε από την αναδρομή. Αυτό μπορούμε να το επιτύχουμε βασιζόμενοι στις προηγούμενες συναρτήσεις για τον υπολογισμό του παραγοντικού, όπως φαίνεται στην επόμενη συνάρτηση. Εφόσον ο υπολογισμός του παραγοντικού με την factorial2 είναι μία γραμμική διαδικασία, έπεται ότι και η διαδικασία comb2 είναι επίσης γραμμική ως προς n, δηλαδή είναι Θ(n). Βέβαια, αξίζει να σημειωθεί ότι υπεισέρχεται μία σταθερά ίση με 2, που πρακτικά είναι ένα πρόβλημα που πρέπει να απαλείψουμε.

     function comb2(n,k);
1.   t1 <-- factorial2(n);
2.   t2 <-- factorial2(k);
3.   t3 <-- factorial2(n-k);
4.   return t1/(t2*t3)

Αν και η συνάρτηση comb2 ήταν τεράστια πρόοδος σε σχέση με τη διαδικασία comb1, εντούτοις υπάρχουν περιθώρια βελτίωσης, αν παρατηρήσουμε ότι η comb2 εκτελεί περιττούς πολλαπλασιασμούς στον αριθμητή που απλοποιούνται στη συνέχεια με αντίστοιχες πράξεις στον παρονομαστή. Η επόμενη διαδικασία comb3 δεν εκτελεί επανα-υπολογισμούς, αν και είναι αναδρομική. Αν θεωρήσουμε την εντολή 2 και εκτελέσουμε τις απλοποιήσεις, τότε παρατηρούμε ότι τόσο ο αριθμητής όσο και ο παρονομαστής είναι γινόμενα ακριβώς k όρων. Από αυτήν την παρατήρηση συνάγεται ότι η πολυπλοκότητα της διαδικασίας αυτής είναι γραμμική ως προς k (όπου προφανώς kn), δηλαδή είναι Θ(k), με μία κρυμμένη σταθερά ίση με 2. Στο ίδιο συμπέρασμα καταλήγουμε και τυπικά επιλύοντας την αναδρομική σχέση:

T(k)=T(k-1)+2 (4.32)

όπου η σταθερά 2 προκύπτει λόγω του κόστους του ενός πολλαπλασιασμού και της μίας διαίρεσης στην εντολή 2 (εξου και η κρυμμένη σταθερά). Για να γίνει περισσότερο κατανοητή αυτή η αναδρομική εξίσωση σημειώνουμε ότι καθώς n>k, έπεται ότι θα γίνουν τόσες κλήσεις, όσες χρειάζονται μέχρι να μηδενισθεί το k (και όχι το n).

     function comb3(n,k);
1.   if (k=0) or (k=n) then return 1
2.   else return comb3(n-1,k-1)*n/k

Η μέθοδος αυτή μπορεί να βελτιωθεί περαιτέρω, αν κάνουμε χρήση της ανωτέρω ιδιότητας των συνδυασμών, δηλαδή ότι (nk)=(nn-k). Η επόμενη διαδικασία comb4 πρώτα υπολογίζει το ελάχιστο μεταξύ των k και n-k, ώστε να εξοικονομήσει έναν αριθμό πράξεων. Αν και η τεχνική αυτή βελτιώνει την κατάσταση, εντούτοις προκύπτει επίσης γραμμική πολυπλοκότητα της τάξης Θ(min(k,n-k)), και πάλι με μία κρυμμένη σταθερά ίση με 2.

     function comb4(n,k);
1.   if k>n-k then k <-- n-k
2.   t1 <-- 1;
3.   for i <-- n downto n-k+1 do t1 <-- t1*i;
4.   t2 <-- factorial2(k);
5.   return t1/t2

Οι συναρτήσεις comb3 και comb4, αν και γραμμικές, έχουν ένα πρακτικό μειονέκτημα. Κατά τον υπολογισμό του συνδυασμού προκύπτουν ενδιάμεσα αποτελέσματα που είναι μεγαλύτερα από το τελικό αποτέλεσμα. Έτσι μπορεί να προκύψει πρόβλημα υπερχείλισης, αναλόγως με τις τιμές των n,k και τους χρησιμοποιούμενους τύπους δεδομένων. Για το λόγο αυτό, προχωρούμε σε μία ακόμη λύση, που τυχαίνει να είναι αναδρομική, σύμφωνα με την οποία εκτελούνται διαιρέσεις σε πρώιμο στάδιο πριν τους πολλαπλασιασμούς, ώστε να αποφευχθεί η υπερχείλιση. Αυτό επιτυγχάνεται θεωρώντας προηγουμένως το ΜΚΔ των n,k, όπως παρουσιάζεται στην επόμενη διαδικασία comb5.

     function comb5(n,k);
1.   d <-- Gcd(n,k); q <-- k/d;
2.   if (k=0) or (k=n) then return 1
3.   else return(Comb5(n-1,k-1)/q)*n/d

Είναι γνωστό από το Κεφάλαιο 4.1 ότι η εύρεση του ΜΚΔ έχει λογαριθμική πολυπλοκότητα Θ(logk). Έτσι θα προκύψει προς επίλυση η αναδρομική εξίσωση:

T(k)=T(k-1)+4+logk (4.33)

όπου η σταθερά 4 συνάγεται καταμετρώντας τους πολλαπλασιασμούς και διαιρέσεις των εντολών 1 και 3. Με τη μέθοδο της αντικατάστασης προκύπτει τελικά ότι:

T(k)= 4k+logk! (4.34)

από όπου με τη βοήθεια του τύπου του Stirling προκύπτει ότι η πολυπλοκότητα της συνάρτησης comb5 είναι Θ(klogk). Με λίγα λόγια έχουμε καταλήξει σε μία βραδύτερη συνάρτηση, αλλά το πλεονέκτημά της είναι η σταθερότητά της (robustness) για διάφορες τιμές των n και k. Δηλαδή, θα έχουμε πρόβλημα υπερχείλισης για μεγαλύτερες τιμές των n και k.

Στη συνέχεια, παρουσιάζουμε την τελευταία εκδοχή comb6 που αποτελεί ό,τι αποτελεσματικότερο μπορούμε να επιτύχουμε από θεωρητική και πρακτική άποψη. Η μέθοδος αυτή έχει τα εξής τρία πλεονεκτήματα:

  1. 1.

    είναι βέλτιστη με πολυπλοκότητα Θ(min(k,n-k)),

  2. 2.

    είναι σταθερή καθώς εκτελεί εναλλάξ πολλαπλασιασμούς και διαιρέσεις αποφεύγοντας την υπερχείλιση των χρησιμοποιούμενων τύπων.

  3. 3.

    είναι επαναληπτική και αποφεύγει τη χρονική επιβάρυνση των αναδρομικών και του αντίστοιχου κόστους σε χώρο αποθήκευσης,

     function comb6(n,k);
1.   t <-- 1;
2.   if k>n-k then k <-- n-k
3.   for i <-- n downto n-k+1 do t <-- t*i/(n-i+1)
4.   return t

4.6 Πολλαπλασιασμός Πινάκων

Έστω ότι δίνονται δύο τετραγωνικοί πίνακες A και B μεγέθους n×n και ζητείται το γινόμενό τους, ο πίνακας C μεγέθους επίσης n×n. Ο πρώτος τρόπος που έρχεται κατά νου είναι να χρησιμοποιήσουμε έναν αλγόριθμο που υπολογίζει κάθε θέση (i,j) του πίνακα C με τη βοήθεια του τύπου:

C(i,j)=k=1nA(i,k)B(k,j) (4.35)

που θα υλοποιηθεί με ένα εσωτερικό βρόχο ως προς k μέσα σε έναν άλλο διπλό βρόχο ως προς i και j. Συνεπώς, θεωρώντας ως βαρόμετρο τους εκτελούμενους πολλαπλασιασμούς πραγματικών αριθμών, από τον τριπλό βρόχο θα προκύψει μία κυβική πολυπλοκότητα Ο(n3). Στο ίδιο συμπέρασμα θα καταλήξουμε θεωρώντας ότι ο πίνακας C έχει n2 θέσεις, ενώ κάθε θέση απαιτεί την εκτέλεση n πολλαπλασιασμών. Στη συνέχεια, θα προσπαθήσουμε να βελτιώσουμε την πολυπλοκότητα της εκτέλεσης του πολλαπλασιασμού πινάκων βασιζόμενοι στη μέθοδο Διαίρει και Βασίλευε.

Έστω ότι ισχύει n=2k. Μπορούμε να θεωρήσουμε ότι οι πίνακες A και B αποτελούνται ο καθένας από τέσσερις υποπίνακες μεγέθους n2×n2. Έτσι, θα μπορούσε να προκύψει η σχέση:

(A11A12A21A22)(B11B12B21B22)=(C11C12C21C22) (4.36)

από όπου κάθε υποπίνακας του πίνακα C θα υπολογίζονταν με βάση τις σχέσεις:

C11 = A11B11+A12B21
C12 = A11B12+A12B22
C21 = A21B11+A22B21
C22 = A21B12+A22B22

Καθώς έχουμε θεωρήσει ότι το n είναι δύναμη του δύο, θα μπορούσαμε αναδρομικά να υποδιαιρέσουμε τους υποπίνακες των A και B σε ακόμη μικρότερους υποπίνακες μέχρι του σημείου n=2, οπότε θα εκτελούσαμε εύκολα τους πολλαπλασιασμούς των πραγματικών αριθμών. Για να διατυπώσουμε την αναδρομική εξίσωση που εκφράζει την πολυπλοκότητα του πολλαπλασιασμού, αρκεί να παρατηρήσουμε ότι σε κάθε επίπεδο της αναδρομής απαιτούνται 8 πολλαπλασιασμοί και 4 προσθέσεις υποπινάκων. Έτσι, προκύπτει η αναδρομική εξίσωση:

T(n)={bανn28T(n/2)+cn2ανn>2 (4.37)

όπου το b εκφράζει το σταθερό κόστος του πολλαπλασιασμού στη βάση της αναδρομής, ενώ το c εκφράζει μία σταθερά που υπεισέρχεται στην πολυπλοκότητα που αφορά στην πρόσθεση δύο υποπινάκων. Αυτή η αναδρομική εξίσωση μπορεί να επιλυθεί με πολλούς τρόπους (ως μη ομογενής, με αντικατάσταση, με το γενικό θεώρημα κλπ.). Σε κάθε περίπτωση το αποτέλεσμα είναι να προκύψει κυβική πολυπλοκότητα Ο(n3). Δηλαδή, αν και χρησιμοποιήσαμε έναν αλγόριθμο βασιζόμενο στη μεθοδολογία Διαίρει και Βασίλευε, εντούτοις δεν καταφέραμε να βελτιώσουμε την πολυπλοκότητα του αλγορίθμου. Αυτό θα επιτευχθεί με τη μέθοδο του Strassen, η οποία προτάθηκε το 1960 και βασίζεται επίσης στη μεθοδολογία Διαίρει και Βασίλευε.

Έστω κατ’αρχάς ότι οι πίνακες A και B είναι μεγέθους 2×2. Αν υπολογίσουμε τις επόμενες ποσότητες:

m1 = (a11+a22)(b11+b22)
m2 = (a11+a22)b11
m3 = a11(b12-b22)
m4 = a22(b21-b11)
m5 = (a11+a12)b22
m6 = (a21-a11)(b11+b12)
m7 = (a12-a22)(b21+b22)

τότε μπορεί εύκολα να επαληθευθεί ότι ο πίνακας C ισούται με:

(m1+m4-m5+m7m3+m5m2+m4m1+m3-m2+m6) (4.38)

Εδώ πλέον παρατηρούμε ότι για την εκτέλεση αυτού του απλού πολλαπλασιασμού πινάκων απαιτούνται 7 πολλαπλασιασμοί και 18 προσθέσεις και αφαιρέσεις. Αν και ο αριθμός των προσθέσεων/αφαιρέσεων είναι τώρα μεγαλύτερος σε σχέση με την προηγούμενη μέθοδο Διαίρει και Βασίλευε, τώρα εξοικονομούμε έναν πολλαπλασιασμό, γεγονός που είναι σημαντικό καθώς ο αριθμός των πολλαπλασιασμών είναι το βαρόμετρό μας. Αν, λοιπόν, γενικεύσουμε αυτήν την τεχνική για μεγαλύτερους τετραγωνικούς πίνακες, προκύπτει αντίστοιχα η εξής αναδρομική εξίσωση:

T(n)={bανn27T(n/2)+cn2ανn>2 (4.39)

Μεταξύ των εναλλακτικών τρόπων για την επίλυση αυτής της αναδρομικής εξίσωσης ας επιλέξουμε να χρησιμοποιήσουμε το γενικό θεώρημα. Ισχύει ότι nlogba=nlog27=n2,81>f(n)=n2 για ϵ=0,81. Επομένως, αναγόμαστε στο πρώτο σκέλος του γενικού θεωρήματος και προκύπτει ότι η πολυπλοκότητα της μεθόδου του Strassen για τον πολλαπλασιασμό πινάκων είναι πολυπλοκότητας Ο(n2,81). Προφανώς, στο ίδιο συμπέρασμα θα καταλήγαμε χρησιμοποιώντας τη μέθοδο της αντικατάστασης.

Ως τελική σημείωση σημειώνεται ότι στη βιβλιογραφία αναφέρονται παρόμοιες λύσεις προς την προηγούμενη. Από αυτές άλλες είναι χειρότερες (δηλαδή, εκτελούν 7 πολλαπλασιασμούς και 24 προσθαφαιρέσεις), άλλες είναι ίδιες (εκτελούν 7 πολλαπλασιασμούς και 18 προσθαφαιρέσεις), ενώ τέλος άλλες είναι καλύτερες, καθώς με έξυπνο τρόπο εκτελούν 7 πολλαπλασιασμούς και 15 προσθαφαιρέσεις (αποθηκεύοντας και μη επανα-υπολογίζοντας μερικά στοιχεία). Βέβαια σε κάθε περίπτωση όλες αυτές οι λύσεις είναι ασυμπτωτικά ισοδύναμες, καθώς το πλήθος εκτέλεσης της πράξης βαρόμετρου του πολλαπλασιασμού είναι το ίδιο. Επίσης, σημειώνεται ότι έχουν προταθεί και άλλες μέθοδοι με χαμηλότερη πολυπλοκότητα, όπως Ο(n2.376), αλλά με πολύ μεγάλη κρυμμένη σταθερά που τις καθιστά μη πρακτικές. Τέλος, σημειώνεται ότι τα ανωτέρω έχουν θεωρητική σημασία. Για παράδειγμα, έχει αποδειχθεί ότι η μέθοος Strassen είναι αποτελεσματικότερη από τη συμβατική μέθοδο για πυκνούς πίνακες με n>100.

4.7 Βιβλιογραφική Συζήτηση

Τα προβλήματα της εύρεσης μέγιστου κοινού διαιρέτη (αλγόριθμος Ευκλείδη) και του πολλαπλασιασμού πινάκων είναι κλασικά αντικείμενα που βρίσκονται σε όλα σχεδόν τα διδακτικά εγχειρίδια αλγορίθμων. Εξίσου δημοφιλής με τα προηγούμενα προβλήματα είναι και η ακολουθία των αριθμών Fibonacci, για την οποία ο αναγνώστης μπορεί να ανατρέξει στο άρθρο [116], που αναφέρεται αποκλειστικά στο θέμα, καθώς επίσης και στα άρθρα [45, 113, 153]. Η συνάρτηση fib3 αναφέρεται στο άρθρο [68]. Με αφορμή το πρόβλημα των πύργων του Ανόι έχουν υπάρξει πολλές παραλλαγές. Για παράδειγμα, 300 τέτοιες παραλλαγές έχουν καταγραφεί στην ιστοσελίδα http://www.cs.wm.edu/pkstoc/toh.html. Το βιβλίο [23] αναφέρεται στον υπολογισμό των δυνάμεων. Σχετικά με τον υπολογισμό συνδυασμών είναι τα άρθρα [112, 155]. Ο αλγόριθμος του Strassen έχει δημοσιευθεί στο άρθρο [178], το άρθρο [31] αναφέρεται στο πρόβλημα της αποτελεσματικής υλοποίησης πολλαπλασιασμού πινάκων κατά Strassen, ενώ το άρθρο [140] βελτιώνει την πολυπλοκότητα του αλγορίθμου Strassen. Η Άσκηση 7 σχετικά με τους 4 πύργους του Ανόι προέρχεται από το άρθρο [29], οι Ασκήσεις 8-9 από το άρθρο [128],ενώ η Άσκηση 14 από το άρθρο [140].

4.8 Ασκήσεις

  1. 1.

    Δίνεται το επόμενο τμήμα ψευδοκώδικα. Ποιά είναι η πολυπλοκότητά του. Τί υπολογίζει;

    rel <-- 0; tot <-- 0;
    for i <-- 1 to n do
       for j <-- i+1 to n do
          tot <-- tot+1;
          if GCD(i,j)=1 then rel <-- rel+1;
    
  2. 2.

    Nα σχεδιασθούν και να αναλυθούν εναλλακτικοί αλγόριθμοι εύρεσης του ΜΚΔ τριών ακεραίων αριθμών.

  3. 3.

    Να αποδειχθεί ότι ένας αριθμός d είναι ο ΜΚΔ δύο αριθμών a και b, αν και μόνο αν υπάρχουν δύο ακέραιοι x και y, έτσι ώστε να ισχύει: d=ax+by.

  4. 4.

    Ο επόμενος ψευδοκώδικας επεκτείνει τον αλγόριθμο του Ευκλείδη για την εύρεση του ΜΚΔ δύο αριθμών a και b. Πέραν του ΜΚΔ (που συμβολίζεται με d), βρίσκει και δύο αριθμούς x και y, ώστε να ισχύει d=ax+by.

    function extended_euclid(a,b)
    if b=0 then return (1,0,a);
    (x',y',d) <-- extended_euclid(b,a mod b);
    return (y',x'- trunc(a/b)*y',d)
    

    Ποιά είναι η πολυπλοκότητα του αλγορίθμου; Να σχεδιασθούν εναλλακτικές εκδοχές του. Σημειώνεται ότι η συνάρτηση trunc υλοποιεί τη συνάρτηση πάτωμα.

  5. 5.

    Να αποδειχθεί ότι:

    • GCD(Fn+1,Fn) = 1

    • GCD(Fm,Fn) = FGCD(m,n).

  6. 6.

    Να θεωρηθεί το puzzle των πύργων του Ανόι με 3 πύργους και 2n δίσκους που αποτελούνται από n ζεύγη όμοιων δίσκων. Να αναλυθεί η περίπτωση.

  7. 7.

    Να θεωρηθεί το puzzle των πύργων του Ανόι με 4 πύργους αντί 3. Η στρατηγική που θα ακολουθηθεί είναι η εξής: Αρχικά, οι n δίσκοι βρίσκονται στον πρώτο πύργο και πρέπει να μεταφερθούν στον τέταρτο. Λαμβάνονται, λοιπόν, οι n-k δίσκοι και τοποθετούνται στο δεύτερο πύργο μέσω του τρίτου πύργου ακολουθώντας τη βέλτιστη τακτική του κλασικού προβλήματος για τρεις πύργους. Στη συνέχεια, οι k δίσκοι με τον ίδιο κλασικό τρόπο τοποθετούνται στον τέταρτο πύργο μέσω του τρίτου. Τελικά, μένει να μεταφερθούν οι n-k δίσκοι από το δεύτερο πύργο στον τέταρτο. Ποιά τιμή του k βελτιστοποιεί τη διαδικασία; Να σχεδιασθεί αλγόριθμος υπολογισμού του βέλτιστου k για διάφορες τιμές του n, αρχίζοντας με n=3. Να βρεθεί το πρότυπο αύξησης του k αυξανομένου του n.

  8. 8.

    Το γραμμικό (linear) πρόβλημα των πύργων του Ανόι απαιτεί τη μεταφορά των δίσκων από τον αριστερότερο στο δεξιότερο πύργο μόνο δια μέσου του μεσαίου πύργου (και ποτέ απευθείας). Ένα παράδειγμα της συγκεκριμένης παραλλαγής βρίσκεται στο Σχήμα LABEL:hanoilinear. Για να μεταφερθούν τα 3 δαχτυλίδια από τον πύργο «Α» στον πύργο «Γ» απαιτούνται 26 κινήσεις. Να σχεδιασθεί και να αναλυθεί αλγόριθμος επίλυσης της παραλλαγής.

    Σχήμα 4.5: Γραμμικό πρόβλημα πύργων Ανόι για τρεις πύργους και τρία δαχτυλίδια
  9. 9.

    Το γραμμικό δίδυμο (linear twin) πρόβλημα των πύργων του Ανόι θεωρεί ότι στον αριστερότερο δίσκο υπάρχουν n άσπροι δίσκοι, ενώ στο δεξιότερο πύργο υπάρχουν n μαύροι δίσκοι. Να σχεδιασθεί και να αναλυθεί αλγόριθμος που να επιτυγχάνει τη μεταφορά των αριστερών δίσκων στο δεξιότερο πύργο και τον μαύρων δίσκων στον αριστερότερο δίσκο χρησιμοποιώντας απαραιτήτως τον ενδιάμεσο δίσκο για κάθε μετακίνηση από το ένα άκρο στο άλλο.

  10. 10.

    Οι αριθμοί Fibonacci μπορούν να υπολογισθούν με πολλαπλασιασμό πινάκων. Για παράδειγμα, ισχύει:

    (F1F2)=(0111)(F0F1) (4.40)

    ενώ γενικώς ισχύει:

    (FnFn+1)=(0111)n(F0F1) (4.41)

    Ζητείται: (α) η έκφραση αυτή να αποδειχθεί, και (β) να βρεθεί η πολυπλοκότητα για τον υπολογισμό του Fn.

  11. 11.

    Κατά σύμπτωση ο αριθμός ϕ=1,61803 ισούται περίπου με την αναλογία χιλιομέτρου προς μίλι (1 km = 1,609344 mi). Έτσι, μία απόσταση Fn+1 χιλιομέτρων ισούται με Fn μίλια περίπου. Να σχεδιασθεί και να αναλυθεί αλγόριθμος μετατροπής χιλιομετρικών αποστάσεων σε μίλια χρησιμοποιώντας τους αριθμούς Fibonacci. Μπορεί, επίσης, να σχεδιασθεί και η αντίστροφη διαδικασία για τη μετατροπή αποστάσεων από μίλια σε χιλιόμετρα.

  12. 12.

    Να χρησιμοποιηθεί το σκεπτικό της Άσκησης 11 για τη σχεδίαση και ανάλυση ενός αλγορίθμου για τη μετατροπή μίας επιφάνειας από τετραγωνικά χιλιόμετρα σε τετραγωνικά μίλια και το αντίστροφο.

  13. 13.

    Να πολλαπλασιασθούν δύο σύνθετοι αριθμοί x=a+bi και y=c+di με 3 πολλαπλασιασμούς.

  14. 14.

    Έστω ένας αλγόριθμος που πολλαπλασιάζει δύο πίνακες 70x70 με 143.640 πολλαπλασιασμούς. Είναι λιγότερο ή περισσότερο αποτελεσματικός από τη μέθοδο Strassen;