Professional Documents
Culture Documents
Lab5 Notes Arrays Recursivefunction FINAL Withextensions PDF
Lab5 Notes Arrays Recursivefunction FINAL Withextensions PDF
ΣΧΟΛΗ ΗΜΜΥ
ΕΡΓΑΣΤΗΡΙΟ ΔΙΑΝΕΜΗΜΕΝΩΝ
ΠΛΗΡΟΦΟΡΙΚΩΝ ΣΥΣΤΗΜΑΤΩΝ ΚΑΙ ΕΦΑΡΜΟΓΩΝ
http://courses.ece.tuc.gr
ΗΜΜΥ ΠΛΗ 101 - ΕΙΣΑΓΩΓΗ ΣΤΟΥΣ ΗΛΕΚΤΡΟΝΙΚΟΥΣ ΥΠΟΛΟΓΙΣΤΕΣ ΚΑΙ ΤΗΝ ΠΛΗΡΟΦΟΡΙΚΗ
5H ΕΡΓΑΣΤΗΡΙΑΚH ΑΣΚΗΣΗ: ΥΠΟΣΤΗΡΙΚΤΙΚΟ ΥΛΙΚΟ
ΥΠΕΥΘΥΝΟΙ ΕΡΓΑΣΤΗΡΙΟΥ: ΑΝΕΣΤΗΣ ΓΕΩΡΓΙΟΣ, ΜΟΥΜΟΥΤΖΗΣ ΝΕΚΤΑΡΙΟΣ, ΜΑΡΑΓΚΟΥΔΑΚΗΣ ΙΩΑΝΝΗΣ
A. Διατάξεις
Μια διάταξη είναι μία συλλογή θέσεων αποθήκευσης δεδομένων, που έχουν τον ίδιο τύπο
δεδομένων και το ίδιο όνομα. Κάθε θέση αποθήκευσης σε μία διάταξη ονομάζεται στοιχείο της
διάταξης. Η διάταξη είναι ένας εύκολος τρόπος για να οργανωθεί ένας μεγάλος αριθμός από
όμοια (δηλαδή ίδιου τύπου) στοιχεία. Μπορείτε να δηλώσετε διατάξεις με κάθε τύπο στοιχείων.
Οι διατάξεις μπορούν να είναι μονοδιάστατες ή πολυδιάστατες.
1 / 11
salaries[10] = salaries[9];
εκχωρεί ένα αντίγραφο της αποθηκευμένης τιμής στο στοιχείο διάταξης salaries[9] στο στοιχείο
διάταξης salaries[10]. Όταν αναφέρεστε σε ένα στοιχείο διάταξης, ο δείκτης της διάταξης μπορεί
να είναι μία κυριολεκτική σταθερά, όπως στα προηγούμενα παραδείγματα. Ωστόσο, τα
προγράμματά σας μπορεί να χρησιμοποιούν συχνά ένα δείκτη που είναι μια ακέραια μεταβλητή ή
έκφραση της C, ή ακόμα και ένα άλλο στοιχείο διάταξης. Ακολουθούν μερικά παραδείγματα:
float salaries[100]; /* το salaries είναι διάταξη πραγματικών (float) αριθμών */
int a[10]; /* το a[] είναι διάταξη ακεραίων */
int i = 0;
salaries[i] = 100; /* ισοδύναμη με το salaries[0] = 100 */
salaries[i + 1] = 200; /* ισοδύναμη με το salaries[1] = 200 */
salaries[1 + 1] = 300; /* ισοδύναμη με το salaries[2] = 300 */
a[2] = 3;
salaries[a[2]] = 400; /* ισοδύναμη με το salaries[3] = 400 */
Όταν χρησιμοποιείτε διατάξεις, κρατήστε στο νου σας το σχήμα αποθήκευσης στοιχείων: Σε μια
διάταξη με n στοιχεία, η επιτρεπόμενη περιοχή δεικτών είναι από το 0 έως το n-1. Εάν
χρησιμοποιήσετε την τιμή δείκτη n, θα έχετε λογικό σφάλμα στο πρόγραμμά σας. Ο
μεταγλωττιστής της C δεν αναγνωρίζει αν το πρόγραμμα χρησιμοποιεί ένα δείκτη διάταξης
εκτός των επιτρεπτών ορίων. Κατά συνέπεια, το πρόγραμμά σας μεταγλωττίζεται και συνδέεται
κανονικά αλλά οι εκτός ορίων τιμές γενικά δημιουργούν απρόβλεπτα αποτελέσματα και πολλές
φορές οδηγούν στην κατάρρευση του προγράμματος.
Παράδειγμα κώδικα 1: Χρήση μιας μονοδιάστατης διάταξης
#include <stdio.h>
#define MAX_SAL 6
int main() {
/* Δήλωση διάταξης για το μισθό έξι μηνών και μιας μεταβλητής μετρητή */
float salaries[MAX_SAL];
int idx;
2 / 11
A.2. Πολυδιάστατες Διατάξεις
Μία πολυδιάστατη διάταξη έχει περισσότερους από έναν δείκτες. Μία διάσταση δύο διαστάσεων
έχει δύο δείκτες, μία τρισδιάστατη διάταξη έχει τρεις δείκτες, κοκ. Δεν υπάρχει όριο στον αριθμό
των διαστάσεων που μπορεί να έχει μία διάταξη στην C.
Για παράδειγμα, για να γράψετε ένα πρόγραμμα που παίζει σκάκι θα μπορούσατε να
αναπαραστήσετε τη σκακιέρα σαν μία δισδιάστατη διάταξη με οκτώ γραμμές και οκτώ στήλες,
ως ακολούθως:
int chessboard[8][8];
Η διάταξη που προκύπτει έχει 64 στοιχεία: chessboard[0][0], chessboard[0][1], chessboard[0]
[2], ..., chessboard[7][6], chessboard[7][7]. Η δομή αυτής της δισδιάστατης διάταξης
απεικονίζεται στην εικόνα που ακολουθεί:
...
chessboard[0][0] chessboard[0][1] chessboard[0][7]
...
chessboard[1][0] chessboard[1][1] chessboard[1][7]
...
chessboard[2][0] chessboard[2][1] chessboard[2][7]
. . .
. . .
. . .
...
chessboard[7][0] chessboard[7][1] chessboard[7][7]
#define ROWS 2
#define COLS 2
int main(){
float a[ROWS][COLS], b[ROWS][COLS], c[ROWS][COLS];
int i, j;
3 / 11
for(j = 0; j < COLS; ++j){
printf("Enter a%d%d: ", i+1, j+1);
scanf("%f", &a[i][j]);
}
}
printf("Enter the elements of 2nd matrix\n");
for(i = 0; i < ROWS; ++i) {
for(j = 0; j < COLS; ++j){
printf("Enter b%d%d: ", i+1, j+1);
scanf("%f", &b[i][j]);
}
}
/* add arrays */
addArray(a, b, c);
printf("\nSum Of Matrix:\n");
printArray(c);
return 0;
}
void addArray(float a[][COLS], float b[][COLS], float c[][COLS]) {
int i, j;
for(i = 0; i < ROWS; ++i) {
for(j = 0; j < COLS; ++j) {
/* Writing the elements of multidimensional array using loop. */
c[i][j] = a[i][j] + b[i][j]; /* Sum of corresponding elements of two arrays. */
}
}
}
void printArray(float c[][COLS]) {
int i, j;
for(i = 0; i < ROWS; ++i) {
for(j = 0; j < COLS; ++j) {
printf("%.1f\t", c[i][j]);
}
printf("\n");
}
}
4 / 11
μεταβλητών. Ένα όνομα διάταξης πρέπει να είναι μοναδικό, δεν μπορεί να χρησιμοποιηθεί για
άλλη διάταξη ή για οποιονδήποτε άλλο προσδιοριστή (μεταβλητή, σταθερά, κοκ.). Οι δηλώσεις
έχουν την ίδια μορφή με τις δηλώσεις των υπολοίπων μεταβλητών, εκτός από το ότι ο αριθμός
των στοιχείων στη διάταξη θα πρέπει να περιβάλλεται από αγκύλες, αμέσως μετά το όνομα της
διάταξης.
Όταν δηλώνετε μία διάταξη, μπορείτε να καθορίσετε τον αριθμό των στοιχείων της με μία
κυριολεκτική σταθερά ή με μία συμβολική σταθερά, με τη ντιρεκτίβα #define. Έτσι, η ακόλουθη
δήλωση:
#define MONTHS 12
int array[MONTHS];
είναι ισοδύναμη με τη δήλωση:
int array[12];
5 / 11
αρχικών τιμών είναι ισοδύναμη με την προηγούμενη:
int array[3][2] = {{1, 2}, {3, 4}, {5, 6}};
Β. Αναδρομή
Ο όρος αναδρομή αναφέρεται σε μία κατάσταση στην οποία μία συνάρτηση καλεί τον ευατό της,
είτε άμεσα, είτε έμμεσα. Η έμμεση αναδρομή λαμβάνει χώρα όταν μία συνάρτηση καλεί μία άλλη
συνάρτηση η οποία κατόπιν καλεί την πρώτη συνάρτηση. Η C επιτρέπει τις αναδρομικές
συναρτήσεις οι οποίες οποίες μπορεί να είναι χρήσιμες σε περιπτώσεις κατά τις οποίες ένα
σύνθετο πρόβλημα μπορεί να διαχωριστεί σε πιο απλά υποπροβλήματα, κατόπιν να λυθούν αυτά
τα υποπροβήματα ξεχωριστά και στη συνέχεια να συνδυαστούν οι (μερικές) λύσεις των
υποπροβλημάτων για να προκύψει η τελική λύση.
Επειδή η αναδρομή είναι στην ουσία μία επαναληπτική διαδικασία (φανταστείτε την ως ένα βρόχο
επανάληψης) θα πρέπει να εξασφαλίζουμε ότι υπάρχει πάντα εντός της αναδρομικής συνάρτησης
μία συνθήκη τερματισμού, δηλαδή εξόδου από την επαναληπτική της κλήση, διαφορετικά θα
προκύψει ατέρμων βρόχος.
int main() {
int i, array[ARRAY_SIZE];
for (i = 0; i < ARRAY_SIZE; ++i) {
printf("Give a[%d]:", i+1);
scanf("%d", &array[i]);
}
printf("\n The array is:\n");
printArray(array, ARRAY_SIZE);
printf("\nThe sum of its elements is: %d", rSum(array, ARRAY_SIZE - 1) );
}
6 / 11
printf("a[%d] = %d\n", i, a[i]);
}
}
Αναφορές
1. Εγχειρίδιο της C, Peter Aitken & Bradley L. Jones.
7 / 11
Εκφώνηση Εργαστηριακής Άσκησης
Κατασκευάστε ένα πρόγραμμα το οποίο στη συνάρτηση main() θα δηλώνεται μια διάταξη δέκα
(10) ακεραίων και στη συνέχεια θα διαβάζει από το πληκτρολόγιο και θα εισάγει στη διάταξη
τους ακεραίους. Αφού ολοκληρωθεί η εισαγωγή των ακεραίων εκτυπώστε τα στοιχεία της
διάταξης. Στη συνέχεια γράψτε μία ΑΝΑΔΡΟΜΙΚΗ συνάρτηση που να ελέγχει εάν σε μία διάταξη
δέκα (10) ακεραίων υπάρχει τετράδα συνεχόμενων στοιχείων με ακεραίους Α, Β, Γ και Δ έτσι
ώστε να ισχύει
Α+Δ=Β+Γ
Για παράδειγμα, η διάταξη με τους ακέραιους 5 10 20 21 31 4 3 9 0 1 περιέχει την τριάδα 10 20
21 31 όπου ικανοποιείται η απαιτούμενη συνθήκη.
Η αναδρομική σας συνάρτηση να τυπώνει ένα μήνυμα για το αν η διάταξη εισόδου περιέχει μία
κατάλληλη τετράδα αριθμών, τη θέση του πρώτου στοιχείου της τετράδας καθώς και την
τετράδα αυτή. Η αναδρομική σας συνάρτηση να επιστρέφει τη θέση του πρώτου στοιχείου της
τετράδας. Σε περίπτωση αποτυχίας, να τυπώνεται κατάλληλο μήνυμα και η συνάρτηση να
επιστρέφει -1.
Η συνάρτηση που θα γράψετε θα είναι η:
τιμή_επιστροφής quadruple(ορίσματα)ορίσματα)
Για την υλοποίηση της αναδρομικής συνάρτησης και μόνο, δεν επιτρέπεται η χρήση
βρόχων επανάληψης καθώς και η χρήση καθολικών και static μεταβλητών.
8 / 11
πλήθους για κάθε κλήση θα λαμβάνει υπόψη και τις πράξεων που έγιναν από επιμέρους
αναδρομικές κλήσεις. Μπορείτε να χρησιμοποιήσετε μια καθολική μεταβλητή για το σκοπό αυτό
που θα την αρχικοποιείτε και θα την αυξάνετε κατάλληλα κάθε φορά. Τι παρατηρείτε; Ποια
από τις δύο συναρτήσεις είναι πιο οικονομική από πλευράς πλήθους εκτελούμενων
προσθέσεων;
Παρακάτω δίνουμε ενδεικτική έξοδο από το πρόγραμμα αυτό. Το όνομα της αναδρομικής
συνάρτησης είναι rfib και της μη αναδρομικής fib.
Please give a number to compute the corresponding value of the fibonacci sequence:
6
Computing rfib(6)
Computing rfib(5)
Computing rfib(4)
Computing rfib(3)
Computing rfib(2)
Computing rfib(1)
rfib(1) finished with result 1
Computing rfib(0)
rfib(0) finished with result 0
rfib(2) finished with result 1
Computing rfib(1)
rfib(1) finished with result 1
rfib(3) finished with result 2
Computing rfib(2)
Computing rfib(1)
rfib(1) finished with result 1
Computing rfib(0)
rfib(0) finished with result 0
rfib(2) finished with result 1
rfib(4) finished with result 3
Computing rfib(3)
Computing rfib(2)
Computing rfib(1)
rfib(1) finished with result 1
Computing rfib(0)
rfib(0) finished with result 0
rfib(2) finished with result 1
Computing rfib(1)
rfib(1) finished with result 1
rfib(3) finished with result 2
rfib(5) finished with result 5
Computing rfib(4)
Computing rfib(3)
Computing rfib(2)
Computing rfib(1)
rfib(1) finished with result 1
Computing rfib(0)
rfib(0) finished with result 0
rfib(2) finished with result 1
Computing rfib(1)
rfib(1) finished with result 1
rfib(3) finished with result 2
Computing rfib(2)
Computing rfib(1)
rfib(1) finished with result 1
Computing rfib(0)
rfib(0) finished with result 0
rfib(2) finished with result 1
rfib(4) finished with result 3
rfib(6) finished with result 8
60%
9 / 11
Value computed by recursive fibonacci function: 8, 12 additions performed
Computing fib(6)
fib(6) finished with result 8
ΤΡΙΤΗ ΕΠΕΚΤΑΣΗ - Όπως παρατηρήσατε στο πρόγραμμά σας και όπως φαίνεται και από την
προηγούμενη ενδεικτική εκτέλεση, το πλήθος των πράξεων της αναδρομικής συνάρτησης είναι
γενικά μεγαλύτερο από το πλήθος πράξεων της μη αναδρομικής. Μάλιστα η μεταξύ τους διαφορά
αυξάνει όσο αυξάνει και η τιμή εισόδου. Για να αντιμετωπίσουμε το πρόβλημα της ραγδαίας
αύξησης των επιπρόσθετων πράξεων που χρειάζεται να κάνουν οι αναδρομικές εκδοχές
συναρτήσεων όπως αυτή που μελετούμε μπορούμε να επιστρατεύσουμε την τεχνική του
δυναμικού προγραμματισμού (dynamic programming). Η τεχνική αυτή συνίσταται στην
αποθήκευση τιμών της συνάρτησης που έχουν ήδη υπολογιστεί ώστε όταν τις
ξαναχρειαστούμε να μην υπολογίζονται εκ νέου. Για παράδειγμα, στο επόμενο σχήμα
απεικονίζεται ο σταδιακός αναδρομικός υπολογισμός της ακολουθίας Fibonacci για αρχική είσοδο
6:
Βλέπουμε από το παραπάνω σχήμα ότι ο υπολογισμός για fib(2) γίνεται 5 φορές για fib(3) 3
φορές, και για fib(4) 2 φορές. Εφαρμόζοντας την τεχνική του δυναμικού προγραμματισμού, οι
περιττοί επανυπολογισμοί εξοικονομούνται, όπως απεικονίζει το επόμενο σχήμα:
Την τελευταία αυτή επέκταση, ζητείται να υλοποιήσετε μια νέα εκδοχή της αναδρομικής
ακολουθίας Fibonacci η οποία θα χρησιμοποιεί την τεχνική του δυναμικού προγραμματισμού. Για
το σκοπό αυτό θα πρέπει να ορίσετε μια διάταξη μέσα στη νέα συνάρτηση που θα κατασκευάσετε
η οποία θα αποθηκεύει τις τιμές που έχουν υπολογιστεί. Όταν καλείται η νέα αυτή συνάρτηση,
αρχικά θα ελέγχει αν η είσοδος που της δόθηκε αντιστοιχεί σε τιμή που έχει ήδη υπολογίσει. Αν
ναι, θα επιστρέφει την τιμή αυτή. Αν όχι, θα υπολογίζει αναδρομικά την τιμή, θα την αποθηκεύει
στη διάταξη των υπολογισμένων τιμών και στη συνέχεια θα την επιστρέφει. Σημειώστε ότι αν
ορίσετε μια διάταξη ως τοπική μεταβλητή μέσα στη νέα συνάρτηση, οι τιμές της δεν θα
διατηρούνται μεταξύ αναδρομικών κλήσεων. Εμείς, αντιθέτως, θέλουμε θέλουμε να διατηρούνται
για να έχουμε πρόσβαση στις τιμές που υπολογίστηκαν από προηγούμενες κλήσεις. Για να το
πετύχετε αυτό η διάταξη θα πρέπει να οριστεί ως static. Μπορείτε να θεωρήσετε ένα μέγιστο
μέγεθος ΜΑΧ_Ν που θα καθορίσετε στο πρόγραμμά σας με μια συμβολική σταθερά. Αυτό σημαίνει
ότι θα μπορείτε να υπολογίσετε τιμές μέχρι MAX_N για την ακολουθία Fibonacci. Επίσης, μπορείτε
να αρχικοποιήσετε τις τιμές αυτής της διάταξης σε 0 ώστε να μπορείτε να ελέγχετε εύκολα αν
μια τιμή εισόδου (μεγαλύτερη του 1) έχει υπολογιστεί ή όχι. Αν δεν έχει υπολογιστεί η αντίστοιχη
τιμή της διάταξης θα είναι 0.
10 / 11
Μάθετε να χρησιμοποιείτε τον debugger!
Σας θυμίζουμε για άλλη μία φορά ότι είναι πολύ σημαντικό να μάθετε να χρησιμοποιείτε τον
debugger για να διορθώνετε τα λογικά λάθη που κάνετε όταν κατασκευάζετε ένα πρόγραμμα. Για
σχετικές οδηγίες ανατρέξτε στις πέντε τελευταίες σελίδες στο “Εισαγωγικό Εγχειρίδιο
Code::Blocks” που σας έχουμε ήδη δώσει από τη δεύτερη εβδομάδα του μαθήματος. Υπάρχει για
να το κατεβάσετε στις εργαστηριακές σημειώσεις του μαθήματος. Τονίζουμε στο σημείο αυτό ότι
η χρήση του debugger θα σας βοηθήσει πολύ στην καλύτερη κατανόηση της αναδρομής καθώς θα
σας δώσει τη δυνατότητα να βλέπετε με τρόπο ιδιαίτερα παραστατικό πώς γίνονται οι κλήσεις
των αναδρομικών συναρτήσεων. Συνιστούμε ισχυρά να κάνετε αυτή τη μελέτη για τις
ασκήσεις αυτού του εργαστηρίου.
11 / 11