Skip to content

Graph Algoritmaları

Genel Bakış

Graph (Graf) algoritmaları, düğümler (nodes/vertices) ve kenarlar (edges) ile oluşturulan veri yapıları üzerinde çalışan algoritmalardır. Sosyal ağlar, harita navigasyonu, bağımlılık yönetimi, ağ topolojisi gibi gerçek dünya problemlerinin büyük çoğunluğu graph yapıları ile modellenir.

Temel Kavramlar:

Kavram Açıklama
Vertex (Düğüm) Graphtaki her bir nokta
Edge (Kenar) İki düğüm arasındaki bağlantı
Directed (Yönlü) Kenarların yönü vardır (A → B, B'den A'ya gidilemez)
Undirected (Yönsüz) Kenarların yönü yoktur (A — B, her iki yönde gidilebilir)
Weighted (Ağırlıklı) Kenarlarda maliyet/mesafe bilgisi vardır
Unweighted (Ağırlıksız) Tüm kenarlar eşit ağırlıktadır
Degree Bir düğüme bağlı kenar sayısı
Path Bir düğümden diğerine giden kenar dizisi
Cycle Başlangıç düğümüne geri dönen path
DAG Directed Acyclic Graph — yönlü, döngüsüz graph

Adjacency List vs Adjacency Matrix:

Özellik Adjacency List Adjacency Matrix
Alan Karmaşıklığı O(V + E) O(V²)
Kenar varlığı kontrolü O(degree) O(1)
Tüm komşuları listeleme O(degree) O(V)
Seyrek graphlar için Daha verimli Verimsiz
Yoğun graphlar için Kabul edilebilir Daha verimli

Çoğu pratik uygulamada graphlar seyrektir (E << V²), bu yüzden Adjacency List tercih edilir.


Mülakat Soruları ve Cevapları

1. Soru

C# ile bir Graph veri yapısını nasıl temsil edersiniz? Adjacency List yaklaşımını generic bir sınıf olarak implemente edin.

Cevap:

Graph'ı C#'ta temsil etmenin en yaygın yolu Dictionary<T, List<T>> kullanmaktır. Bu yapı, her düğümün komşularını dinamik bir liste olarak tutar. Generic bir Graph sınıfı, hem yönlü hem de yönsüz graphları destekleyecek şekilde tasarlanabilir.

Örnek Kod:

public class Graph<T> where T : notnull
{
    private readonly Dictionary<T, List<T>> _adjacencyList;
    private readonly bool _isDirected;

    public Graph(bool isDirected = false)
    {
        _adjacencyList = new Dictionary<T, List<T>>();
        _isDirected = isDirected;
    }

    // Düğüm ekleme
    public void AddVertex(T vertex)
    {
        if (!_adjacencyList.ContainsKey(vertex))
            _adjacencyList[vertex] = new List<T>();
    }

    // Kenar ekleme
    public void AddEdge(T from, T to)
    {
        AddVertex(from);
        AddVertex(to);

        _adjacencyList[from].Add(to);

        // Yönsüz graphta her iki yönde de kenar eklenir
        if (!_isDirected)
            _adjacencyList[to].Add(from);
    }

    // Düğümün komşularını getir
    public IReadOnlyList<T> GetNeighbors(T vertex)
    {
        return _adjacencyList.TryGetValue(vertex, out var neighbors)
            ? neighbors.AsReadOnly()
            : Array.Empty<T>();
    }

    // Tüm düğümleri getir
    public IEnumerable<T> GetVertices() => _adjacencyList.Keys;

    // Kenar var mı kontrolü
    public bool HasEdge(T from, T to)
    {
        return _adjacencyList.TryGetValue(from, out var neighbors)
               && neighbors.Contains(to);
    }

    // Düğüm sayısı
    public int VertexCount => _adjacencyList.Count;

    // Kenar sayısı
    public int EdgeCount
    {
        get
        {
            int total = _adjacencyList.Values.Sum(list => list.Count);
            return _isDirected ? total : total / 2;
        }
    }

    public override string ToString()
    {
        var sb = new System.Text.StringBuilder();
        foreach (var (vertex, neighbors) in _adjacencyList)
        {
            sb.Append($"{vertex} -> [{string.Join(", ", neighbors)}]");
            sb.AppendLine();
        }
        return sb.ToString();
    }
}

// Kullanım örneği
var graph = new Graph<string>(isDirected: false);
graph.AddEdge("A", "B");
graph.AddEdge("A", "C");
graph.AddEdge("B", "D");
graph.AddEdge("C", "D");
graph.AddEdge("D", "E");

Console.WriteLine(graph);
// A -> [B, C]
// B -> [A, D]
// C -> [A, D]
// D -> [B, C, E]
// E -> [D]

2. Soru

BFS (Breadth-First Search) algoritmasını açıklayın ve C# ile implemente edin. Yönsüz bir graphta iki düğüm arasındaki en kısa yolu (edge sayısı olarak) bulun.

Cevap:

BFS, bir graph üzerinde başlangıç düğümünden itibaren önce tüm komşuları, sonra komşuların komşularını ziyaret eden seviye seviye bir arama algoritmasıdır. Queue (kuyruk) veri yapısını kullanır. Ağırlıksız graphlarda kenar sayısı bakımından en kısa yolu garanti eder çünkü düğümleri uzaklık sırasına göre keşfeder.

Zaman Karmaşıklığı: O(V + E) Alan Karmaşıklığı: O(V)

Örnek Kod:

public class BfsAlgorithms
{
    // Temel BFS — tüm erişilebilir düğümleri ziyaret et
    public static List<T> BreadthFirstSearch<T>(
        Dictionary<T, List<T>> graph,
        T start) where T : notnull
    {
        var visited = new HashSet<T>();
        var queue = new Queue<T>();
        var result = new List<T>();

        visited.Add(start);
        queue.Enqueue(start);

        while (queue.Count > 0)
        {
            var current = queue.Dequeue();
            result.Add(current);

            foreach (var neighbor in graph.GetValueOrDefault(current, new List<T>()))
            {
                if (!visited.Contains(neighbor))
                {
                    visited.Add(neighbor);
                    queue.Enqueue(neighbor);
                }
            }
        }

        return result;
    }

    // En kısa yolu bul (unweighted graph)
    public static List<T>? ShortestPath<T>(
        Dictionary<T, List<T>> graph,
        T start,
        T end) where T : notnull
    {
        if (EqualityComparer<T>.Default.Equals(start, end))
            return new List<T> { start };

        var visited = new HashSet<T> { start };
        // Her düğüm için önceki düğümü sakla (path rekonstrüksiyonu için)
        var parent = new Dictionary<T, T>();
        var queue = new Queue<T>();
        queue.Enqueue(start);

        while (queue.Count > 0)
        {
            var current = queue.Dequeue();

            foreach (var neighbor in graph.GetValueOrDefault(current, new List<T>()))
            {
                if (visited.Contains(neighbor))
                    continue;

                visited.Add(neighbor);
                parent[neighbor] = current;

                if (EqualityComparer<T>.Default.Equals(neighbor, end))
                    return ReconstructPath(parent, start, end);

                queue.Enqueue(neighbor);
            }
        }

        return null; // Yol bulunamadı
    }

    // BFS ile seviye (level) bazlı dolaşma
    public static List<List<T>> BfsLevelOrder<T>(
        Dictionary<T, List<T>> graph,
        T start) where T : notnull
    {
        var levels = new List<List<T>>();
        var visited = new HashSet<T> { start };
        var queue = new Queue<T>();
        queue.Enqueue(start);

        while (queue.Count > 0)
        {
            int levelSize = queue.Count;
            var currentLevel = new List<T>();

            for (int i = 0; i < levelSize; i++)
            {
                var node = queue.Dequeue();
                currentLevel.Add(node);

                foreach (var neighbor in graph.GetValueOrDefault(node, new List<T>()))
                {
                    if (!visited.Contains(neighbor))
                    {
                        visited.Add(neighbor);
                        queue.Enqueue(neighbor);
                    }
                }
            }

            levels.Add(currentLevel);
        }

        return levels;
    }

    private static List<T> ReconstructPath<T>(
        Dictionary<T, T> parent,
        T start,
        T end) where T : notnull
    {
        var path = new List<T>();
        var current = end;

        while (!EqualityComparer<T>.Default.Equals(current, start))
        {
            path.Add(current);
            current = parent[current];
        }

        path.Add(start);
        path.Reverse();
        return path;
    }
}

// Kullanım
var graph = new Dictionary<string, List<string>>
{
    ["A"] = new List<string> { "B", "C" },
    ["B"] = new List<string> { "A", "D", "E" },
    ["C"] = new List<string> { "A", "F" },
    ["D"] = new List<string> { "B" },
    ["E"] = new List<string> { "B", "F" },
    ["F"] = new List<string> { "C", "E" }
};

var path = BfsAlgorithms.ShortestPath(graph, "A", "F");
Console.WriteLine(string.Join(" -> ", path!));
// A -> C -> F

var levels = BfsAlgorithms.BfsLevelOrder(graph, "A");
for (int i = 0; i < levels.Count; i++)
    Console.WriteLine($"Seviye {i}: [{string.Join(", ", levels[i])}]");
// Seviye 0: [A]
// Seviye 1: [B, C]
// Seviye 2: [D, E, F]

3. Soru

DFS (Depth-First Search) algoritmasını hem recursive hem de iterative olarak implemente edin. Pre-order ve post-order dolaşma arasındaki fark nedir?

Cevap:

DFS, bir pathı mümkün olduğunca derinlemesine takip eden, sonra geri dönerek (backtrack) diğer yolları deneyen bir arama algoritmasıdır. Recursive implementasyonda call stack kullanılır; iterative versiyonda ise explicit bir stack yapısı kullanılır.

  • Pre-order: Düğüm önce işlenir, sonra komşulara geçilir (ziyaret sırası önemli olduğunda)
  • Post-order: Önce tüm komşular işlenir, sonra düğüm işlenir (topological sort için kullanılır)

Zaman Karmaşıklığı: O(V + E)

Örnek Kod:

public class DfsAlgorithms
{
    // Recursive DFS — pre-order
    public static List<T> DfsRecursive<T>(
        Dictionary<T, List<T>> graph,
        T start) where T : notnull
    {
        var visited = new HashSet<T>();
        var result = new List<T>();
        DfsHelper(graph, start, visited, result);
        return result;
    }

    private static void DfsHelper<T>(
        Dictionary<T, List<T>> graph,
        T current,
        HashSet<T> visited,
        List<T> result) where T : notnull
    {
        visited.Add(current);
        result.Add(current); // Pre-order: önce işle

        foreach (var neighbor in graph.GetValueOrDefault(current, new List<T>()))
        {
            if (!visited.Contains(neighbor))
                DfsHelper(graph, neighbor, visited, result);
        }
    }

    // Recursive DFS — post-order (Topological Sort için temel)
    public static List<T> DfsPostOrder<T>(
        Dictionary<T, List<T>> graph,
        T start) where T : notnull
    {
        var visited = new HashSet<T>();
        var result = new List<T>();
        DfsPostOrderHelper(graph, start, visited, result);
        return result;
    }

    private static void DfsPostOrderHelper<T>(
        Dictionary<T, List<T>> graph,
        T current,
        HashSet<T> visited,
        List<T> result) where T : notnull
    {
        visited.Add(current);

        foreach (var neighbor in graph.GetValueOrDefault(current, new List<T>()))
        {
            if (!visited.Contains(neighbor))
                DfsPostOrderHelper(graph, neighbor, visited, result);
        }

        result.Add(current); // Post-order: sonra işle
    }

    // Iterative DFS — explicit stack kullanımı
    public static List<T> DfsIterative<T>(
        Dictionary<T, List<T>> graph,
        T start) where T : notnull
    {
        var visited = new HashSet<T>();
        var stack = new Stack<T>();
        var result = new List<T>();

        stack.Push(start);

        while (stack.Count > 0)
        {
            var current = stack.Pop();

            if (visited.Contains(current))
                continue;

            visited.Add(current);
            result.Add(current);

            // Komşuları ters sırada ekle (sol-sağ sırasını korumak için)
            var neighbors = graph.GetValueOrDefault(current, new List<T>());
            for (int i = neighbors.Count - 1; i >= 0; i--)
            {
                if (!visited.Contains(neighbors[i]))
                    stack.Push(neighbors[i]);
            }
        }

        return result;
    }

    // Tüm düğümleri DFS ile dolaş (disconnected graph desteği)
    public static List<T> DfsAll<T>(
        Dictionary<T, List<T>> graph) where T : notnull
    {
        var visited = new HashSet<T>();
        var result = new List<T>();

        foreach (var vertex in graph.Keys)
        {
            if (!visited.Contains(vertex))
                DfsHelper(graph, vertex, visited, result);
        }

        return result;
    }
}

// Kullanım
var graph = new Dictionary<string, List<string>>
{
    ["A"] = new List<string> { "B", "C" },
    ["B"] = new List<string> { "D", "E" },
    ["C"] = new List<string> { "F" },
    ["D"] = new List<string>(),
    ["E"] = new List<string> { "F" },
    ["F"] = new List<string>()
};

var preOrder = DfsAlgorithms.DfsRecursive(graph, "A");
Console.WriteLine("Pre-order:  " + string.Join(", ", preOrder));
// Pre-order:  A, B, D, E, F, C

var postOrder = DfsAlgorithms.DfsPostOrder(graph, "A");
Console.WriteLine("Post-order: " + string.Join(", ", postOrder));
// Post-order: D, F, E, B, F, C, A  (F ve C sırası bağımlılığa göre)

var iterative = DfsAlgorithms.DfsIterative(graph, "A");
Console.WriteLine("Iterative:  " + string.Join(", ", iterative));
// Iterative:  A, B, D, E, F, C

4. Soru

Yönsüz bir graphta döngü (cycle) tespiti nasıl yapılır? DFS kullanarak C# implementasyonu yapın.

Cevap:

Yönsüz graphlarda döngü tespiti için DFS kullanılırken, bir düğümün zaten ziyaret edilmiş başka bir düğüme bağlandığını kontrol ederiz. Ancak dikkat edilmesi gereken nokta: geldiğimiz düğümü (parent) döngü olarak saymamamız gerekir, çünkü yönsüz graphta her kenar her iki yönde de mevcuttur.

Örnek Kod:

public class CycleDetection
{
    // Yönsüz graphta döngü tespiti
    public static bool HasCycleUndirected<T>(
        Dictionary<T, List<T>> graph) where T : notnull
    {
        var visited = new HashSet<T>();

        // Her bileşen için ayrı ayrı kontrol et (disconnected graph)
        foreach (var vertex in graph.Keys)
        {
            if (!visited.Contains(vertex))
            {
                if (DfsDetectCycleUndirected(graph, vertex, default, visited))
                    return true;
            }
        }

        return false;
    }

    private static bool DfsDetectCycleUndirected<T>(
        Dictionary<T, List<T>> graph,
        T current,
        T? parent,
        HashSet<T> visited) where T : notnull
    {
        visited.Add(current);

        foreach (var neighbor in graph.GetValueOrDefault(current, new List<T>()))
        {
            if (!visited.Contains(neighbor))
            {
                if (DfsDetectCycleUndirected(graph, neighbor, current, visited))
                    return true;
            }
            else if (!EqualityComparer<T>.Default.Equals(neighbor, parent))
            {
                // Ziyaret edilmiş ama parent değil — döngü var!
                return true;
            }
        }

        return false;
    }

    // Yönlü graphta döngü tespiti — recursion stack takibi
    public static bool HasCycleDirected<T>(
        Dictionary<T, List<T>> graph) where T : notnull
    {
        var visited = new HashSet<T>();
        var recursionStack = new HashSet<T>(); // Mevcut DFS pathındaki düğümler

        foreach (var vertex in graph.Keys)
        {
            if (!visited.Contains(vertex))
            {
                if (DfsDetectCycleDirected(graph, vertex, visited, recursionStack))
                    return true;
            }
        }

        return false;
    }

    private static bool DfsDetectCycleDirected<T>(
        Dictionary<T, List<T>> graph,
        T current,
        HashSet<T> visited,
        HashSet<T> recursionStack) where T : notnull
    {
        visited.Add(current);
        recursionStack.Add(current);

        foreach (var neighbor in graph.GetValueOrDefault(current, new List<T>()))
        {
            if (!visited.Contains(neighbor))
            {
                if (DfsDetectCycleDirected(graph, neighbor, visited, recursionStack))
                    return true;
            }
            else if (recursionStack.Contains(neighbor))
            {
                // Mevcut DFS pathında — back edge — döngü var!
                return true;
            }
        }

        recursionStack.Remove(current); // Backtrack sırasında stack'ten çıkar
        return false;
    }
}

// Kullanım — yönsüz
var undirectedGraph = new Dictionary<string, List<string>>
{
    ["A"] = new List<string> { "B", "C" },
    ["B"] = new List<string> { "A", "D" },
    ["C"] = new List<string> { "A", "D" }, // A-B-D-C-A döngüsü oluşturur
    ["D"] = new List<string> { "B", "C" }
};

Console.WriteLine(CycleDetection.HasCycleUndirected(undirectedGraph)); // True

// Kullanım — yönlü
var directedGraph = new Dictionary<string, List<string>>
{
    ["A"] = new List<string> { "B" },
    ["B"] = new List<string> { "C" },
    ["C"] = new List<string> { "A" }, // A -> B -> C -> A döngüsü
    ["D"] = new List<string> { "C" }
};

Console.WriteLine(CycleDetection.HasCycleDirected(directedGraph)); // True

5. Soru

Topological Sort (Topolojik Sıralama) nedir? DAG üzerinde Kahn's algoritması (BFS tabanlı) ile C# implementasyonu yapın. Build order sorununu çözün.

Cevap:

Topological Sort, yönlü döngüsüz bir graphta (DAG) düğümleri, her düğümün bağımlı olduğu düğümlerden sonra gelecek şekilde sıralamaktır. Yazılım build sistemleri, görev zamanlama (task scheduling), ders önkoşul planlaması gibi bağımlılık problemlerinde kullanılır.

Kahn's Algoritması: Her düğümün in-degree (kendisine gelen kenar sayısı) takip edilir. In-degree 0 olan düğümler sıraya alınır, işlendikçe komşuların in-degree değeri azaltılır.

Örnek Kod:

public class TopologicalSort
{
    // Kahn's Algoritması — BFS tabanlı
    public static List<T>? KahnTopologicalSort<T>(
        Dictionary<T, List<T>> graph) where T : notnull
    {
        // Her düğümün in-degree değerini hesapla
        var inDegree = new Dictionary<T, int>();

        foreach (var vertex in graph.Keys)
            inDegree.TryAdd(vertex, 0);

        foreach (var (_, neighbors) in graph)
        {
            foreach (var neighbor in neighbors)
            {
                inDegree[neighbor] = inDegree.GetValueOrDefault(neighbor, 0) + 1;
            }
        }

        // In-degree 0 olan tüm düğümleri kuyruğa ekle
        var queue = new Queue<T>();
        foreach (var (vertex, degree) in inDegree)
        {
            if (degree == 0)
                queue.Enqueue(vertex);
        }

        var result = new List<T>();

        while (queue.Count > 0)
        {
            var current = queue.Dequeue();
            result.Add(current);

            foreach (var neighbor in graph.GetValueOrDefault(current, new List<T>()))
            {
                inDegree[neighbor]--;

                if (inDegree[neighbor] == 0)
                    queue.Enqueue(neighbor);
            }
        }

        // Tüm düğümler işlenemediyse döngü var demektir
        return result.Count == graph.Count ? result : null;
    }

    // DFS tabanlı Topological Sort
    public static List<T>? DfsTopologicalSort<T>(
        Dictionary<T, List<T>> graph) where T : notnull
    {
        var visited = new HashSet<T>();
        var recursionStack = new HashSet<T>();
        var stack = new Stack<T>();

        foreach (var vertex in graph.Keys)
        {
            if (!visited.Contains(vertex))
            {
                if (!DfsHelper(graph, vertex, visited, recursionStack, stack))
                    return null; // Döngü tespit edildi
            }
        }

        return stack.ToList();
    }

    private static bool DfsHelper<T>(
        Dictionary<T, List<T>> graph,
        T current,
        HashSet<T> visited,
        HashSet<T> recursionStack,
        Stack<T> stack) where T : notnull
    {
        visited.Add(current);
        recursionStack.Add(current);

        foreach (var neighbor in graph.GetValueOrDefault(current, new List<T>()))
        {
            if (recursionStack.Contains(neighbor))
                return false; // Döngü var

            if (!visited.Contains(neighbor))
            {
                if (!DfsHelper(graph, neighbor, visited, recursionStack, stack))
                    return false;
            }
        }

        recursionStack.Remove(current);
        stack.Push(current); // Post-order: tüm bağımlılıklar işlendikten sonra ekle
        return true;
    }

    // Gerçek dünya örneği: Build order
    public static List<string>? FindBuildOrder(
        string[] projects,
        (string, string)[] dependencies)
    {
        var graph = new Dictionary<string, List<string>>();

        foreach (var project in projects)
            graph[project] = new List<string>();

        // (A, B) → A, B'ye bağımlı, yani B önce build edilmeli (B → A)
        foreach (var (dependent, dependency) in dependencies)
            graph[dependency].Add(dependent);

        return KahnTopologicalSort(graph);
    }
}

// Build order kullanımı
var projects = new[] { "a", "b", "c", "d", "e", "f" };
var dependencies = new (string, string)[]
{
    ("d", "a"), // d, a'ya bağımlı
    ("b", "f"), // b, f'ye bağımlı
    ("d", "b"), // d, b'ye bağımlı
    ("a", "f"), // a, f'ye bağımlı
    ("c", "d")  // c, d'ye bağımlı
};

var buildOrder = TopologicalSort.FindBuildOrder(projects, dependencies);
if (buildOrder != null)
    Console.WriteLine("Build sırası: " + string.Join(" -> ", buildOrder));
else
    Console.WriteLine("Döngüsel bağımlılık tespit edildi!");
// Build sırası: e -> f -> a -> b -> d -> c

6. Soru

Dijkstra's Algoritması nedir ve ne zaman kullanılır? .NET 6+ ile gelen PriorityQueue<TElement, TPriority> kullanarak C# implementasyonu yapın.

Cevap:

Dijkstra's algoritması, ağırlıklı bir graphta tek bir kaynaktan (single source) tüm diğer düğümlere olan en kısa yolu bulan greedy bir algoritmadır. Negatif ağırlıklı kenarlarla çalışmaz (bunun için Bellman-Ford kullanılır). .NET 6 ile gelen PriorityQueue<TElement, TPriority> min-heap implementasyonu bu algoritma için idealdir.

Zaman Karmaşıklığı: O((V + E) log V) — PriorityQueue ile

Örnek Kod:

public class DijkstraAlgorithm
{
    public record Edge<T>(T To, int Weight);

    // Ağırlıklı graph için Dijkstra
    public static (Dictionary<T, int> Distances, Dictionary<T, T?> Previous)
        Dijkstra<T>(Dictionary<T, List<Edge<T>>> graph, T source)
        where T : notnull
    {
        var distances = new Dictionary<T, int>();
        var previous = new Dictionary<T, T?>();
        var visited = new HashSet<T>();

        // Tüm mesafeleri sonsuz olarak başlat
        foreach (var vertex in graph.Keys)
        {
            distances[vertex] = int.MaxValue;
            previous[vertex] = default;
        }

        distances[source] = 0;

        // .NET 6+ PriorityQueue — (düğüm, mesafe) çiftlerini mesafeye göre sıralar
        var priorityQueue = new PriorityQueue<T, int>();
        priorityQueue.Enqueue(source, 0);

        while (priorityQueue.Count > 0)
        {
            priorityQueue.TryDequeue(out var current, out var currentDist);

            // Zaten daha kısa bir yol bulunduysa atla (lazy deletion)
            if (visited.Contains(current))
                continue;

            visited.Add(current);

            if (!graph.TryGetValue(current, out var neighbors))
                continue;

            foreach (var edge in neighbors)
            {
                if (visited.Contains(edge.To))
                    continue;

                int newDist = distances[current] == int.MaxValue
                    ? int.MaxValue
                    : distances[current] + edge.Weight;

                if (newDist < distances.GetValueOrDefault(edge.To, int.MaxValue))
                {
                    distances[edge.To] = newDist;
                    previous[edge.To] = current;
                    priorityQueue.Enqueue(edge.To, newDist);
                }
            }
        }

        return (distances, previous);
    }

    // Belirli bir hedefe giden yolu rekonstrükte et
    public static List<T> GetPath<T>(
        Dictionary<T, T?> previous,
        T source,
        T target) where T : notnull
    {
        var path = new List<T>();
        var current = target;

        while (!EqualityComparer<T>.Default.Equals(current, source))
        {
            if (previous[current] == null)
                return new List<T>(); // Yol yok

            path.Add(current);
            current = previous[current]!;
        }

        path.Add(source);
        path.Reverse();
        return path;
    }
}

// Kullanım — harita/navigasyon senaryosu
var cityGraph = new Dictionary<string, List<DijkstraAlgorithm.Edge<string>>>
{
    ["Istanbul"] = new()
    {
        new("Ankara", 450),
        new("Bursa", 150)
    },
    ["Ankara"] = new()
    {
        new("Istanbul", 450),
        new("Konya", 260),
        new("Izmir", 590)
    },
    ["Bursa"] = new()
    {
        new("Istanbul", 150),
        new("Izmir", 330)
    },
    ["Izmir"] = new()
    {
        new("Bursa", 330),
        new("Ankara", 590),
        new("Konya", 350)
    },
    ["Konya"] = new()
    {
        new("Ankara", 260),
        new("Izmir", 350)
    }
};

var (distances, previous) = DijkstraAlgorithm.Dijkstra(cityGraph, "Istanbul");

Console.WriteLine("Istanbul'dan mesafeler:");
foreach (var (city, dist) in distances.OrderBy(x => x.Value))
    Console.WriteLine($"  {city}: {dist} km");

var path = DijkstraAlgorithm.GetPath(previous, "Istanbul", "Konya");
Console.WriteLine("\nIstanbul -> Konya en kısa yol: " + string.Join(" -> ", path));
Console.WriteLine($"Toplam mesafe: {distances["Konya"]} km");
// Istanbul -> Ankara -> Konya
// Toplam mesafe: 710 km

7. Soru

Connected Components (Bağlı Bileşenler) nedir? Yönsüz bir graphta kaç ayrı bağlı bileşen olduğunu ve her bileşenin düğümlerini bulan algoritmayı yazın.

Cevap:

Bağlı bileşen (connected component), bir graphta birbirinden erişilebilir düğümler grubudur. Yönsüz graphlarda iki düğüm aynı bileşendeyse birbirlerine bir path üzerinden ulaşılabilir. Bu problem, sosyal ağ analizinde (birbirini tanıyan kişi grupları), harita üzerinde izole bölge tespitinde kullanılır.

Örnek Kod:

public class ConnectedComponents
{
    // DFS ile bağlı bileşenleri bul
    public static List<List<T>> FindConnectedComponents<T>(
        Dictionary<T, List<T>> graph) where T : notnull
    {
        var visited = new HashSet<T>();
        var components = new List<List<T>>();

        foreach (var vertex in graph.Keys)
        {
            if (!visited.Contains(vertex))
            {
                var component = new List<T>();
                DfsComponent(graph, vertex, visited, component);
                components.Add(component);
            }
        }

        return components;
    }

    private static void DfsComponent<T>(
        Dictionary<T, List<T>> graph,
        T current,
        HashSet<T> visited,
        List<T> component) where T : notnull
    {
        visited.Add(current);
        component.Add(current);

        foreach (var neighbor in graph.GetValueOrDefault(current, new List<T>()))
        {
            if (!visited.Contains(neighbor))
                DfsComponent(graph, neighbor, visited, component);
        }
    }

    // Union-Find (Disjoint Set Union) ile bağlı bileşen sayısı
    public static int CountComponentsUnionFind(int n, int[][] edges)
    {
        var parent = Enumerable.Range(0, n).ToArray();
        var rank = new int[n];

        int Find(int x)
        {
            if (parent[x] != x)
                parent[x] = Find(parent[x]); // Path compression
            return parent[x];
        }

        bool Union(int x, int y)
        {
            int px = Find(x), py = Find(y);
            if (px == py) return false;

            // Union by rank
            if (rank[px] < rank[py]) (px, py) = (py, px);
            parent[py] = px;
            if (rank[px] == rank[py]) rank[px]++;
            return true;
        }

        int components = n;
        foreach (var edge in edges)
        {
            if (Union(edge[0], edge[1]))
                components--;
        }

        return components;
    }

    // Strongly Connected Components için Kosaraju algoritması (yönlü graph)
    public static List<List<T>> KosarajuSCC<T>(
        Dictionary<T, List<T>> graph) where T : notnull
    {
        // 1. Adım: Finish time sırasına göre düğümleri stack'e ekle
        var visited = new HashSet<T>();
        var finishStack = new Stack<T>();

        foreach (var vertex in graph.Keys)
        {
            if (!visited.Contains(vertex))
                Dfs1(graph, vertex, visited, finishStack);
        }

        // 2. Adım: Graphı ters çevir
        var transposed = TransposeGraph(graph);

        // 3. Adım: Ters graph üzerinde finish time sırasına göre DFS
        visited.Clear();
        var sccs = new List<List<T>>();

        while (finishStack.Count > 0)
        {
            var vertex = finishStack.Pop();
            if (!visited.Contains(vertex))
            {
                var scc = new List<T>();
                Dfs2(transposed, vertex, visited, scc);
                sccs.Add(scc);
            }
        }

        return sccs;
    }

    private static void Dfs1<T>(
        Dictionary<T, List<T>> graph, T current,
        HashSet<T> visited, Stack<T> finishStack) where T : notnull
    {
        visited.Add(current);
        foreach (var neighbor in graph.GetValueOrDefault(current, new List<T>()))
            if (!visited.Contains(neighbor))
                Dfs1(graph, neighbor, visited, finishStack);
        finishStack.Push(current);
    }

    private static void Dfs2<T>(
        Dictionary<T, List<T>> graph, T current,
        HashSet<T> visited, List<T> scc) where T : notnull
    {
        visited.Add(current);
        scc.Add(current);
        foreach (var neighbor in graph.GetValueOrDefault(current, new List<T>()))
            if (!visited.Contains(neighbor))
                Dfs2(graph, neighbor, visited, scc);
    }

    private static Dictionary<T, List<T>> TransposeGraph<T>(
        Dictionary<T, List<T>> graph) where T : notnull
    {
        var transposed = new Dictionary<T, List<T>>();
        foreach (var vertex in graph.Keys)
            transposed[vertex] = new List<T>();

        foreach (var (vertex, neighbors) in graph)
            foreach (var neighbor in neighbors)
                transposed[neighbor].Add(vertex);

        return transposed;
    }
}

// Kullanım
var graph = new Dictionary<string, List<string>>
{
    ["A"] = new List<string> { "B" },
    ["B"] = new List<string> { "A" },
    ["C"] = new List<string> { "D" },
    ["D"] = new List<string> { "C" },
    ["E"] = new List<string>() // Yalnız düğüm
};

var components = ConnectedComponents.FindConnectedComponents(graph);
Console.WriteLine($"Bağlı bileşen sayısı: {components.Count}");
for (int i = 0; i < components.Count; i++)
    Console.WriteLine($"  Bileşen {i + 1}: [{string.Join(", ", components[i])}]");
// Bağlı bileşen sayısı: 3
// Bileşen 1: [A, B]
// Bileşen 2: [C, D]
// Bileşen 3: [E]

// Union-Find kullanımı
int nodeCount = 5;
int[][] edges = { new[] { 0, 1 }, new[] { 1, 2 }, new[] { 3, 4 } };
Console.WriteLine($"Bileşen sayısı (Union-Find): {ConnectedComponents.CountComponentsUnionFind(nodeCount, edges)}");
// Bileşen sayısı (Union-Find): 2

8. Soru

Gerçek bir mülakat sorusu: "Number of Islands" — 2D bir grid üzerinde '1' (kara) ve '0' (su) karakterlerinden oluşan bir matrixte ada sayısını bulun. BFS ve DFS çözümlerini karşılaştırın.

Cevap:

Bu LeetCode tarzı klasik bir graph sorusudur. Grid'i bir graph olarak düşünebiliriz; her '1' hücresi bir düğüm, komşu '1' hücreleri arasındaki bağlantılar ise kenardır. Her DFS/BFS çağrısı bir adayı tamamen işaretler. Bu teknik "flood fill" olarak da bilinir.

Zaman Karmaşıklığı: O(M × N) — M satır, N sütun Alan Karmaşıklığı: O(M × N) — worst case (tüm grid kara)

Örnek Kod:

public class NumberOfIslands
{
    private static readonly int[] DeltaRow = { -1, 1, 0, 0 };
    private static readonly int[] DeltaCol = { 0, 0, -1, 1 };

    // DFS çözümü — recursive flood fill
    public static int CountIslandsDfs(char[][] grid)
    {
        if (grid == null || grid.Length == 0)
            return 0;

        int rows = grid.Length;
        int cols = grid[0].Length;
        int count = 0;

        for (int r = 0; r < rows; r++)
        {
            for (int c = 0; c < cols; c++)
            {
                if (grid[r][c] == '1')
                {
                    count++;
                    DfsFloodFill(grid, r, c, rows, cols);
                }
            }
        }

        return count;
    }

    private static void DfsFloodFill(char[][] grid, int row, int col, int rows, int cols)
    {
        if (row < 0 || row >= rows || col < 0 || col >= cols || grid[row][col] != '1')
            return;

        grid[row][col] = '0'; // Ziyaret edildi olarak işaretle (in-place)

        for (int d = 0; d < 4; d++)
            DfsFloodFill(grid, row + DeltaRow[d], col + DeltaCol[d], rows, cols);
    }

    // BFS çözümü — queue ile genişleme
    public static int CountIslandsBfs(char[][] grid)
    {
        if (grid == null || grid.Length == 0)
            return 0;

        int rows = grid.Length;
        int cols = grid[0].Length;
        int count = 0;

        for (int r = 0; r < rows; r++)
        {
            for (int c = 0; c < cols; c++)
            {
                if (grid[r][c] == '1')
                {
                    count++;
                    BfsFloodFill(grid, r, c, rows, cols);
                }
            }
        }

        return count;
    }

    private static void BfsFloodFill(char[][] grid, int startRow, int startCol, int rows, int cols)
    {
        var queue = new Queue<(int Row, int Col)>();
        grid[startRow][startCol] = '0';
        queue.Enqueue((startRow, startCol));

        while (queue.Count > 0)
        {
            var (row, col) = queue.Dequeue();

            for (int d = 0; d < 4; d++)
            {
                int newRow = row + DeltaRow[d];
                int newCol = col + DeltaCol[d];

                if (newRow >= 0 && newRow < rows &&
                    newCol >= 0 && newCol < cols &&
                    grid[newRow][newCol] == '1')
                {
                    grid[newRow][newCol] = '0';
                    queue.Enqueue((newRow, newCol));
                }
            }
        }
    }

    // Orijinal grid'i koruyarak çözüm (visited matrix ile)
    public static int CountIslandsNonDestructive(char[][] grid)
    {
        if (grid == null || grid.Length == 0)
            return 0;

        int rows = grid.Length;
        int cols = grid[0].Length;
        bool[,] visited = new bool[rows, cols];
        int count = 0;

        for (int r = 0; r < rows; r++)
        {
            for (int c = 0; c < cols; c++)
            {
                if (grid[r][c] == '1' && !visited[r, c])
                {
                    count++;
                    DfsWithVisited(grid, r, c, rows, cols, visited);
                }
            }
        }

        return count;
    }

    private static void DfsWithVisited(
        char[][] grid, int row, int col,
        int rows, int cols, bool[,] visited)
    {
        if (row < 0 || row >= rows || col < 0 || col >= cols ||
            visited[row, col] || grid[row][col] != '1')
            return;

        visited[row, col] = true;

        for (int d = 0; d < 4; d++)
            DfsWithVisited(grid, row + DeltaRow[d], col + DeltaCol[d], rows, cols, visited);
    }
}

// Test
char[][] grid1 =
{
    new[] { '1', '1', '1', '1', '0' },
    new[] { '1', '1', '0', '1', '0' },
    new[] { '1', '1', '0', '0', '0' },
    new[] { '0', '0', '0', '0', '0' }
};

// grid'i kopyala (DFS grid'i değiştirir)
char[][] grid2 =
{
    new[] { '1', '1', '0', '0', '0' },
    new[] { '1', '1', '0', '0', '0' },
    new[] { '0', '0', '1', '0', '0' },
    new[] { '0', '0', '0', '1', '1' }
};

Console.WriteLine(NumberOfIslands.CountIslandsDfs(grid1));  // 1
Console.WriteLine(NumberOfIslands.CountIslandsBfs(grid2));  // 3

// DFS vs BFS Karşılaştırma:
// DFS: Call stack kullanır, büyük grids'de StackOverflowException riski
// BFS: Explicit queue kullanır, büyük girişlerde daha güvenli
// Her ikisi de O(M*N) zaman ve alan karmaşıklığına sahiptir

Özet Karşılaştırma Tablosu

Algoritma Zaman Alan Kullanım Alanı
BFS O(V+E) O(V) En kısa yol (unweighted), level-order
DFS O(V+E) O(V) Cycle detection, pathfinding, topo sort
Dijkstra O((V+E) log V) O(V) En kısa yol (weighted, pozitif)
Topological Sort (Kahn) O(V+E) O(V) Build order, task scheduling
Connected Components O(V+E) O(V) Cluster analizi, izole bölge tespiti
Union-Find O(α(N)) amortized O(N) Dynamic connectivity, Kruskal's MST

Mülakatlarda Dikkat Edilecek Noktalar:

  • Ziyaret edilmiş düğümleri (HashSet) takip etmeyi unutmayın, aksi takdirde sonsuz döngüye girersiniz.
  • Yönsüz graphlarda cycle detection için parent düğümü saklayın.
  • Yönlü graphlarda cycle detection için recursion stack ayrı tutulmalıdır.
  • Dijkstra negatif kenarlarla çalışmaz; Bellman-Ford kullanın.
  • PriorityQueue<TElement, TPriority> .NET 6 ile geldi; önceki versiyonlarda SortedSet veya harici kütüphane gerekir.
  • Graph disconnected olabilir; tüm düğümleri dolaşmak için her düğümden DFS/BFS başlatın.