Παράδειγµα: Το τρίγωνο του Pascal 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 Να εκτυπωθούν οι πρώτες Ν σειρές του τριγώνου, χρησιµοποιώντας ένα πίνακα µεγέθους Ν στοιχείων (η Ν-οστή σειρά περιέχει Ν στοιχεία). Έστω Ν = 6 1 0 0 0 0 0 1 1 0 0 0 0 1 2 1 0 0 0 1 3 3 1 0 0 1 4 6 4 1 0 1 5 10 10 5 1 Αλγόριθµος Θα χρησιµοποιηθεί ένας πίνακας ακεραίων µεγέθους 6. Ο πίνακας θα αρχικοποιηθεί στη πρώτη σειρά του τριγώνου. Στη συνέχεια (αφού εκτυπωθεί η πρώτη σειρά), για κάθε µία από τις υπόλοιπες σειρές (2.. 6), υπολογίζεται η επόµενη σειρά από την προηγούµενή της και εκτυπώνεται. 20
Εποµένως υπάρχουν δύο υποπροβλήµατα: Εκτύπωση δεδοµένης σειράς Υπολογισµός δεδοµένης σειράς από την προηγούµενη σειρά Η εκτύπωση δεδοµένης σειράς αρχίζει από το πρώτο στοιχείο και προχωρεί µέχρι το τελευταίο στοιχείο της εν λόγω σειράς. Ο υπολογισµός δεδοµένης σειράς αρχίζει από το τελευταίο στοιχείο της εν λόγω σειράς και προχωρεί µέχρι το δεύτερο στοιχείο. #include <stdio.h> #define SIZE 6 /* Πρωτότυπα βοηθητικών συναρτήσεων */ /* Συνάρτηση για εκτύπωση δεδοµένης σειράς */ void print_row (const int table [], int row); /* Συνάρτηση για υπολογισµό δεδοµένης σειράς. Ο ορισµός θεωρεί ότι ο πίνακας περιέχει τη προηγούµενη σειρά */ void compute_row (int table[], int row); 21
/* Συνάρτηση main */ void main () { int pascal[size] = {1, 0, 0, 0, 0, 0, k; putchar( \n ); print_row(pascal,1); for (k = 2; k <= SIZE; k++) { compute_row(pascal,k); putchar( \n ); print_row(pascal,k); void print_row (const int table[], int row) { int k; for (k = 0; k < row; k++) printf( %4d, table[k]); void compute_row (int table[], int row) { int k; for (k = row 1; k > 0; k ) table[k] = table[k] + table[k 1]; Αποτέλεσµα προγράµµατος 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 22
Παράδειγµα: Στοίβα (stack) push pop Εάν υπάρχει όριο στη χωρητικότητα της στοίβας, ο πίνακας αποτελεί κατάλληλη δοµή για την υλοποίηση αυτής της έννοιας. #include <stdio.h> #define STACK_EMPTY 999 #define MAX_SIZE 100 void push (int stack[], int *top, int max_size, int item) { if (*top < max_size 1) stack[++(*top)] = item; Σηµείωση: stack[++(*top)] = item; σηµαίνει ++(*top); stack[*top] = item; 23
int pop (int stack[], int *top) { int item; if (*top < 0) item = STACK_EMPTY; else item = stack[(*top) ]; return item; Ερώτηση: Θα µπορούσε το µέρος else της εντολής επιλογής, να είχε διατυπωθεί ως item = stack[ (*top)]; Πρόβληµα: Εισδοχή µίας ακολουθίας ακεραίων, το πλήθος της οποίας δεν υπερβαίνει το MAX_SIZE, και η οποία τερµατίζεται από το 0, και εκτύπωσή της σε αντίστροφη σειρά. void main () { int stack[max_size], item, top = 1; printf( \nenter sequence, terminating with 0> ); scanf( %d, &item); while (item!= 0) { push(stack, &top, MAX_SIZE, item); scanf( %d, &item); printf( \nsequence in reverse order\n ); 24
item = pop (stack, &top); while (item!= STACK_EMPTY) { printf( %d, item); item = pop(stack, &top); Enter sequence terminating with 0> 56 78 42 9 0 Sequence in reverse order 9 42 78 56 Πολυδιάστατοι Πίνακες Multidimensional arrays Ένας πίνακας είναι πολυδιάστατος εάν έχει δύο ή περισσότερες διαστάσεις. char tictac [3][3]; Στήλη 0 1 2 Σειρά 0 X O X 1 O X O tictac[1][2] 2 O X X Όταν µία παράµετρος συνάρτησης είναι πολυδιάστατος πίνακας, µόνο η εξωτερική διάσταση µπορεί να είναι ανοικτή. 25
void fill_array (int table[][10], int size, int value) { int row, col; for (row = 0; row < size; row++) for (col = 0; col < 10; col++) table[row][col] = value; Σύνταξη: τύπος_στοιχείων ονοµασία_πίνακα [πλήθος 1 ] [πλήθος 2 ] [πλήθος n ]; Τα ατοµικά στοιχεία ενός πολυδιάστατου πίνακα αναφέρονται ως ονοµασία_πίνακα[0][0]..[0], ονοµασία_πίνακα[πλήθος 1 1] [πλήθος 2 1].. [πλήθος n 1]. Όπως και για µονοδιάστατους πίνακες, η ονοµασία ενός πολυδιάστατου πίνακα αντιπροσωπεύει τη διεύθυνση του πρώτου στοιχείου του πίνακα. Επίσης, πολυδιάστατοι πίνακες που αποτελούν ορίσµατα, δίνονται µέσω διευθύνσεως, δηλαδή µέσω της διεύθυνσης του πρώτου στοιχείου του πίνακα. double table [7][5][6]; 26
Ο πίνακας table έχει τρεις διαστάσεις και σύνολο 210 στοιχεία. Οι δείκτες της πρώτης διάστασης είναι 0..6, της δεύτερης 0..4 και της τρίτης 0..5. table[2][3][4] σηµαίνει το στοιχείο του πίνακα table που έχει δείκτη 2 στη πρώτη διάσταση (δηλαδή το τρίτο σε σειρά), έχει δείκτη 3 στη δεύτερη διάσταση και δείκτη 4 στη τρίτη διάσταση. Αρχικοποίηση πολυδιάστατων πινάκων char tictac [3][3] = {{,,,{,,,{,, ; Εναλλακτικά για strings: char tictac [3][3] = {,, ; Παράδειγµα: Είναι δεδοµένος πίνακας (σε σχέση µε το παιγνίδι tic-tac-toe) τελείως γεµάτος; int filled (char ttt_board[3][3]) { int row, col, ans = 1; for (row = 0; row < 3; row++) for (col = 0; col < 3; col++) if (ttt_board[row][col] == ) ans = 0; return ans; 27
Σηµείωση: Εάν χρησιµοποιηθεί ο προσδιοριστής const σε σχέση µε παράµετρο συνάρτησης που είναι πολυδιάστατος πίνακας, τότε και το αντίστοιχο όρισµα σε κλήσεις της συνάρτησης πρέπει να έχει δηλωθεί ως const. Αυτό είναι περιοριστικό. Πρόβληµα: Να ορισθεί συνάρτηση η οποία λαµβάνει ως παράµετρο δεδοµένο πίνακα που αντιπροσωπεύει τη διάταξη των επιλογών των δύο παιχτών στο παιγνίδι tic-tac-toe, και αποφασίζει εάν η διάταξη αποτελεί νίκη για έναν από τους παίκτες, και εάν ναι για ποιόν από τους δύο. ιάσπαση προβλήµατος σε υποπροβλήµατα Είναι τα περιεχόµενα δεδοµένης σειράς τα ίδια; Είναι τα περιεχόµενα δεδοµένης στήλης τα ίδια; Είναι όλα τα στοιχεία της µίας ή της άλλης διαγωνίου τα ίδια; Προσδιορισµός πιο βασικών υποπροβληµάτων Είναι όλα τα στοιχεία δεδοµένου µονοδιάστατου πίνακα τα ίδια; Αντιγραφή δεδοµένης στήλης δυσδιάστατου πίνακα σε µονοδιάστατο πίνακα. Αντιγραφή δεδοµένης διαγωνίου δυσδιάστατου πίνακα σε µονοδιάστατο πίνακα. 28
Σηµείωση: Μόνο τα στοιχεία της εξωτερικής διάστασης µπορεί να αναφερθούν ως ολότητες, σε σχέση µε πολυδιάστατους πίνακες. Εποµένως σχετικά µε δυσδιάστατους πίνακες, µπορεί να γίνει άµεση αναφορά στις σειρές του πίνακα, ως ολότητες (δηλαδή ως µονοδιάστατους πίνακες), αλλά όχι στις στήλες ή τις διαγωνίους του. char table [3][3]; table ισοδυναµεί µε τη διεύθυνση του πρώτου στοιχείου (table[0][0]). table[0] ισοδυναµεί µε τη διεύθυνση της πρώτης σειράς του πίνακα, δηλαδή και πάλι µε τη διεύθυνση του στοιχείου table[0][0] table[1] ισοδυναµεί µε τη διεύθυνση της δεύτερης σειράς του πίνακα, δηλαδή µε τη διεύθυνση του στοιχείου table[1][0]. table[2] ισοδυναµεί µε τη διεύθυνση της τρίτης σειράς του πίνακα, δηλαδή µε τη διεύθυνση του στοιχείου table[2][0]. 29
Ορισµός συναρτήσεων για βασικά υποπροβλήµατα /* Συνάρτηση same_elem: Είναι όλα τα στοιχεία του πίνακα τα ίδια; */ int same_elem (const char table[], int size) { int ans = 1, k; for (k = 1; k < size; k++) if (table[k]!= table[0]) ans = 0; return ans; /* Συνάρτηση copy_col: αντιγραφή δεδοµένης στήλης του δυσδιάστατου πίνακα. */ void copy_col (char table[3][3], char col_table[3], int col) { int k; for (k = 0; k < 3; k++) col_table[k] = table[k][col]; /* Συνάρτηση copy_diag1: Αντιγραφή κανονικής διαγωνίου του δυσδιάστατου πίνακα. */ void copy_diag1 (char table[3][3], char diag[3]) { int k; for (k = 0; k < 3; k++) col_table[k] = table[k][k]; /* Συνάρτηση copy_diag2: Αντιγραφή αντι-διαγωνίου του δυσδιάστατου πίνακα. */ void copy_diag2 (char table[3][3], char diag[3]) { int k; for (k = 0; k < 3; k++) col_table[k] = table[k][2 k]; 30
Συνάρτηση win ttt_b win Υπάρχει νίκη; νικητής (player) Η ρητή τιµή εξόδου είναι κατά πόσον υπάρχει νίκη (0, δεν υπάρχει νίκη, 1, νίκη βάσει σειράς, 2, νίκη βάσει στήλης, 3, νίκη βάσει διαγωνίου, και 4, νίκη βάσει αντι-διαγωνίου) Ο νικητής (player) είναι παράµετρος εξόδου int win (char ttt_b [3][3], char *player) { char auxt[3]; int k = 0; ans = 0; while (k < 3 && ans == 0) { /* νικούσα σειρά; */ if (ttt_b[k][0]!= && same_elem(ttt_b[k],3)) { ans = 1; *player = ttt_b[k][0]; k++; if (ans); /* υπάρχει νικητής βάσει κάποιας σειράς*/ else { k = 0; while (k < 3 && ans == 0) { /* νικούσα στήλη; */ copy_col(ttt_b, auxt, k); if (auxt[0]!= && same_elem(auxt,3)) { ans = 2; *player = auxt[0]; k++; 31
if (ans); /* υπάρχει νικητής βάσει κάποιας στήλης */ else {copy_diag1(ttt_b, auxt); /* νικούσα διαγώνιος; */ if (auxt[0]!= && same_elem(auxt,3)) { /* ναι */ ans = 3; *player = auxt[0]; else {copy_diag2(ttt_b, auxt); /* νικούσα αντι-διαγώνιος; */ if (auxt[0]!= && same_elem(auxt,3)) { /* ναι */ ans = 4; *player = auxt[0]; return ans; win same_elem copy_col copy_diag1 copy_diag2 οκιµή συνάρτησης win void main () { char board [3][3] = {,, ; char player; printf( \n%d, win(board, &player)); printf( %c, player); 32
Περιπτώσεις οκιµής board απάντηση {,, 0 { X, X, X 2 X { OOO,, 1 O { X, X, X 3 X { X, X, X 4 X κτλ 33
Παράδειγµα: Υπολογισµός µέσων όρων για µαθήµατα και φοιτητές marks φοιτητές 0 1 2 3 4 5 6 7 0 µέσοι 1 όροι µαθήµατα 2 µαθηµ. 3 4 5 µέσοι όροι φοιτητών marks [c][s] marks [c] ο βαθµός του φοιτητή s στο µάθηµα c οι βαθµοί όλων των φοιτητών στο µάθηµα c ιάσπαση Προβλήµατος Μέσοι Όροι Μαθηµάτων και Φοιτητών Εισδοχή Βαθµών από Αρχείο εδοµένων Υπολογισµός Μέσων Όρων για Φοιτ. & Μαθ. Εκτύπωση Βαθµών και Μέσων Όρων 34
Εισδοχή Βαθµών από Αρχείο εδοµένων Έστω ότι τα µαθήµατα είναι 5 και οι φοιτητές 7. Αρχείο MARKS 5.0 4.5 9.0 8.5 6.0 6.0 7.5 10.0 3.5 9.5 3.5 5.0 8.0 4.5 6.5 7.5 5.0 5.5 7.0 8.0 9.0 8.5 3.0 2.5 6.5 5.5 8.5 7.5 9.0 7.0 6.0 6.0 5.0 5.0 7.0 Κάθε σειρά δίνει τους βαθµούς ενός µαθήµατος. Κάθε στήλη δίνει τους βαθµούς ενός φοιτητή. #include <stdio.h> #define NUM_COURSES 5 #define NUM_STUDENTS 7 #define C_TOTAL NUM_COURSES + 1 #define S_TOTAL NUM_STUDENTS + 1 void get_marks (double marks [C_TOTAL] [S_TOTAL], char *filename) { int c, s; FILE *inp = fopen(filename, r ); for (c = 0; c < NUM_COURSES; c++) for (s = 0; s < NUM_STUDENTS; s++) fscanf(inp, %lf, &marks[c][s]); fclose(inp); 35
Υπολογισµός Μέσων Όρων για Μαθήµατα και Φοιτητές void compute_avers (double marks [C_TOTAL][S_TOTAL]) { int c, s; /* Αρχικοποίηση θέσεων που θα περιέχουν µέσους όρους */ for (c = 0; c < NUM_COURSES; c++) marks[c][num_students] = 0.0; for (s = 0; s < NUM_STUDENTS; s++) marks[num_courses][s] = 0.0; /* Επεξεργασία βαθµών στη σειρά µαθήµατα-φοιτητές. Κάθε βαθµός αφορά δύο µέσους όρους. */ for (c = 0; c < NUM_COURSES; c++) { for (s = 0; s < NUM_STUDENTS; s++) { marks[c][num_students] += marks[c][s]; marks[num_courses][s] += marks[c][s]; /* ολοκλήρωση αθροίσµατος για το µ.ο. του c */ marks[c][num_students] /= (double) NUM_STUDENTS; /* ολοκλήρωση αθροισµάτων για τους µ.ο. φοιτητών */ for (s = 0; s < NUM_STUDENTS; s++) marks[num_courses][s] /= (double) NUM_COURSES; 36
Εκτύπωση Βαθµών και Μέσων Όρων void display_marks_avers (double marks[c_total][s_total]) { int c, s; printf( \n\ncourse and Student Averages\n ); for (c = 0; c < C_TOTAL; c++) { printf( \n ); for (s = 0; s < S_TOTAL; s++) printf( %7.1f, marks[c][s]); Συνάρτηση main void main () { double marks[c_total][s_total]; char filename[30]; printf( \nenter filename: ); scanf( %s, filename); get_marks(marks, filename); compute_avers(marks); display_marks_avers(marks); 37
οκιµή Προγράµµατος Enter filename: MARKS Course and Student Averages 5.0 4.5 9.0 8.5 6.0 6.0 7.5 6.6 10.0 3.5 9.5 3.5 5.0 8.0 4.5 6.3 6.5 7.5 5.0 5.5 7.0 8.0 9.0 6.9 8.5 3.0 2.5 6.5 5.5 8.5 7.5 6.0 9.0 7.0 6.0 6.0 5.0 5.0 7.0 6.4 7.8 5.1 6.4 6.0 5.7 7.1 7.1 0.0 Παράδειγµα: Indexed Sort Index 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 Table 45 23 5 102 47 Η πρόσβαση στον πίνακα Table γίνεται διαµέσου του πίνακα Index. Τα περιεχόµενα του πίνακα Table δεν εναλλάσσονται. Το τι εναλλάσσονται είναι οι είσοδοι του πίνακα Index. 38
Μετά την ταξινόµηση η πρώτη σε σειρά είσοδος του Table, θα είναι η είσοδος Table[Index[0]] και η τελευταία η Table[Index[4]]. Επαναδιατύπωση συνάρτησης bsort void bsort (int pred(int,int), int table[], int index [], int size) { int Sorted, len = size 1; k; for (k = 0; k < size; k++) index[k] = k; do { Sorted = 1; for (k = 0; k < len; k++) if (!pred(table[index[k]],table[index[k+1]])) {swap(&index[k],&index[k+1]); Sorted = 0; len; while (!Sorted && len > 0); Ταξινόµηση του Table σε αύξουσα σειρά Index Table 0 2 0 45 1 1 1 23 2 0 2 5 3 4 3 102 4 3 4 47 39
Η σειρά δηλαδή είναι: Table[Index[0]] Table[2] 5 Table[Index[1]] Table[1] 23 Table[Index[2]] Table[0] 45 Table[Index[3]] Table[4] 47 Table[Index[4]] Table[3] 102 Συνάρτηση main void main () { int Table[] = 45,23,5,102,47], Index[5], k; bsort(lessp,table,index,5); printf( \n\n ); for (k = 0; k < 5; k++) printf( %d, Table[Index[k]]); Η ίδια µέθοδος µπορεί να εφαρµοσθεί σε σχέση µε οποιοδήποτε βασικό αλγόριθµο ταξινόµησης. Η χρήση αυτής της µεθόδου ενδείκνυται σε περιπτώσεις όπου οι είσοδοι του πίνακα που χρειάζεται να ταξινοµηθεί είναι ογκώδεις. Ως εκ τούτου είναι πολύ πιο αποδοτικό να εναλλάσσονται ακέραιοι παρά µεγάλου όγκου δεδοµένα. 40
Παράδειγµα: Το Παιγνίδι της Ζωής (Game of Life) X X X X X X Μία γενεά κυττάρων αντιπροσωπεύεται ως ένας πίνακας δύο διαστάσεων, όπου τα στοιχεία είναι κύτταρα. Κύτταρα είναι νεκρά ή ζωντανά (Χ). Κάθε κύτταρο έχει µέχρι οκτώ γείτονες που είναι τα κύτταρα που γειτονεύουν µε αυτό. Από κάποια δεδοµένη γενεά κυττάρων µπορεί να παραχθεί η επόµενη γενεά, βάσει των ακόλουθων κανόνων: 1. Η επόµενη γενεά είναι η ένωση των επιζησάντων και των νεογέννητων που υπολογίζονται ταυτόχρονα από τα ζωντανά κύτταρα της τρέχουσας γενεάς. 2. Ένα ζωντανό κύτταρο επιζεί στην επόµενη γενεά, µόνο εάν έχει δύο ή τρεις ζωντανούς γείτονες στην τρέχουσα γενεά (διαφορετικά πεθαίνει από µοναξιά ή από υπερπλήρωση). 41
3. Ένα πεθαµένο κύτταρο γίνεται ένα νεογέννητο, ζωντανό κύτταρο στην επόµενη γενεά, µόνο εάν έχει ακριβώς τρεις ζωντανούς γείτονες στην τρέχουσα γενεά. Πρόβληµα: Αρχίζοντας από κάποια δεδοµένη γενεά κυττάρων, διαδοχικά να παραχθούν και να εκτυπωθούν οι επόµενες Ν γενεές. #define ALIVE X #define DEAD #define SIZE 18 char generation[size][size]; Ανάλυση Προβλήµατος σε Υποπροβλήµατα Εισδοχή αρχικής γενεάς Υπολογισµός πλήθους ζωντανών γειτόνων, δεδοµένου κυττάρου σε δεδοµένη γενεά Υπολογισµός επόµενης γενεάς από τρέχουσα γενεά Εκτύπωση δεδοµένης γενεάς Αποθήκευση δεδοµένης γενεάς σε κάποιο αρχείο 42
Πρωτότυπα Αντίστοιχων Συναρτήσεων void EnterGen (char generation[size][size], char *filename); int NumAliveNeighbours (char generation[size][size], int xcoord_cell, int ycoord_cell); void ComputeGen (char cur_gen[size][size], char next_gen[size][size]); void DisplayGen(char generation[size][size]); void StoreGen(char generation[size][size], char *filename); Υπολογισµός πλήθους ζωντανών γειτόνων int NumAliveNeighbours (char generation[size][size], int xcoord_cell, int ycoord_cell) { int x, y, N; for (x = xcoord_cell 1; x <= xcoord_cell + 1, x++) return N; for (y = ycoord_cell 1; y <= ycoord + 1; y++) if (x >= 0 && x <= SIZE 1 && y >= 0 && y <= SIZE 1) if ((x!= xcoord_cell y!= ycoord_cell) && generation[x][y] == ALIVE) N++; 43
Παράδειγµα: Επαναφορά στη στοίβα stack 0 1 top 2.... cur_cap.. 0 MAX_SIZE 1 top_stack push cap #include <stdio.h> #define MAX_SIZE 100 #define STACK_EMPTY 999 44
void push (int * * top_stack, int *cap, int max_size, int item){ if (*cap == 0) { *(*top_stack) = item; ++(*cap); else if (*cap < max_size) { (*top_size)++; *(*top_stack) = item; ++(*cap); Σηµείωση: (*top_stack)++; *top_stack += sizeof(int); int pop (int * * top_stack, int *cap) { int item; if (*cap == 0) item = STACK_EMPTY; else { item = *(*top_stack); (*top_stack) ; (*cap) ; return item; 45
void main () { int stack[max_size], *top = &stack[0], cur_cap = 0, item; printf( \nenter sequence, terminating with 0> ); scanf( %d, &item); while(item!= 0){ push(&top, &cur_cap, MAX_SIZE, item); scanf( %d, &item); printf( \nsequence in reverse order\n ); item = pop(&top, &cur_cap); while (item!= STACK_EMPTY){ printf( %d, item); item = pop (&top, &cur_cap); Σηµείωση: *top = &stack[0]; *top = stack; Όµως *top = &stack; δεν είναι επιτρεπτή εντολή. 46
47