Οι αλγόριθμοι αναζήτησης είναι θεμελιώδη εργαλεία στον προγραμματισμό και χρησιμοποιούνται για την εύρεση στοιχείων μέσα σε δομές δεδομένων. Παρακάτω θα εξετάσουμε μερικούς κοινούς αλγόριθμους αναζήτησης και θα τους υλοποιήσουμε σε C# και ψευδογλώσσα.

1. Δυαδική Αναζήτηση

Περιγραφή

Η δυαδική αναζήτηση (Binary Search) είναι ένας αποτελεσματικός αλγόριθμος που χρησιμοποιείται για να βρει ένα στοιχείο σε έναν ταξινομημένο πίνακα. Λειτουργεί με την αρχή της διαίρεσης και κυριαρχίας (divide and conquer), επιτρέποντας τη μείωση του αριθμού των στοιχείων που πρέπει να εξεταστούν σε κάθε βήμα.

Βήματα της Δυαδικής Αναζήτησης

  1. Αρχικοποίηση:
    • Ορίζουμε δύο δείκτες: αριστερά (left) και δεξιά (right), που δείχνουν στην αρχή το πρώτο και το τελευταίο στοιχείο του πίνακα, αντίστοιχα.
  2. Διαίρεση:
    • Υπολογίζουμε το μέσο (middle) του πίνακα με τη συνθήκη: middle=(αριστεραˊ+δεξιαˊ)/2\text{middle} = \left(\text{αριστερά} + \text{δεξιά}\right) / 2middle=(αριστεραˊ+δεξιαˊ)/2
    • Ελέγχουμε αν το στοιχείο στη θέση middle είναι ίσο με το κλειδί που ψάχνουμε.
  3. Κατάκτηση:
    • Αν το στοιχείο στη θέση middle είναι το κλειδί, επιστρέφουμε τη θέση του.
    • Αν το κλειδί είναι μικρότερο από το στοιχείο στη θέση middle, επαναλαμβάνουμε τη διαδικασία για το αριστερό μισό του πίνακα.
    • Αν το κλειδί είναι μεγαλύτερο, επαναλαμβάνουμε τη διαδικασία για το δεξί μισό.
  4. Συνέχιση:
    • Συνεχίζουμε τη διαδικασία μέχρι να βρούμε το στοιχείο ή να μην υπάρχουν άλλα στοιχεία να εξετάσουμε (δηλαδή, όταν αριστερά > δεξιά).

Προϋποθέσεις

  • Ο πίνακας πρέπει να είναι ταξινομημένος. Αν δεν είναι, η δυαδική αναζήτηση δεν θα λειτουργήσει σωστά.

Χρόνος Εκτέλεσης

Η δυαδική αναζήτηση έχει πολύ καλύτερη απόδοση σε σύγκριση με την απλή γραμμική αναζήτηση. Ο χρόνος εκτέλεσης είναι:

  • O(log n), όπου n είναι ο αριθμός των στοιχείων στον πίνακα. Αυτό σημαίνει ότι ο αριθμός των στοιχείων που πρέπει να εξεταστούν μειώνεται στη μέση σε κάθε βήμα.

.

Ψευδογλώσσα

Διαδικασία Δυαδική_Αναζήτηση(πίνακας, αριστερά, δεξιά, κλειδί)
    Αν αριστερά > δεξιά τότε
        Επιστροφή -1
    Τέλος_αν
    
    μέσο <- (αριστερά + δεξιά) / 2
    
    Αν πίνακας[μέσο] = κλειδί τότε
        Επιστροφή μέσο
    Αλλιώς_αν πίνακας[μέσο] > κλειδί τότε
        Επιστροφή Δυαδική_Αναζήτηση(πίνακας, αριστερά, μέσο - 1, κλειδί)
    Αλλιώς
        Επιστροφή Δυαδική_Αναζήτηση(πίνακας, μέσο + 1, δεξιά, κλειδί)
    Τέλος_αν
Τέλος_Διαδικασίας

C#

using System;

class Program
{
    static void Main()
    {
        int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        int key = 7;
        int result = BinarySearch(array, 0, array.Length - 1, key);

        if (result != -1)
        {
            Console.WriteLine($"Element {key} found at index: {result}");
        }
        else
        {
            Console.WriteLine($"Element {key} not found in the array.");
        }
    }

    static int BinarySearch(int[] array, int left, int right, int key)
    {
        if (left > right)
            return -1;

        int middle = (left + right) / 2;

        if (array[middle] == key)
            return middle;
        else if (array[middle] > key)
            return BinarySearch(array, left, middle - 1, key);
        else
            return BinarySearch(array, middle + 1, right, key);
    }
}


2. Αναζήτηση σε Δέντρο (Binary Search Tree)

Περιγραφή

Τι είναι το Δυαδικό Δέντρο Αναζήτησης;

Ένα δυαδικό δέντρο αναζήτησης (Binary Search Tree – BST) είναι μια δομή δεδομένων που οργανώνει τα στοιχεία με τέτοιο τρόπο ώστε να επιτρέπει γρήγορες αναζητήσεις, προσθήκες και διαγραφές. Σε ένα BST, κάθε κόμβος έχει το πολύ δύο παιδιά, και πληρούνται οι παρακάτω ιδιότητες:

  1. Αριστερό Υποδέντρο: Όλοι οι κόμβοι του αριστερού υποδέντρου ενός κόμβου έχουν τιμές μικρότερες από την τιμή του κόμβου.
  2. Δεξί Υποδέντρο: Όλοι οι κόμβοι του δεξιού υποδέντρου έχουν τιμές μεγαλύτερες από την τιμή του κόμβου.
  3. Διπλή Ικανότητα: Αυτές οι ιδιότητες ισχύουν για κάθε κόμβο του δέντρου.

Δομή Δέντρου

Η δομή ενός BST μπορεί να αναπαρασταθεί ως εξής:

      15
     /  \
   10    20
   / \   / \
  8  12 17  25

Σε αυτό το δέντρο, ο κόμβος με τιμή 15 είναι η ρίζα. Οι κόμβοι 10 και 20 είναι τα παιδιά του, και συνεχίζουμε με τον ίδιο τρόπο για κάθε κόμβο.

Αναζήτηση σε Δέντρο

Η διαδικασία αναζήτησης σε ένα BST είναι απλή και ακολουθεί τη δομή του δέντρου. Ακολουθούν τα βήματα:

  1. Ξεκίνησε από τη ρίζα: Ξεκινάς την αναζήτηση από τον κόμβο ρίζας.
  2. Σύγκριση: Σύγκρινε την τιμή που ψάχνεις με την τιμή του τρέχοντος κόμβου.
    • Αν η τιμή είναι ίση, έχεις βρει τον κόμβο.
    • Αν η τιμή είναι μικρότερη, συνέχισε την αναζήτηση στο αριστερό υποδέντρο.
    • Αν η τιμή είναι μεγαλύτερη, συνέχισε την αναζήτηση στο δεξί υποδέντρο.
  3. Επανέλαβε: Επανέλαβε τη διαδικασία μέχρι να βρεις τον κόμβο ή μέχρι να φτάσεις σε ένα κενό (null) δείκτη.

Ψευδογλώσσα για Αναζήτηση σε Δέντρο

Διαδικασία Αναζήτηση_Δέντρου(κόμβος, τιμή)
    Αν κόμβος είναι null τότε
        Επιστροφή false
    Αν κόμβος.τιμή = τιμή τότε
        Επιστροφή true
    Αλλιώς αν τιμή < κόμβος.τιμή τότε
        Επιστροφή Αναζήτηση_Δέντρου(κόμβος.αριστερό, τιμή)
    Αλλιώς
        Επιστροφή Αναζήτηση_Δέντρου(κόμβος.δεξί, τιμή)
Τέλος_Διαδικασίας

Υλοποίηση σε C#

Ακολουθεί μια υλοποίηση της αναζήτησης σε ένα δυαδικό δέντρο αναζήτησης σε C#.

using System;

class Node
{
    public int Value;
    public Node Left;
    public Node Right;

    public Node(int value)
    {
        Value = value;
        Left = null;
        Right = null;
    }
}

class BinarySearchTree
{
    private Node root;

    public BinarySearchTree()
    {
        root = null;
    }

    // Μέθοδος για την εισαγωγή ενός κόμβου
    public void Insert(int value)
    {
        root = InsertRec(root, value);
    }

    private Node InsertRec(Node root, int value)
    {
        if (root == null)
        {
            root = new Node(value);
            return root;
        }
        if (value < root.Value)
            root.Left = InsertRec(root.Left, value);
        else if (value > root.Value)
            root.Right = InsertRec(root.Right, value);
        return root;
    }

    // Μέθοδος για αναζήτηση
    public bool Search(int value)
    {
        return SearchRec(root, value);
    }

    private bool SearchRec(Node root, int value)
    {
        if (root == null)
            return false;
        if (root.Value == value)
            return true;
        if (value < root.Value)
            return SearchRec(root.Left, value);
        return SearchRec(root.Right, value);
    }
}

class Program
{
    static void Main()
    {
        BinarySearchTree bst = new BinarySearchTree();

        // Εισαγωγή στοιχείων
        bst.Insert(15);
        bst.Insert(10);
        bst.Insert(20);
        bst.Insert(8);
        bst.Insert(12);
        bst.Insert(17);
        bst.Insert(25);

        // Δοκιμή αναζητήσεων
        Console.WriteLine(bst.Search(10) ? "Element 10 found." : "Element 10 not found.");
        Console.WriteLine(bst.Search(22) ? "Element 22 found." : "Element 22 not found.");
    }
}

Ψευδογλώσσα

Διαδικασία Αναζήτηση_Δέντρου(κόμβος, κλειδί)
    Αν κόμβος είναι NULL τότε
        Επιστροφή NULL
    Τέλος_αν
    
    Αν κόμβος.τιμή = κλειδί τότε
        Επιστροφή κόμβος
    Αλλιώς_αν κλειδί < κόμβος.τιμή τότε
        Επιστροφή Αναζήτηση_Δέντρου(κόμβος.αριστερός, κλειδί)
    Αλλιώς
        Επιστροφή Αναζήτηση_Δέντρου(κόμβος.δεξιός, κλειδί)
    Τέλος_αν
Τέλος_Διαδικασίας

C#

class TreeNode{    public int Value;    public TreeNode Left;    public TreeNode Right;    public TreeNode(int value)    {        Value = value;        Left = null;        Right = null;    }}class Program{    static void Main()    {        TreeNode root = new TreeNode(5);        root.Left = new TreeNode(3);        root.Right = new TreeNode(7);        int key = 3;        TreeNode result = SearchTree(root, key);        if (result != null)        {            Console.WriteLine($"Element {key} found.");        }        else        {            Console.WriteLine($"Element {key} not found.");        }    }    static TreeNode SearchTree(TreeNode node, int key)    {        if (node == null)            return null;        if (node.Value == key)            return node;        else if (key < node.Value)            return SearchTree(node.Left, key);        else            return SearchTree(node.Right, key);    }}

3. Αναζήτηση σε Γράφο (Depth-First Search)

Περιγραφή

Τι είναι η Αναζήτηση Βάθους;

Η αναζήτηση βάθους (Depth-First Search – DFS) είναι μια από τις πιο κοινές τεχνικές για την εξερεύνηση και αναζήτηση σε γραφήματα και δέντρα. Η βασική ιδέα πίσω από την DFS είναι να εξερευνήσουμε όσο το δυνατόν πιο βαθιά σε ένα υποδέντρο πριν επιστρέψουμε και εξερευνήσουμε άλλες διαδρομές.

Χαρακτηριστικά της DFS

  1. Εξερεύνηση Πρώτα σε Βάθος: Ξεκινά από έναν αρχικό κόμβο και εξερευνά όσο το δυνατόν πιο βαθιά σε κάθε διαδρομή προτού επιστρέψει.
  2. Στοίβα (Stack): Η DFS μπορεί να υλοποιηθεί με τη χρήση μιας στοίβας, είτε ρητά με μια δομή δεδομένων στοίβας είτε αναδρομικά μέσω της κλήσης της στοίβας της γλώσσας προγραμματισμού.
  3. Επίσκεψη Κόμβων: Κάθε κόμβος επισκέπτεται μία φορά, ελαχιστοποιώντας τη δυνατότητα βρόχων σε άκυρες επισκέψεις.

Βασική Υλοποίηση

Ψευδογλώσσα

Διαδικασία DFS(κόμβος, επισκέφτηκε)
    Αν κόμβος είναι null τότε
        Επιστροφή
    Τέλος_αν
    
    Επισκέφτηκε[κόμβος] <- true
    Εκτύπωσε κόμβος

    Για κάθε γειτονικό_κόμβο σε κόμβος.γειτονίες
        Αν όχι επισκέφτηκε[γειτονικό_κόμβο] τότε
            DFS(γειτονικό_κόμβο, επισκέφτηκε)
        Τέλος_αν
    Τέλος_Για
Τέλος_Διαδικασίας
 

Γραφική Αναπαράσταση

Ας δούμε ένα παράδειγμα γραφήματος:

    A
   / \
  B   C
 / \   \
D   E   F

Η DFS από τον κόμβο A μπορεί να επισκεφθεί τους κόμβους σε μια σειρά όπως A, B, D, E, C, F.

Υλοποίηση σε C#

Ακολουθεί μια υλοποίηση της αναζήτησης βάθους σε C#.

using System;
using System.Collections.Generic;

class Graph
{
    private Dictionary<string, List<string>> adjacencyList;

    public Graph()
    {
        adjacencyList = new Dictionary<string, List<string>>();
    }

    // Προσθήκη κόμβου
    public void AddNode(string node)
    {
        adjacencyList[node] = new List<string>();
    }

    // Προσθήκη άκρης (edge)
    public void AddEdge(string from, string to)
    {
        adjacencyList[from].Add(to);
    }

    // DFS μέθοδος
    public void DepthFirstSearch(string start)
    {
        var visited = new HashSet<string>();
        DFSUtil(start, visited);
    }

    private void DFSUtil(string node, HashSet<string> visited)
    {
        if (visited.Contains(node)) return;

        visited.Add(node);
        Console.WriteLine(node); // Εκτύπωση του επισκεπτόμενου κόμβου

        foreach (var neighbor in adjacencyList[node])
        {
            DFSUtil(neighbor, visited);
        }
    }
}

class Program
{
    static void Main()
    {
        Graph graph = new Graph();
        
        // Προσθήκη κόμβων
        graph.AddNode("A");
        graph.AddNode("B");
        graph.AddNode("C");
        graph.AddNode("D");
        graph.AddNode("E");
        graph.AddNode("F");

        // Προσθήκη ακμών
        graph.AddEdge("A", "B");
        graph.AddEdge("A", "C");
        graph.AddEdge("B", "D");
        graph.AddEdge("B", "E");
        graph.AddEdge("C", "F");

        // Εκτέλεση DFS
        Console.WriteLine("DFS Starting from A:");
        graph.DepthFirstSearch("A");
    }
}

Επεξήγηση του Κώδικα

  1. Graph Class: Ορίζει τη δομή του γραφήματος, με ένα λεξικό (dictionary) που αποθηκεύει τη λίστα γειτονιών για κάθε κόμβο.
  2. AddNode: Προσθέτει έναν κόμβο στο γράφο.
  3. AddEdge: Δημιουργεί μια κατεύθυνση (edge) μεταξύ δύο κόμβων.
  4. DepthFirstSearch: Ξεκινά τη διαδικασία αναζήτησης από έναν δεδομένο κόμβο.
  5. DFSUtil: Υλοποιεί τη λογική της αναζήτησης βάθους. Επισκέπτεται έναν κόμβο, τον προσθέτει στη συλλογή επισκεμμένων κόμβων, και αναζητά στους γειτονικούς κόμβους.

Συμπέρασμα

Η αναζήτηση βάθους (DFS) είναι μια σημαντική τεχνική για την εξερεύνηση γραφημάτων. Χρησιμοποιείται ευρέως σε αλγορίθμους και εφαρμογές, όπως σε προβλήματα αναζήτησης, ανίχνευσης κύκλων και εύρεσης συνδεδεμένων συνιστωσών. Η ικανότητα κατανόησης και υλοποίησης αυτής της τεχνικής είναι θεμελιώδης για την εργασία με δεδομένα που έχουν δομή γραφήματος.

Ψευδογλώσσα

Διαδικασία DFS(κόμβος, επισκέπτες)
    Αν κόμβος είναι NULL ή κόμβος είναι επισκέπτης τότε
        Επιστροφή
    Τέλος_αν

    Πρόσθεση κόμβου στους επισκέπτες
    Για κάθε γείτονα του κόμβου κάνε
        DFS(γείτονας, επισκέπτες)
    Τέλος_Για
Τέλος_Διαδικασίας

C#

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Dictionary<int, List<int>> graph = new Dictionary<int, List<int>>
        {
            { 1, new List<int> { 2, 3 } },
            { 2, new List<int> { 4 } },
            { 3, new List<int> { 5 } },
            { 4, new List<int> { } },
            { 5, new List<int> { } }
        };

        HashSet<int> visited = new HashSet<int>();
        DFS(1, graph, visited);
    }

    static void DFS(int node, Dictionary<int, List<int>> graph, HashSet<int> visited)
    {
        if (visited.Contains(node)) return;

        visited.Add(node);
        Console.WriteLine(node);

        foreach (var neighbor in graph[node])
        {
            DFS(neighbor, graph, visited);
        }
    }
}

Γραμμική Αναζήτηση (Linear Search)

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

Ψευδογλώσσα
Αλγόριθμος Γραμμική_Αναζήτηση(λίστα, στόχος)  Για κάθε στοιχείο στη λίστα    Αν στοιχείο == στόχος τότε      Επιστροφή Αληθές  Τέλος_αν  Επιστροφή ΨευδέςΤέλος_Αλγορίθμου

C#

bool LinearSearch(int[] list, int target)
{
    foreach (int element in list)
    {
        if (element == target)
        {
            return true;
        }
    }
    return false;
}