Derinlik Öncelikli Arama - Dalları Sonuna Kadar Keşfetmek
DFS (Depth-First Search), bir grafı derinlemesine dolaşan bir algoritmadır. Bir yolu sonuna kadar takip eder, çıkmaza gelince geri döner ve başka yolları dener. Tıpkı bir labirentte ilerleme stratejisi gibi!
Bir labirentte kaybolduğunuzu düşünün. DFS stratejisi:
LIFO: Son giren ilk çıkar
Özyinelemeli çözüm doğal
Dal dal keşfeder
En doğal ve okunabilir yöntem. Call stack'i kullanır.
def dfs(node):
if node visited: return
mark node as visited
process(node)
for neighbor in node.neighbors:
dfs(neighbor)
Explicit stack kullanır. Derin graflarda stack overflow'u önler.
def dfs(start):
stack = [start]
while stack not empty:
node = stack.pop()
if node visited: continue
mark as visited
for neighbor in node.neighbors:
stack.push(neighbor)
BFS = Queue (FIFO) → Seviye seviye yayılır
DFS = Stack (LIFO) → Derinlemesine dalar
Başlangıç düğümü: A
📚 Stack (Yığın)
✅ Ziyaret Sırası
def dfs_recursive(graph, start, visited=None):
"""
DFS - Recursive (Özyinelemeli) Versiyon
Bu yöntem call stack'i kullanır.
Doğal ve okunabilir ama derin graflarda stack overflow olabilir.
"""
if visited is None:
visited = set()
# Bu düğümü ziyaret et
visited.add(start)
print(f"Ziyaret: {start}")
# Tüm komşuları recursive olarak ziyaret et
for neighbor in graph[start]:
if neighbor not in visited:
dfs_recursive(graph, neighbor, visited)
return visited
# Test graf
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
print("Graf:")
for node, neighbors in graph.items():
print(f" {node}: {neighbors}")
print("\n=== DFS (Recursive) - A'dan Başlayarak ===\n")
visited = dfs_recursive(graph, 'A')
print(f"\n✅ Ziyaret edilen düğümler: {visited}")
def dfs_iterative(graph, start):
"""
DFS - Iterative (Stack ile) Versiyon
Explicit stack kullanır.
Derin graflarda stack overflow'u önler.
"""
visited = set()
stack = [start]
result = []
while stack:
node = stack.pop() # Stack'in tepesinden al (LIFO)
if node in visited:
continue
visited.add(node)
result.append(node)
print(f"Ziyaret: {node}, Stack: {stack}")
# Komşuları stack'e ekle (ters sırada ekleyerek alfabetik sırayı koruyoruz)
for neighbor in reversed(graph[node]):
if neighbor not in visited:
stack.append(neighbor)
print(f" → {neighbor} stack'e eklendi")
return result
# Test
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
print("=== DFS (Iterative) - A'dan Başlayarak ===\n")
result = dfs_iterative(graph, 'A')
print(f"\n✅ Ziyaret sırası: {result}")
DFS'in en önemli uygulamalarından biri: grafta döngü olup olmadığını tespit etmek. Yönlü ve yönsüz graflar için farklı yaklaşımlar gerekir.
def has_cycle_undirected(graph):
"""
Yönsüz grafta döngü tespiti
Mantık: Eğer ziyaret ettiğimiz bir komşu daha önce ziyaret edilmişse
VE parent'ımız değilse, döngü var demektir.
"""
visited = set()
def dfs(node, parent):
visited.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
if dfs(neighbor, node): # Recursive çağrı
return True
elif neighbor != parent:
# Ziyaret edilmiş ama parent değil = DÖNGÜ!
print(f" 🔄 Döngü bulundu: {node} -> {neighbor}")
return True
return False
# Tüm bileşenleri kontrol et
for node in graph:
if node not in visited:
if dfs(node, None):
return True
return False
# Test 1: Döngüsüz graf (ağaç)
tree = {
'A': ['B', 'C'],
'B': ['A', 'D'],
'C': ['A'],
'D': ['B']
}
print("=== TEST 1: Ağaç (Döngüsüz) ===")
print(f"Graf: {tree}")
print(f"Döngü var mı? {has_cycle_undirected(tree)}\n")
# Test 2: Döngülü graf
cyclic = {
'A': ['B', 'C'],
'B': ['A', 'C'], # B-C kenarı döngü oluşturur
'C': ['A', 'B']
}
print("=== TEST 2: Döngülü Graf ===")
print(f"Graf: {cyclic}")
print(f"Döngü var mı? {has_cycle_undirected(cyclic)}")
def has_cycle_directed(graph):
"""
Yönlü grafta döngü tespiti
3 renk sistemi:
- WHITE (0): Henüz ziyaret edilmedi
- GRAY (1): Şu an ziyaret ediliyor (DFS stack'inde)
- BLACK (2): Tamamen işlendi
Eğer GRAY bir düğüme tekrar ulaşırsak = Döngü!
"""
WHITE, GRAY, BLACK = 0, 1, 2
color = {node: WHITE for node in graph}
def dfs(node):
color[node] = GRAY # İşlemeye başladık
for neighbor in graph.get(node, []):
if color[neighbor] == GRAY:
# GRAY düğüme ulaştık = Döngü!
print(f" 🔄 Döngü: {node} -> {neighbor} (back edge)")
return True
if color[neighbor] == WHITE:
if dfs(neighbor):
return True
color[node] = BLACK # Tamamen işlendi
return False
for node in graph:
if color[node] == WHITE:
if dfs(node):
return True
return False
# Test 1: DAG (Döngüsüz Yönlü Graf)
dag = {
'A': ['B', 'C'],
'B': ['D'],
'C': ['D'],
'D': []
}
print("=== TEST 1: DAG (Döngüsüz) ===")
print(f"Graf: {dag}")
print(f"Döngü var mı? {has_cycle_directed(dag)}\n")
# Test 2: Döngülü yönlü graf
cyclic = {
'A': ['B'],
'B': ['C'],
'C': ['A'] # C -> A döngü oluşturur
}
print("=== TEST 2: Döngülü Yönlü Graf ===")
print(f"Graf: {cyclic}")
print(f"Döngü var mı? {has_cycle_directed(cyclic)}")
Topological Sort, bir DAG'daki (Directed Acyclic Graph) düğümleri öyle sıralar ki, her kenar u→v için, u her zaman v'den önce gelir. Görev planlaması için çok kullanışlı!
Matematik → Fizik → Elektrik Mühendisliği
Topolojik sıralama, dersleri hangi sırada almanız gerektiğini söyler!
def topological_sort(graph):
"""
DFS tabanlı Topolojik Sıralama
Mantık: DFS'te bir düğümün tüm bağımlılıkları işlendikten sonra
o düğümü stack'e ekleriz. Sonunda stack'i ters çeviririz.
"""
visited = set()
result = [] # Stack olarak kullanılacak
def dfs(node):
visited.add(node)
for neighbor in graph.get(node, []):
if neighbor not in visited:
dfs(neighbor)
# Tüm bağımlılıklar işlendi, şimdi ekle
result.append(node)
# Tüm düğümleri ziyaret et
for node in graph:
if node not in visited:
dfs(node)
# Ters çevir (stack mantığı)
return result[::-1]
# Ders ön koşulları örneği
courses = {
'Matematik': ['Fizik', 'İstatistik'],
'Fizik': ['Elektrik', 'Mekanik'],
'İstatistik': ['Makine Öğrenmesi'],
'Elektrik': ['Sinyal İşleme'],
'Mekanik': [],
'Makine Öğrenmesi': [],
'Sinyal İşleme': []
}
print("=== DERS ÖN KOŞULLARI ===")
print("Graf (ders -> bağımlı dersler):")
for course, deps in courses.items():
print(f" {course}: {deps if deps else '(bağımlılık yok)'}")
print("\n=== TOPOLOJİK SIRALAMA ===")
order = topological_sort(courses)
print(f"Ders alma sırası: ")
for i, course in enumerate(order, 1):
print(f" {i}. {course}")
| Metrik | Karmaşıklık | Açıklama |
|---|---|---|
| Zaman | O(V + E) | Her düğüm 1 kez, her kenar 1 kez işlenir |
| Alan (Recursive) | O(V) | Call stack derinliği (en kötü: V) |
| Alan (Iterative) | O(V) | Explicit stack + visited set |
Bir yolu sonuna kadar takip et, çıkmazsa geri dön. Klasik backtracking.
Topological sort ile bağımlılıkları çöz. Build sistemleri, CI/CD pipeline'lar.
Deadlock detection, circular import kontrolü, dependency cycle bulma.
Sosyal ağ kümeleri, görüntü segmentasyonu, network partitioning.
Karar ağaçları, state space exploration, minimax algoritması.
Derinlemesine sayfa tarama, sitemap oluşturma.
| Senaryo | Tercih | Neden? |
|---|---|---|
| En kısa yol (ağırlıksız) | BFS | Seviye sırasında arar, ilk bulunan en kısa |
| Döngü tespiti | DFS | Back edge kontrolü doğal |
| Topological sort | DFS | Post-order traversal kullanır |
| Yol var mı kontrolü | Her ikisi | İkisi de O(V+E), fark yok |
| Bellek kısıtlı ortam | DFS | O(h) vs O(V) bellek |
| Seviye analizi gerekli | BFS | Seviye bilgisi doğal olarak var |