Deteksi Siklus pada Graf Berarah dan Tak Berarah
Deteksi siklus pada graf tak berarah dengan melacak induk dan pada graf berarah dengan pewarnaan DFS (tiga status kunjungan putih/abu-abu/hitam).
Deteksi Siklus pada Graf Berarah dan Tak Berarah adalah pelajaran DSA Interview Prep gratis di CoddyKit. Ini adalah pelajaran 4 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar DSA Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Mengapa Deteksi Siklus Penting
Sebuah siklus dalam graf adalah jalur yang dimulai dan berakhir pada simpul yang sama. Deteksi siklus sangat penting dalam banyak algoritme: pengurutan topologis gagal pada graf yang memiliki siklus, penyelesaian dependensi harus mendeteksi dependensi melingkar, dan deteksi kebuntuan dalam penjadwalan OS memerlukan pencarian siklus dalam graf alokasi sumber daya. Pendekatannya berbeda antara graf tak berarah dan graf berarah — keduanya memerlukan algoritme yang pada dasarnya berbeda.
from collections import defaultdict
# Undirected cycle: A-B-C-A (triangle)
undirected = defaultdict(list)
for u, v in [('A','B'),('B','C'),('C','A')]:
undirected[u].append(v)
undirected[v].append(u)
# Directed cycle: A->B->C->A
directed = defaultdict(list)
for u, v in [('A','B'),('B','C'),('C','A')]:
directed[u].append(v) # one direction only
# Key difference:
# Undirected: edge A-B appears as both A->B and B->A
# Must track parent to distinguish cycle from back-edge to parent
print('Undirected and directed cycles need different detection')Deteksi Siklus Tak Berarah dengan DFS
Dalam graf tak berarah, sebuah siklus ada jika DFS mengunjungi simpul yang sudah berada dalam jalur saat ini (bukan sekadar sudah dikunjungi). Tantangannya: setiap sisi muncul dalam kedua arah, sehingga ketika kita mengunjungi simpul anak, daftar tetangganya menyertakan simpul kita saat ini (induknya). Kita harus melacak induk setiap simpul agar tidak keliru menganggap sisi yang kembali ke induk sebagai siklus. Jika menemukan simpul yang sudah dikunjungi dan bukan induk kita, berarti kita telah menemukan siklus.
def has_cycle_undirected(n, edges):
from collections import defaultdict
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
visited = set()
def dfs(node, parent):
visited.add(node)
for nb in graph[node]:
if nb not in visited:
if dfs(nb, node): # recurse with current as parent
return True
elif nb != parent: # visited and not parent = CYCLE
return True
return False
for node in range(n):
if node not in visited:
if dfs(node, -1): # -1 = no parent for root
return True
return False
print(has_cycle_undirected(4, [(0,1),(1,2),(2,3),(3,1)])) # True
print(has_cycle_undirected(3, [(0,1),(1,2)])) # FalseSiklus Tak Berarah dengan BFS
Deteksi siklus dengan BFS pada graf tak berarah juga melacak induk setiap simpul yang telah dikunjungi. Saat memproses tetangga sebuah simpul, jika tetangga tersebut sudah dikunjungi dan bukan induk simpul saat ini, berarti ada siklus. Gunakan kamus untuk menyimpan induk. Pendekatan O(V + E) ini menghindari kekhawatiran tentang batas rekursi dan merupakan alternatif iteratif yang lebih disukai untuk graf berukuran besar.
from collections import deque, defaultdict
def has_cycle_bfs_undirected(n, edges):
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
visited = set()
for start in range(n):
if start in visited:
continue
visited.add(start)
parent = {start: -1}
queue = deque([start])
while queue:
node = queue.popleft()
for nb in graph[node]:
if nb not in visited:
visited.add(nb)
parent[nb] = node
queue.append(nb)
elif parent[node] != nb: # visited and not parent = CYCLE
return True
return False
print(has_cycle_bfs_undirected(4, [(0,1),(1,2),(2,0)])) # TrueSiklus Berarah: Mengapa Pelacakan Induk Gagal
Dalam graf berarah, pelacakan induk tidak memadai. Perhatikan A→C dan B→C: simpul C memiliki dua 'induk', tetapi tidak memiliki siklus. Pendekatan yang benar menggunakan pewarnaan tiga keadaan: putih (belum dikunjungi), abu-abu (berada dalam jalur/tumpukan DFS saat ini), dan hitam (telah selesai diproses sepenuhnya). Siklus ada jika kita menemukan simpul abu-abu saat DFS — artinya kita menemukan sisi balik menuju leluhur dalam jalur saat ini.
# Three-state DFS coloring:
# WHITE (0): not yet visited
# GRAY (1): currently being visited (in DFS stack)
# BLACK (2): fully visited (all descendants processed)
# Why parent fails for directed graphs:
# A -> C (no cycle)
# B -> C (no cycle)
# If we DFS from A, mark C gray
# Then DFS from B finds C is gray -- but this is NOT a cycle!
# C is gray from A's path, not B's path.
# Parent tracking only works when the back-edge goes to the IMMEDIATE parent.
print('Directed graph: use 3-state coloring (white/gray/black)')Deteksi Siklus Berarah dengan DFS Tiga Keadaan
Gunakan array state[] dengan nilai 0 (putih/belum dikunjungi), 1 (abu-abu/berada dalam tumpukan), dan 2 (hitam/selesai). Mulai DFS, tandai simpul sebagai abu-abu saat masuk dan hitam saat keluar. Jika DFS mencapai simpul abu-abu, sisi balik ditemukan — berarti ada siklus. Jika mencapai simpul hitam, jalur tersebut sudah sepenuhnya ditelusuri dan bebas siklus, sehingga lewati simpul itu.
def has_cycle_directed(n, edges):
from collections import defaultdict
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
state = [0] * n # 0=white, 1=gray, 2=black
def dfs(node):
state[node] = 1 # mark gray (in stack)
for nb in graph[node]:
if state[nb] == 1: # gray = back edge = CYCLE
return True
if state[nb] == 0: # white = unvisited
if dfs(nb):
return True
state[node] = 2 # mark black (fully processed)
return False
for node in range(n):
if state[node] == 0:
if dfs(node):
return True
return False
print(has_cycle_directed(4, [(0,1),(1,2),(2,0),(2,3)])) # True (0->1->2->0)
print(has_cycle_directed(3, [(0,1),(1,2)])) # FalseJadwal Mata Kuliah: Siklus dalam DAG
Jadwal Mata Kuliah (LeetCode #207) menanyakan apakah semua mata kuliah dapat diselesaikan jika diberikan prasyaratnya. Modelkan mata kuliah sebagai simpul dan prasyarat sebagai sisi berarah. Semua mata kuliah dapat diselesaikan jika dan hanya jika graf tersebut merupakan DAG (tanpa siklus). Gunakan deteksi siklus DFS tiga keadaan — jika ditemukan siklus, kembalikan False; jika tidak, kembalikan True.
from collections import defaultdict
def can_finish(num_courses, prerequisites):
graph = defaultdict(list)
for a, b in prerequisites:
graph[b].append(a) # b is prerequisite for a: b -> a
state = [0] * num_courses
def dfs(course):
if state[course] == 1: return False # cycle!
if state[course] == 2: return True # already verified
state[course] = 1 # mark as in-progress
for next_course in graph[course]:
if not dfs(next_course):
return False
state[course] = 2 # mark as done
return True
return all(dfs(i) for i in range(num_courses) if state[i] == 0)
print(can_finish(2, [[1,0]])) # True: take 0 then 1
print(can_finish(2, [[1,0],[0,1]])) # False: circular dependencyDeteksi Siklus dengan Algoritme Kahn (BFS)
Alternatif deteksi siklus untuk graf berarah menggunakan pengurutan topologis BFS Kahn. Hitung derajat masuk semua simpul. Masukkan simpul dengan derajat masuk 0 ke dalam antrean. Proses setiap simpul: kurangi derajat masuk tetangganya dan masukkan tetangga yang derajat masuknya mencapai 0 ke antrean. Jika jumlah simpul yang diproses sama dengan V, tidak ada siklus; jika tidak, berarti ada siklus (simpul yang belum diproses membentuk siklus). Pendekatan O(V + E) ini intuitif dan lebih mudah diingat daripada DFS tiga keadaan.
from collections import defaultdict, deque
def has_cycle_kahn(n, edges):
graph = defaultdict(list)
in_degree = [0] * n
for u, v in edges:
graph[u].append(v)
in_degree[v] += 1
# Start with all zero in-degree nodes
queue = deque(i for i in range(n) if in_degree[i] == 0)
processed = 0
while queue:
node = queue.popleft()
processed += 1
for nb in graph[node]:
in_degree[nb] -= 1
if in_degree[nb] == 0:
queue.append(nb)
return processed != n # if not all processed, cycle exists
print(has_cycle_kahn(4, [(0,1),(1,2),(2,0),(2,3)])) # True
print(has_cycle_kahn(3, [(0,1),(1,2)])) # FalseTemukan Siklus: Mengumpulkan Simpul Siklus
Kadang-kadang Anda perlu mengidentifikasi simpul mana yang merupakan bagian dari siklus, bukan hanya mendeteksi keberadaannya. Selama DFS tiga keadaan, ketika sisi balik ditemukan, telusuri mundur melalui tumpukan pemanggilan (atau tumpukan jalur) untuk mengumpulkan semua simpul di antara leluhur dan simpul saat ini. Tumpukan jalur yang dikelola bersama array keadaan menangkap jalur DFS saat ini, sehingga memungkinkan rekonstruksi siklus dengan biaya O(cycle_length).
def find_cycle_nodes(n, edges):
from collections import defaultdict
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
state = [0] * n
path = [] # current DFS path
cycle = []
def dfs(node):
state[node] = 1
path.append(node)
for nb in graph[node]:
if state[nb] == 1: # back edge -> found cycle
start = path.index(nb)
cycle.extend(path[start:])
return True
if state[nb] == 0 and dfs(nb):
return True
path.pop()
state[node] = 2
return False
for i in range(n):
if state[i] == 0 and dfs(i):
break
return cycle
print(find_cycle_nodes(4, [(0,1),(1,2),(2,0),(2,3)])) # [0, 1, 2]Temukan Status Aman Akhir
Temukan Status Aman Akhir (LeetCode #802) menanyakan simpul mana yang pada akhirnya menuju simpul terminal (tanpa sisi keluar) tanpa terjebak dalam siklus. Sebuah simpul 'aman' jika semua jalur dari simpul tersebut menuju simpul terminal. Gunakan DFS tiga keadaan: simpul yang berwarna hitam (telah selesai diproses sepenuhnya tanpa mendeteksi siklus) bersifat aman. Simpul yang merupakan bagian dari siklus atau menuju siklus tidak aman.
def eventual_safe_nodes(graph):
n = len(graph)
state = [0] * n # 0=unvisited, 1=visiting, 2=safe
def dfs(node):
if state[node] == 1: # currently visiting = cycle
return False
if state[node] == 2: # already verified safe
return True
state[node] = 1 # mark as visiting
for nb in graph[node]:
if not dfs(nb):
return False # leads to cycle, not safe
state[node] = 2 # mark as safe
return True
return [i for i in range(n) if dfs(i)]
# [[1,2],[2,3],[5],[0],[5],[],[]] means:
# 0->[1,2], 1->[2,3], 2->[5], 3->[0] (cycle!), 4->[5], 5->[], 6->[]
print(eventual_safe_nodes([[1,2],[2,3],[5],[0],[5],[],[]]))
# [2, 4, 5, 6]Koneksi Berlebih pada Graf Tak Berarah
Koneksi Berlebih (LeetCode #684) menemukan sisi yang menciptakan siklus ketika ditambahkan ke graf tak berarah yang sebelumnya tidak memiliki siklus. Meskipun hal ini dapat diselesaikan dengan deteksi siklus DFS, solusi yang paling bersih menggunakan Penggabungan-Pencarian (DSU): proses sisi satu per satu; jika kedua ujungnya sudah terhubung (berada dalam komponen yang sama), sisi saat ini menciptakan siklus dan merupakan jawabannya. DSU memberikan O(alpha(n)) untuk setiap operasi — pada praktiknya O(1).
def find_redundant_connection(edges):
n = len(edges)
parent = list(range(n + 1))
rank = [0] * (n + 1)
def find(x):
if parent[x] != x:
parent[x] = find(parent[x]) # path compression
return parent[x]
def union(x, y):
px, py = find(x), find(y)
if px == py:
return False # already connected = cycle!
if rank[px] < rank[py]: px, py = py, px
parent[py] = px
if rank[px] == rank[py]: rank[px] += 1
return True
for u, v in edges:
if not union(u, v):
return [u, v] # this edge creates the cycle
return []
print(find_redundant_connection([[1,2],[1,3],[2,3]])) # [2,3]
print(find_redundant_connection([[1,2],[2,3],[3,4],[1,4],[1,5]])) # [1,4]Ringkasan Strategi Deteksi Siklus
Untuk merangkum kumpulan strategi deteksi siklus: untuk graf tak berarah, gunakan DFS dengan pelacakan induk atau Penggabungan-Pencarian. Untuk graf berarah, gunakan DFS tiga keadaan (putih/abu-abu/hitam) atau pengurutan topologis BFS Kahn. Pilih Penggabungan-Pencarian ketika Anda menambahkan sisi satu per satu. Pilih Kahn jika Anda juga memerlukan urutan topologis. Pilih DFS tiga keadaan jika Anda perlu mengidentifikasi simpul siklus tertentu. Saat membahas deteksi siklus dalam wawancara, selalu nyatakan perbedaan antara graf berarah dan tak berarah.
# Cycle detection summary:
# Graph type | Algorithm | Complexity
# ------------|----------------------|-----------
# Undirected | DFS + parent track | O(V + E)
# Undirected | Union-Find (DSU) | O(E * alpha(V))
# Directed | DFS 3-state (W/G/B) | O(V + E)
# Directed | Kahn's BFS topo sort | O(V + E)
# When to choose:
# Online (edges added one at a time): Union-Find
# Need topological order too: Kahn's BFS
# Need cycle nodes identified: 3-state DFS with path stack
# Simple existence check: any of the above
print('Always clarify directed vs undirected before coding')Uji Cepat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda mempelajari: deteksi siklus tak berarah dengan DFS yang melacak induk, deteksi siklus berarah dengan pewarnaan tiga keadaan putih/abu-abu/hitam, alternatif BFS Kahn untuk graf berarah, serta penerapannya pada jadwal mata kuliah, koneksi berlebih, dan status aman akhir. Berikutnya kita akan mendalami dasar-dasar pemrograman dinamis.
Belajar Python dengan tutor AI — gratis
Tulis dan jalankan kode asli di browser kamu, dapatkan bantuan instan dari tutor AI 24/7, dan lanjutkan di mana kamu tinggalkan di web atau aplikasi.
- Kursus
- 30
- Pelajaran
- 120
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Deteksi Siklus pada Graf Berarah dan Tak Berarah” gratis?
Ya — teks lengkap “Deteksi Siklus pada Graf Berarah dan Tak Berarah” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus DSA Interview Prep, upgrade ke CoddyKit PRO. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Deteksi Siklus pada Graf Berarah dan Tak Berarah”?
Deteksi siklus pada graf tak berarah dengan melacak induk dan pada graf berarah dengan pewarnaan DFS (tiga status kunjungan putih/abu-abu/hitam). Kamu berlatih DSA Interview Prep dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai DSA Interview Prep?
Tidak diperlukan pengalaman sebelumnya. DSA Interview Prep di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 4 dari 4.
Berapa lama pelajaran “Deteksi Siklus pada Graf Berarah dan Tak Berarah” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran DSA Interview Prep ini?
Ya. Setiap pelajaran DSA Interview Prep menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Representasi Graf dan Persiapan Traversal
- BFS: Jalur Terpendek dan Traversal Level
- DFS: Komponen Terhubung dan Flood Fill
- Deteksi Siklus pada Graf Berarah dan Tak Berarah