Στη C, όταν περνάμε μεταβλητές σε συναρτήσεις, περνάμε αντίγραφα (call by value). Αν θέλουμε η συνάρτηση να αλλάξει πραγματικά τις μεταβλητές μας, πρέπει να περάσουμε τις διευθύνσεις τους — δηλαδή δείκτες (call by reference).
3.1.1 Το Πρόβλημα — swap χωρίς Δείκτες
#include<stdio.h>/* ΛΑΘΟΣ swap — δεν αλλάζει τίποτα! */voidswap_wrong(inta, intb) {
inttemp = a;
a = b;
b = temp;
/* οι a, b είναι ΑΝΤΙΓΡΑΦΑ — αλλάζουν μόνο τοπικά */
}
intmain() {
intx = 5, y = 10;
swap_wrong(x, y);
printf("x=%d, y=%d\n", x, y); /* x=5, y=10 — δεν άλλαξαν! */return0;
}
x=5, y=10
3.1.2 Η Λύση — swap με Δείκτες
#include<stdio.h>/* ΣΩΣΤΟ swap — χρησιμοποιούμε δείκτες */voidswap(int *a, int *b) {
inttemp = *a; /* αποθηκεύουμε τιμή από τη διεύθυνση a */
*a = *b; /* βάζουμε στο a αυτό που έχει το b */
*b = temp; /* βάζουμε στο b αυτό που είχε το a */
}
intmain() {
intx = 5, y = 10;
printf("Πριν: x=%d, y=%d\n", x, y);
swap(&x, &y); /* περνάμε τις ΔΙΕΥΘΥΝΣΕΙΣ */printf("Μετά: x=%d, y=%d\n", x, y);
return0;
}
Πριν: x=5, y=10
Μετά: x=10, y=5
🔑 Κανόνας: Αν θέλεις μια συνάρτηση να αλλάξει τις τιμές μεταβλητών της main(), πρέπει:
• Στη δήλωση της συνάρτησης: οι παράμετροι να είναι δείκτες → void func(int *a)
• Στην κλήση της συνάρτησης: να περνάμε διευθύνσεις → func(&x)
• Μέσα στη συνάρτηση: να χρησιμοποιούμε *a για να διαβάσουμε/αλλάξουμε τιμή
3.2 Δείκτες και Πίνακες
Στη C, το όνομα ενός πίνακα είναι στην ουσία ένας δείκτης στο πρώτο στοιχείο του πίνακα. Αυτό σημαίνει ότι arr και &arr[0] δείχνουν στην ίδια θέση μνήμης.
#include<stdio.h>intmain() {
intarr[] = {10, 20, 30, 40, 50};
int *ptr = arr; /* ίδιο με: int *ptr = &arr[0]; *//* Πρόσβαση μέσω δείκτη */printf("*ptr = %d\n", *ptr); /* 10 — πρώτο στοιχείο */printf("*(ptr+1) = %d\n", *(ptr+1)); /* 20 — δεύτερο στοιχείο */printf("*(ptr+2) = %d\n", *(ptr+2)); /* 30 — τρίτο στοιχείο *//* Ισοδύναμα: arr[i] == *(arr + i) */for (inti = 0; i < 5; i++) {
printf("arr[%d] = %d | *(ptr+%d) = %d\n",
i, arr[i], i, *(ptr + i));
}
return0;
}
✅ Βασικός κανόνας: Η έκφραση arr[i] είναι ακριβώς ισοδύναμη με *(arr + i). Ο compiler μετατρέπει τη μία μορφή στην άλλη εσωτερικά. Η ptr + i δεν προσθέτει i bytes, αλλά i × sizeof(int) bytes, δηλαδή μετακινείται κατά i θέσεις.
3.2.1 Αριθμητική Δεικτών
Η C υποστηρίζει πράξεις σε δείκτες. Όταν προσθέτουμε ή αφαιρούμε έναν ακέραιο σε/από δείκτη, αυτός μετακινείται κατά τόσες θέσεις (όχι bytes):
#include<stdio.h>intmain() {
intarr[] = {10, 20, 30, 40};
int *p = arr; /* p δείχνει στο arr[0] */printf("*p = %d\n", *p); /* 10 */p++; /* p μετακινείται στο arr[1] */printf("*p = %d\n", *p); /* 20 */p += 2; /* p μετακινείται στο arr[3] */printf("*p = %d\n", *p); /* 40 */p--; /* p πάει πίσω στο arr[2] */printf("*p = %d\n", *p); /* 30 */return0;
}
*p = 10
*p = 20
*p = 40
*p = 30
3.3 Πέρασμα Πίνακα σε Συνάρτηση
Όταν περνάμε πίνακα σε συνάρτηση, στην ουσία περνάμε δείκτη στο πρώτο στοιχείο. Γι' αυτό η συνάρτηση μπορεί να αλλάξει τα στοιχεία του πίνακα:
#include<stdio.h>/*
* Συνάρτηση: double_array
* ------------------------
* Δέχεται:
* - έναν δείκτη σε ακέραιο πίνακα (int *arr)
* - το μέγεθος του πίνακα (int size)
*
* Κάνει:
* Πολλαπλασιάζει κάθε στοιχείο του πίνακα επί 2.
* Επειδή περνάμε τον πίνακα ως δείκτη, οι αλλαγές
* επηρεάζουν τον αρχικό πίνακα (όχι αντίγραφο).
*/voiddouble_array(int *arr, intsize) {
for (inti = 0; i < size; i++) {
arr[i] *= 2; // Ισοδύναμο με: arr[i] = arr[i] * 2;
}
}
/*
* Συνάρτηση: print_array
* -----------------------
* Εκτυπώνει όλα τα στοιχεία ενός πίνακα.
*/voidprint_array(int *arr, intsize) {
for (inti = 0; i < size; i++) {
printf("%d ", arr[i]); // Εκτύπωση κάθε στοιχείου
}
printf("\n"); // Αλλαγή γραμμής μετά την εκτύπωση
}
intmain() {
// Δήλωση και αρχικοποίηση πίνακα 5 ακεραίωνintnums[] = {1, 2, 3, 4, 5};
printf("Πριν: ");
print_array(nums, 5); // Εκτύπωση αρχικού πίνακαdouble_array(nums, 5);
// Περνάμε τον πίνακα στη συνάρτηση.// Ο πίνακας τροποποιείται απευθείας (μέσω δείκτη).printf("Μετά: ");
print_array(nums, 5); // Εκτύπωση τροποποιημένου πίνακαreturn0; // Επιτυχής τερματισμός προγράμματος
}
Πριν: 1 2 3 4 5
Μετά: 2 4 6 8 10
💡 Σημείωση: Η δήλωση void func(int *arr, int size) είναι ισοδύναμη με void func(int arr[], int size). Και οι δύο μορφές δέχονται δείκτη σε πίνακα. Ο compiler τις μεταχειρίζεται με τον ίδιο τρόπο.
3.4 Διαδραστικό Εργαλείο Δεικτών
Εισάγετε τιμή για το x και δείτε πώς λειτουργεί ο δείκτης:
📌 Εξερεύνηση Δεικτών
Εισάγετε τιμή και πατήστε Εκτέλεση...
3.5 Πλήρες Παράδειγμα Δήλωσης & Χρήσης Δεικτών
Ας δούμε ένα ολοκληρωμένο παράδειγμα που δείχνει όλους τους τρόπους δήλωσης και χρήσης δεικτών σε ένα πρόγραμμα:
#include<stdio.h>intmain() {
/* ═══════════════════════════════════════════
ΜΕΡΟΣ 1: Βασική δήλωση δεικτών
═══════════════════════════════════════════ */inta = 10; // κανονική μεταβλητή intfloatb = 3.14; // κανονική μεταβλητή floatcharc = 'A'; // κανονική μεταβλητή charint *pa = &a; // δείκτης σε int → δείχνει στο afloat *pb = &b; // δείκτης σε float → δείχνει στο bchar *pc = &c; // δείκτης σε char → δείχνει στο cprintf("=== ΜΕΡΟΣ 1: Βασική δήλωση ===\n");
printf("a = %d, &a = %p\n", a, &a);
printf("*pa = %d, pa = %p\n", *pa, pa);
printf("b = %.2f, *pb = %.2f\n", b, *pb);
printf("c = %c, *pc = %c\n\n", c, *pc);
/* ═══════════════════════════════════════════
ΜΕΡΟΣ 2: Αλλαγή τιμής μέσω δείκτη
═══════════════════════════════════════════ */printf("=== ΜΕΡΟΣ 2: Αλλαγή μέσω δείκτη ===\n");
printf("Πριν: a = %d\n", a);
*pa = 99; // αλλάζουμε το a μέσω του δείκτη paprintf("Μετά: a = %d\n\n", a); // εκτυπώνει 99!/* ═══════════════════════════════════════════
ΜΕΡΟΣ 3: Αλλαγή του δείκτη (νέος στόχος)
═══════════════════════════════════════════ */intx = 100, y = 200;
int *ptr = &x; // ptr δείχνει στο xprintf("=== ΜΕΡΟΣ 3: Αλλαγή στόχου δείκτη ===\n");
printf("ptr → x: *ptr = %d\n", *ptr); // 100ptr = &y; // τώρα ο ptr δείχνει στο yprintf("ptr → y: *ptr = %d\n\n", *ptr); // 200/* ═══════════════════════════════════════════
ΜΕΡΟΣ 4: NULL δείκτης (ασφάλεια)
═══════════════════════════════════════════ */int *safe_ptr = NULL; // αρχικοποίηση σε NULLprintf("=== ΜΕΡΟΣ 4: NULL δείκτης ===\n");
if (safe_ptr == NULL) {
printf("Ο δείκτης είναι NULL — δεν δείχνει πουθενά\n");
}
safe_ptr = &a; // τώρα δείχνει κάπου ασφαλέςif (safe_ptr != NULL) {
printf("Τώρα δείχνει στο a: *safe_ptr = %d\n", *safe_ptr);
}
return0;
}
=== ΜΕΡΟΣ 1: Βασική δήλωση ===
a = 10, &a = 0x7ffd5a30
*pa = 10, pa = 0x7ffd5a30
b = 3.14, *pb = 3.14
c = A, *pc = A
=== ΜΕΡΟΣ 2: Αλλαγή μέσω δείκτη ===
Πριν: a = 10
Μετά: a = 99
=== ΜΕΡΟΣ 3: Αλλαγή στόχου δείκτη ===
ptr → x: *ptr = 100
ptr → y: *ptr = 200
=== ΜΕΡΟΣ 4: NULL δείκτης ===
Ο δείκτης είναι NULL — δεν δείχνει πουθενά
Τώρα δείχνει στο a: *safe_ptr = 99
3.6 Δισδιάστατοι Πίνακες & Δείκτες
Ένας δισδιάστατος πίνακας (2D array) είναι στην ουσία ένας «πίνακας από πίνακες» — ένα πλέγμα με γραμμές και στήλες, σαν πίνακα βαθμολογίας ή σκακιέρα.
📊 Παράδειγμα: Ένας πίνακας int matrix[3][4] έχει 3 γραμμές και 4 στήλες — σύνολο 12 θέσεις. Στη μνήμη αποθηκεύεται γραμμή-γραμμή (row-major order), δηλαδή πρώτα η γραμμή 0, μετά η γραμμή 1, κ.ο.κ.
Ο 2D πίνακας αποθηκεύεται στη μνήμη σαν μία συνεχόμενη γραμμή. Ο compiler πρέπει να υπολογίσει πού βρίσκεται κάθε στοιχείο. Ας τον αναλύσουμε κομμάτι-κομμάτι:
• βάση: η διεύθυνση του πρώτου στοιχείου [0][0] στη μνήμη
• i × στήλες: «πόσα στοιχεία πρέπει να προσπεράσω για να φτάσω στη γραμμή i». Π.χ. αν έχω 4 στήλες και θέλω τη γραμμή 2, προσπερνάω 2×4 = 8 στοιχεία
• + j: μετά μετράω j θέσεις μέσα στη γραμμή για τη στήλη που θέλω
• × sizeof(int): πολλαπλασιάζω επί το μέγεθος κάθε στοιχείου σε bytes (4 bytes για int) γιατί η μνήμη μετράει σε bytes, όχι σε στοιχεία
Παράδειγμα: Για τον πίνακα matrix[3][4], πού βρίσκεται το matrix[2][1];
βάση + (2 × 4 + 1) × 4 = βάση + 9 × 4 = βάση + 36 bytes
Δηλαδή: προσπέρνα 2 γραμμές (8 στοιχεία) + 1 στήλη = 9ο στοιχείο × 4 bytes = 36 bytes μετά την αρχή.
3.6.3 Πέρασμα 2D Πίνακα σε Συνάρτηση
Όταν περνάμε δισδιάστατο πίνακα σε συνάρτηση, πρέπει να καθορίσουμε τον αριθμό των στηλών — ο compiler τον χρειάζεται για να υπολογίσει τη διεύθυνση κάθε στοιχείου:
#include<stdio.h>/*
* Συνάρτηση: print_matrix
* Δέχεται δισδιάστατο πίνακα με 4 στήλες.
* ΣΗΜΑΝΤΙΚΟ: ο αριθμός στηλών (4) ΠΡΕΠΕΙ να δηλωθεί!
*/voidprint_matrix(intmat[][4], introws) {
for (inti = 0; i < rows; i++) {
for (intj = 0; j < 4; j++) {
printf("%3d ", mat[i][j]);
}
printf("\n");
}
}
/*
* Συνάρτηση: sum_matrix
* Υπολογίζει το άθροισμα ΟΛΩΝ των στοιχείων.
*/intsum_matrix(intmat[][4], introws) {
intsum = 0;
for (inti = 0; i < rows; i++)
for (intj = 0; j < 4; j++)
sum += mat[i][j];
returnsum;
}
intmain() {
intgrades[3][4] = {
{85, 90, 78, 92}, // Φοιτητής 0: βαθμοί 4 μαθημάτων
{70, 65, 88, 73}, // Φοιτητής 1
{95, 87, 91, 82} // Φοιτητής 2
};
printf("Πίνακας βαθμών:\n");
print_matrix(grades, 3);
printf("\nΆθροισμα όλων: %d\n", sum_matrix(grades, 3));
/* Μέσος όρος κάθε φοιτητή */for (inti = 0; i < 3; i++) {
introw_sum = 0;
for (intj = 0; j < 4; j++)
row_sum += grades[i][j];
printf("Φοιτητής %d: Μ.Ο. = %.1f\n", i, row_sum / 4.0);
}
return0;
}
⚠️ Σημαντικό: Στη δήλωση παραμέτρου int mat[][4], η πρώτη διάσταση (γραμμές) μπορεί να παραλειφθεί, αλλά η δεύτερη (στήλες) είναι υποχρεωτική. Χωρίς αυτή, ο compiler δεν μπορεί να υπολογίσει πού βρίσκεται κάθε στοιχείο στη μνήμη.
3.6.4 Πρόσβαση 2D Πίνακα μέσω Δείκτη
Μπορούμε να χρησιμοποιήσουμε δείκτη για να προσπελάσουμε τα στοιχεία ενός 2D πίνακα, εκμεταλλευόμενοι το γεγονός ότι αποθηκεύονται συνεχόμενα στη μνήμη:
#include<stdio.h>intmain() {
intmatrix[2][3] = {
{10, 20, 30},
{40, 50, 60}
};
/* Δείκτης στο πρώτο στοιχείο (τύπου int) */int *ptr = &matrix[0][0];
printf("Με δείκτη (γραμμικά στη μνήμη):\n");
for (inti = 0; i < 2 * 3; i++) {
printf("*(ptr+%d) = %d\n", i, *(ptr + i));
}
printf("\nΑντιστοίχιση [γραμμή][στήλη]:\n");
introws = 2, cols = 3;
for (inti = 0; i < rows; i++) {
for (intj = 0; j < cols; j++) {
/* Τύπος: στοιχείο[i][j] = *(ptr + i*cols + j) */printf("[%d][%d]=%d ", i, j, *(ptr + i * cols + j));
}
printf("\n");
}
return0;
}
✅ Βασικός τύπος: Για να προσπελάσουμε το στοιχείο [i][j] ενός 2D πίνακα με δείκτη: *(ptr + i * αριθμός_στηλών + j). Αυτό δουλεύει γιατί ο πίνακας αποθηκεύεται γραμμή-γραμμή στη μνήμη.
3.7 Ασκήσεις Δεικτών
Δοκιμάστε πρώτα μόνοι σας — και μετά ανοίξτε τη λύση!
📝 Άσκηση 1: Εύρεση μέγιστου και ελάχιστου μέσω δεικτών
Γράψτε μια συνάρτηση find_min_max που δέχεται πίνακα ακεραίων και επιστρέφει μέσω δεικτών το μέγιστο και το ελάχιστο στοιχείο.
✅ Σημείωση: Η συνάρτηση find_min_max δέχεται δύο δείκτες (*min, *max) και γράφει τα αποτελέσματα απευθείας στις μεταβλητές της main μέσω *min = ... και *max = .... Έτσι μπορούμε να «επιστρέψουμε» δύο τιμές.
📝 Άσκηση 2: Αντιστροφή πίνακα με δείκτες
Γράψτε μια συνάρτηση reverse_array που αντιστρέφει τα στοιχεία ενός πίνακα χρησιμοποιώντας δείκτες και τη swap.
Η ταξινόμηση (sorting) είναι μία από τις πιο θεμελιώδεις λειτουργίες στην πληροφορική. Σκεφτείτε ότι ψάχνετε ένα όνομα σε τηλεφωνικό κατάλογο — αν τα ονόματα ήταν σε τυχαία σειρά, θα χρειαζόσασταν ώρες! Αλλά επειδή είναι αλφαβητικά ταξινομημένα, βρίσκετε αμέσως αυτό που θέλετε.
📋 Πού χρησιμοποιείται η ταξινόμηση;
• Εμφάνιση αποτελεσμάτων σε φθίνουσα/αύξουσα σειρά (π.χ. βαθμολογίες)
• Αναζήτηση — πολύ πιο γρήγορη σε ταξινομημένα δεδομένα
• Αφαίρεση διπλότυπων, εύρεση μεσαίας τιμής, στατιστικά κ.ά.
3.9 Bubble Sort (Ταξινόμηση Φυσαλίδας)
Ο Bubble Sort είναι ο πιο απλός αλγόριθμος ταξινόμησης. Η ιδέα είναι πολύ εύκολη: διατρέχουμε τον πίνακα επαναληπτικά και συγκρίνουμε κάθε ζεύγος γειτονικών στοιχείων. Αν είναι σε λάθος σειρά, τα αντιμεταθέτουμε. Αυτό επαναλαμβάνεται μέχρι να μην χρειαστεί καμία αντιμετάθεση.
🫧 Γιατί "Φυσαλίδα"; Γιατί σε κάθε πέρασμα, το μεγαλύτερο στοιχείο "ανεβαίνει" στη σωστή θέση — όπως μια φυσαλίδα που ανεβαίνει στην επιφάνεια.
3.9.1 Πώς Λειτουργεί — Βήμα-Βήμα
Ας ταξινομήσουμε τον πίνακα {5, 3, 8, 1, 2}:
Πέρασμα 1:
[53] 8 1 2 → [35] 8 1 2 ← swap!
3 [58] 1 2 → 3 [58] 1 2 ← ok
3 5 [81] 2 → 3 5 [18] 2 ← swap!
3 5 1 [82] → 3 5 1 [28] ← swap! Το 8 στη θέση του! ✅Πέρασμα 2:(δεν κοιτάμε πια το 8)
[35] 1 2 |8| → [35] 1 2 |8| ← ok
3 [51] 2 |8| → 3 [15] 2 |8| ← swap!
3 1 [52]|8| → 3 1 [25]|8| ← swap! Το 5 στη θέση του! ✅Πέρασμα 3:(δεν κοιτάμε 5,8)
[31] 2 |5||8| → [13] 2 |5||8| ← swap!
1 [32]|5||8| → 1 [23]|5||8| ← swap! Το 3 στη θέση του! ✅
💡 Γιατί κάθε πέρασμα ελέγχει λιγότερα στοιχεία;
Σε κάθε πέρασμα του Bubble Sort, το μεγαλύτερο στοιχείο «ανεβαίνει» σαν φυσαλίδα στο τέλος του πίνακα. Αυτό σημαίνει ότι μετά από κάθε πέρασμα, το τελευταίο στοιχείο είναι σίγουρα στη σωστή θέση — δεν χρειάζεται να το ξαναελέγξουμε.
• Πέρασμα 1: Ελέγχουμε 4 ζεύγη (θέσεις 0–4). Μετά το πέρασμα, το 8 είναι στη θέση 4 ✅
• Πέρασμα 2: Ελέγχουμε 3 ζεύγη (θέσεις 0–3). Δεν κοιτάμε το 8 γιατί είναι ήδη σωστά. Μετά, το 5 στη θέση 3 ✅
• Πέρασμα 3: Ελέγχουμε 2 ζεύγη (θέσεις 0–2). Δεν κοιτάμε τα 5, 8. Μετά, το 3 στη θέση 2 ✅
Γι' αυτό στον κώδικα γράφουμε j < n - 1 - i — το - i μειώνει τα ζεύγη σε κάθε πέρασμα, αφού τα τελευταία i στοιχεία είναι ήδη ταξινομημένα.
3.9.2 Κώδικας C
#include<stdio.h>// βιβλιοθήκη για printf// Συνάρτηση ταξινόμησης Bubble Sortvoidbubble_sort(int *arr, intn) {
// arr: δείκτης στον πίνακα (διεύθυνση πρώτου στοιχείου)// n: πλήθος στοιχείων του πίνακαfor (inti = 0; i < n - 1; i++) {
// Κάθε πέρασμα βάζει το μεγαλύτερο στοιχείο// στη σωστή θέση στο τέλος του πίνακαfor (intj = 0; j < n - 1 - i; j++) {
// Σύγκριση γειτονικών στοιχείων// Τα τελευταία i στοιχεία είναι ήδη σωστάif (arr[j] > arr[j + 1]) {
// Αν είναι σε λάθος σειρά,// κάνουμε ανταλλαγή (swap)inttemp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// Συνάρτηση εκτύπωσης πίνακαvoidprint_array(int *arr, intn) {
// Εκτυπώνει όλα τα στοιχεία του πίνακαfor (inti = 0; i < n; i++)
printf("%d ", arr[i]); // εκτύπωση στοιχείουprintf("\n"); // αλλαγή γραμμής
}
intmain() {
intarr[] = {64, 25, 12, 22, 11};
// Δήλωση και αρχικοποίηση πίνακαintn = 5;
// Μέγεθος πίνακα (αριθμός στοιχείων)printf("Πριν: ");
print_array(arr, n);
// Εκτύπωση πριν την ταξινόμησηbubble_sort(arr, n);
// Κλήση συνάρτησης ταξινόμησηςprintf("Μετά: ");
print_array(arr, n);
// Εκτύπωση μετά την ταξινόμησηreturn0;
// Τερματισμός προγράμματος
}
Σύγκριση γειτονικών στοιχείων — αντιμετάθεση αν χρειάζεται
Βήμα 0
Πατήστε ▶ Επόμενο για να ξεκινήσετε.
⚠️ Πολυπλοκότητα: Ο Bubble Sort κάνει O(n²) συγκρίσεις στη χειρότερη περίπτωση — δηλαδή για πίνακα 1000 στοιχείων, μπορεί να χρειαστεί ~1.000.000 συγκρίσεις! Είναι καλός για εκμάθηση αλλά αργός για μεγάλα δεδομένα.
3.10 Selection Sort (Ταξινόμηση Επιλογής)
Ο Selection Sort είναι ένας ακόμα απλός αλγόριθμος. Η ιδέα: σε κάθε βήμα, βρίσκουμε το ελάχιστο στοιχείο του μη ταξινομημένου τμήματος και το βάζουμε στη σωστή θέση.
📋 Βήμα 1: Εύρεση ελαχίστου
Σαρώνουμε τον μη ταξινομημένο πίνακα και βρίσκουμε το μικρότερο στοιχείο.
🔄 Βήμα 2: Τοποθέτηση
Αντιμεταθέτουμε το ελάχιστο με το πρώτο στοιχείο του μη ταξινομημένου τμήματος.
3.10.1 Πώς Λειτουργεί — Βήμα-Βήμα
Ας ταξινομήσουμε τον πίνακα {29, 10, 14, 37, 13}:
Πέρασμα 1: Ψάχνουμε ελάχιστο σε ΟΛΟΝ τον πίνακα (θέσεις 0–4)
[29 10 14 37 13] → ελάχιστο = 10 (θέση 1)
swap θέση 0 ↔ θέση 1
[10| 29 14 37 13] ← Το 10 στη θέση του! ✅Πέρασμα 2: Ψάχνουμε ελάχιστο στις θέσεις 1–4
10 |[29 14 37 13] → ελάχιστο = 13 (θέση 4)
swap θέση 1 ↔ θέση 4
[10 13| 14 37 29] ← Το 13 στη θέση του! ✅Πέρασμα 3: Ψάχνουμε ελάχιστο στις θέσεις 2–4
10 13 |[14 37 29] → ελάχιστο = 14 (θέση 2)
ήδη στη σωστή θέση — κανένα swap!
[10 13 14| 37 29] ← Το 14 στη θέση του! ✅Πέρασμα 4: Ψάχνουμε ελάχιστο στις θέσεις 3–4
10 13 14 |[37 29] → ελάχιστο = 29 (θέση 4)
swap θέση 3 ↔ θέση 4
[10 13 14 2937] ← Ταξινομημένος! 🎉
💡 Παρατηρήστε τη διαφορά με τον Bubble Sort:
• Ο Bubble Sort συγκρίνει γειτονικά ζεύγη και κάνει πολλά swaps σε κάθε πέρασμα.
• Ο Selection Sort βρίσκει πρώτα το ελάχιστο και κάνει μόνο 1 swap ανά πέρασμα (ή κανένα, αν ήδη είναι στη σωστή θέση). Γι' αυτό είναι πιο αποδοτικός σε swaps.
3.10.2 Κώδικας C
#include<stdio.h>/*
* Η συνάρτηση selection_sort ταξινομεί έναν πίνακα
* σε αύξουσα σειρά (από το μικρότερο στο μεγαλύτερο).
*
* arr = ο πίνακας
* n = πόσα στοιχεία έχει ο πίνακας
*/voidselection_sort(int *arr, intn) {
// Πηγαίνουμε θέση-θέση στον πίνακαfor (inti = 0; i < n - 1; i++) {
// Υποθέτουμε ότι το μικρότερο στοιχείο// βρίσκεται στην τωρινή θέση iintmin_idx = i;
// Ψάχνουμε στο υπόλοιπο του πίνακα// αν υπάρχει μικρότερο στοιχείοfor (intj = i + 1; j < n; j++) {
// Αν βρούμε μικρότερο αριθμό,// κρατάμε τη θέση τουif (arr[j] < arr[min_idx]) {
min_idx = j;
}
}
// Αν βρέθηκε μικρότερο στοιχείο,// κάνουμε ανταλλαγή (swap)if (min_idx != i) {
inttemp = arr[i]; // κρατάμε προσωρινά το arr[i]arr[i] = arr[min_idx]; // βάζουμε το μικρότερο στη θέση iarr[min_idx] = temp; // βάζουμε το παλιό arr[i] στη θέση του
}
}
}
intmain() {
// Δημιουργούμε έναν πίνακα με 5 αριθμούςintarr[] = {29, 10, 14, 37, 13};
intn = 5;
printf("Πριν: ");
// Εμφανίζουμε τον πίνακα πριν την ταξινόμησηfor (inti = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
// Ταξινομούμε τον πίνακαselection_sort(arr, n);
printf("Μετά: ");
// Εμφανίζουμε τον πίνακα μετά την ταξινόμησηfor (inti = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
return0; // Το πρόγραμμα τελείωσε σωστά
}
Πριν: 29 10 14 37 13
Μετά: 10 13 14 29 37
3.10.3 Σύγκριση Bubble vs Selection Sort
Χαρακτηριστικό
Bubble Sort
Selection Sort
Πολυπλοκότητα
O(n²)
O(n²)
Swaps
Πολλά (σε κάθε σύγκριση)
Λίγα (1 ανά πέρασμα)
Σταθερότητα
Σταθερός (stable)
Ασταθής (unstable)
Ιδέα
Σύγκριση γειτονικών
Εύρεση ελαχίστου
Καλύτερος για
Σχεδόν ταξινομημένα
Ελάχιστα swaps
3.11 Γραμμική Αναζήτηση (Linear Search)
Η πιο απλή μέθοδος αναζήτησης: ελέγχουμε ένα-ένα τα στοιχεία του πίνακα μέχρι να βρούμε αυτό που ψάχνουμε ή μέχρι να τελειώσει ο πίνακας.
3.11.1 Κώδικας C
#include<stdio.h>/* Επιστρέφει τη θέση (index) αν βρεθεί, αλλιώς -1 */intlinear_search(int *arr, intn, inttarget) {
for (inti = 0; i < n; i++) {
if (arr[i] == target) {
returni; /* βρέθηκε στη θέση i */
}
}
return -1; /* δεν βρέθηκε */
}
intmain() {
intarr[] = {4, 2, 7, 1, 9, 3};
intn = 6;
intpos = linear_search(arr, n, 7);
if (pos != -1)
printf("Βρέθηκε στη θέση %d\n", pos);
elseprintf("Δεν βρέθηκε\n");
pos = linear_search(arr, n, 5);
if (pos != -1)
printf("Βρέθηκε στη θέση %d\n", pos);
elseprintf("Δεν βρέθηκε\n");
return0;
}
Βρέθηκε στη θέση 2
Δεν βρέθηκε
✅ Πλεονεκτήματα: Η γραμμική αναζήτηση λειτουργεί σε οποιονδήποτε πίνακα — δεν χρειάζεται να είναι ταξινομημένος! Απλή υλοποίηση, αλλά αργή σε μεγάλους πίνακες: O(n).
3.12 Δυαδική Αναζήτηση (Binary Search)
Η δυαδική αναζήτηση είναι πολύ πιο γρήγορη από τη γραμμική, αλλά απαιτεί ο πίνακας να είναι ταξινομημένος. Η ιδέα: κοιτάμε το μεσαίο στοιχείο — αν είναι αυτό που ψάχνουμε, τέλεια! Αν όχι, ψάχνουμε μόνο στο μισό πίνακα.
📖 Παράδειγμα από τη ζωή: Όταν ψάχνετε μια λέξη στο λεξικό, δεν ξεκινάτε από τη σελίδα 1. Ανοίγετε κάπου στη μέση, κοιτάτε αν η λέξη σας είναι πριν ή μετά, και συνεχίζετε μόνο στο κατάλληλο μισό. Αυτό κάνει η δυαδική αναζήτηση!
#include<stdio.h>/* Δυαδική αναζήτηση — ΑΠΑΙΤΕΙ ταξινομημένο πίνακα!
Επιστρέφει θέση αν βρεθεί, αλλιώς -1 */intbinary_search(int *arr, intn, inttarget) {
intleft = 0;
intright = n - 1;
while (left <= right) {
intmid = (left + right) / 2;
if (arr[mid] == target) {
returnmid; /* βρέθηκε! */
}
else if (arr[mid] < target) {
left = mid + 1; /* ψάχνουμε δεξιά */
}
else {
right = mid - 1; /* ψάχνουμε αριστερά */
}
}
return -1; /* δεν βρέθηκε */
}
intmain() {
intarr[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91};
intn = 10;
intpos = binary_search(arr, n, 23);
if (pos != -1)
printf("Βρέθηκε στη θέση %d\n", pos);
elseprintf("Δεν βρέθηκε\n");
pos = binary_search(arr, n, 50);
if (pos != -1)
printf("Βρέθηκε στη θέση %d\n", pos);
elseprintf("Δεν βρέθηκε\n");
return0;
}
Βρέθηκε στη θέση 5
Δεν βρέθηκε
3.12.3 Animated Δυαδική Αναζήτηση
Δείτε πώς η δυαδική αναζήτηση βρίσκει το 23 στον πίνακα:
🔍 Binary Search — Αναζήτηση του 23
Πίνακας: [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
Βήμα 0
Πατήστε ▶ Επόμενο για να ξεκινήσετε.
3.12.4 Σύγκριση Γραμμικής vs Δυαδικής Αναζήτησης
Χαρακτηριστικό
Γραμμική (Linear)
Δυαδική (Binary)
Πολυπλοκότητα
O(n)
O(log n)
Πίνακας 1.000 στοιχείων
~1.000 βήματα
~10 βήματα!
Πίνακας 1.000.000 στοιχείων
~1.000.000 βήματα
~20 βήματα!
Απαιτεί ταξινόμηση;
ΟΧΙ
ΝΑΙ
Υλοποίηση
Πολύ απλή
Λίγο πιο σύνθετη
✅ Πρακτικός κανόνας: Αν θα κάνετε πολλές αναζητήσεις στα ίδια δεδομένα, αξίζει να ταξινομήσετε πρώτα και να χρησιμοποιείτε δυαδική αναζήτηση. Η αρχική ταξινόμηση κοστίζει O(n²) αλλά κάθε αναζήτηση μετά γίνεται O(log n) αντί O(n).
✅ Σημείωση: Η median (μεσαία τιμή) είναι πιο αξιόπιστη από τον μέσο όρο γιατί δεν επηρεάζεται από ακραίες τιμές. Π.χ. αν ο ένας φοιτητής πήρε 5 και οι υπόλοιποι 90+, η median δείχνει καλύτερα την "τυπική" επίδοση.
📝 Άσκηση 5: Selection Sort σε φθίνουσα σειρά
Τροποποιήστε τον Selection Sort ώστε να ταξινομεί σε φθίνουσα σειρά (από μεγαλύτερο σε μικρότερο).