DSA Interview Prep · पाठ

निर्देशित और अनिर्देशित ग्राफ में चक्र पहचान

अनिर्देशित ग्राफ में अभिभावक रिकॉर्ड रखकर और निर्देशित ग्राफ में DFS रंग-चिह्नन से चक्र पहचानिए; देखी गई तीन अवस्थाएँ सफ़ेद, धूसर और काली हैं।

पाठ 4, कुल 4 में से13 चरण

निर्देशित और अनिर्देशित ग्राफ में चक्र पहचान, CoddyKit पर DSA Interview Prep का एक निःशुल्क पाठ है। यह 4 में से 4वाँ पाठ है। इस अध्ययन पथ के 3 तक कोई भी पाठ पूरा पढ़ना निःशुल्क है — इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ व्यावहारिक अभ्यास भी उपलब्ध कराता है। यह DSA Interview Prep सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। DSA Interview Prep पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

चक्र की पहचान क्यों महत्वपूर्ण है

ग्राफ़ में चक्र ऐसा पथ है जो एक ही नोड से शुरू होकर उसी नोड पर समाप्त होता है। अनेक एल्गोरिदम में चक्र की पहचान महत्वपूर्ण है: चक्रीय ग्राफ़ पर टोपोलॉजिकल क्रम विफल हो जाता है, निर्भरता समाधान को परिपत्र निर्भरताओं की पहचान करनी होती है, और OS शेड्यूलिंग में गतिरोध की पहचान के लिए संसाधन-वितरण ग्राफ़ में चक्र ढूँढ़ने पड़ते हैं। अनिर्देशित और निर्देशित ग्राफ़ के लिए तरीका अलग होता है — इनके लिए मूल रूप से अलग एल्गोरिदम आवश्यक हैं।

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')

DFS द्वारा अनिर्देशित चक्र की पहचान

अनिर्देशित ग्राफ़ में चक्र तब मौजूद होता है जब DFS किसी ऐसे नोड पर पहुँचता है जो केवल देखे गए नोड्स में ही नहीं, बल्कि वर्तमान पथ में भी मौजूद है। चुनौती यह है कि प्रत्येक किनारा दोनों दिशाओं में दिखाई देता है, इसलिए जब हम किसी बाल नोड पर पहुँचते हैं, तो उसकी पड़ोसी सूची में हमारा वर्तमान नोड (मूल नोड) भी शामिल होता है। मूल नोड की ओर लौटने वाले किनारे को गलती से चक्र मानने से बचने के लिए हमें प्रत्येक नोड के मूल नोड का अभिलेख रखना होगा। यदि हमें ऐसा देखा हुआ नोड मिलता है जो हमारा मूल नोड नहीं है, तो चक्र मिल गया है।

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)]))               # False

BFS द्वारा अनिर्देशित चक्र

अनिर्देशित ग्राफ़ में BFS द्वारा चक्र की पहचान करते समय भी प्रत्येक देखे गए नोड के मूल नोड का अभिलेख रखा जाता है। किसी नोड के पड़ोसियों को संसाधित करते समय, यदि कोई पड़ोसी पहले से देखा जा चुका है और वर्तमान नोड का मूल नोड नहीं है, तो चक्र मौजूद है। मूल नोड्स को संग्रहीत करने के लिए शब्दकोश इस्तेमाल करें। यह O(V + E) तरीका पुनरावृत्ति सीमा की चिंता से बचाता है और बड़े ग्राफ़ के लिए पसंदीदा पुनरावृत्त विकल्प है।

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)]))  # True

निर्देशित चक्र: मूल नोड का अभिलेख रखना क्यों विफल होता है

निर्देशित ग्राफ़ में केवल मूल नोड का अभिलेख रखना पर्याप्त नहीं है। A→C और B→C पर विचार करें: नोड C के दो 'मूल नोड' हैं, लेकिन कोई चक्र नहीं है। सही तरीका तीन-अवस्था वाले रंग-निर्धारण का उपयोग करता है: सफेद (नहीं देखा गया), धूसर (वर्तमान DFS पथ/स्टैक में), काला (पूरी तरह संसाधित)। यदि DFS के दौरान कभी किसी धूसर नोड पर पहुँचते हैं, तो चक्र मौजूद है — इसका अर्थ है कि वर्तमान पथ में किसी पूर्वज की ओर लौटने वाला किनारा मिल गया है।

# 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)')

3-अवस्था वाले DFS द्वारा निर्देशित चक्र की पहचान

मान 0 (सफेद/नहीं देखा गया), 1 (धूसर/स्टैक में) और 2 (काला/पूरा) रखने वाली state[] सरणी इस्तेमाल करें। DFS शुरू करते समय प्रवेश पर नोड को धूसर और बाहर निकलते समय काला चिह्नित करें। यदि DFS कभी धूसर नोड तक पहुँचता है, तो पीछे लौटने वाला किनारा मिला — अर्थात चक्र मौजूद है। यदि वह काले नोड तक पहुँचता है, तो उस पथ की पूरी जाँच हो चुकी है और उसमें कोई चक्र नहीं है, इसलिए उसे छोड़ दें।

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)]))               # False

पाठ्यक्रम अनुसूची: DAG में चक्र

पाठ्यक्रम अनुसूची (LeetCode #207) में पूछा जाता है कि क्या दी गई पूर्वापेक्षाओं के आधार पर सभी पाठ्यक्रम पूरे किए जा सकते हैं। पाठ्यक्रमों को नोड्स और पूर्वापेक्षाओं को निर्देशित किनारों के रूप में मॉडल करें। सभी पाठ्यक्रम तभी पूरे किए जा सकते हैं जब ग्राफ़ एक DAG हो (जिसमें कोई चक्र न हो)। 3-अवस्था वाले DFS से चक्र की पहचान करें — यदि चक्र मिले, तो असत्य लौटाएँ; अन्यथा सत्य लौटाएँ।

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 dependency

कान के एल्गोरिदम (BFS) से चक्र की पहचान

निर्देशित ग्राफ़ में चक्र की पहचान का एक वैकल्पिक तरीका कान का BFS टोपोलॉजिकल क्रम इस्तेमाल करता है। सभी नोड्स की आने वाली धाराओं की संख्या गिनें। जिन नोड्स की आने वाली धाराओं की संख्या 0 है, उन्हें एक कतार में डालें। प्रत्येक को संसाधित करें: पड़ोसी नोड्स की आने वाली धाराओं की संख्या घटाएँ और जिनकी संख्या 0 हो जाए, उन्हें कतार में डालें। यदि संसाधित नोड्स की संख्या V के बराबर है, तो कोई चक्र नहीं है; अन्यथा चक्र मौजूद है (असंसाधित नोड्स चक्र बनाते हैं)। यह O(V + E) तरीका 3-अवस्था वाले DFS की तुलना में अधिक सहज और याद रखने में आसान है।

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)]))               # False

चक्र ढूँढ़ना: चक्र वाले नोड्स एकत्र करना

कभी-कभी आपको केवल चक्र की मौजूदगी की पहचान नहीं करनी होती, बल्कि यह भी पता लगाना होता है कि कौन-से नोड चक्र का हिस्सा हैं। 3-अवस्था वाले DFS के दौरान जब पीछे लौटने वाला किनारा मिले, तो कॉल स्टैक (या पथ स्टैक) में पीछे जाकर पूर्वज और वर्तमान नोड के बीच के सभी नोड्स एकत्र करें। अवस्था वाली सरणी के साथ रखा गया पथ स्टैक वर्तमान DFS पथ को दर्ज करता है और O(चक्र_की_लंबाई) में चक्र का पुनर्निर्माण संभव बनाता है।

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]

अंततः सुरक्षित अवस्थाएँ ढूँढ़ना

अंततः सुरक्षित अवस्थाएँ ढूँढ़ना (LeetCode #802) में पूछा जाता है कि कौन-से नोड अंततः किसी अंतिम नोड (जिससे कोई बाहर जाने वाला किनारा न हो) तक पहुँचते हैं और चक्र में फँसते नहीं हैं। कोई नोड 'सुरक्षित' तब है जब उससे निकलने वाले सभी पथ अंतिम नोड्स तक पहुँचते हैं। 3-अवस्था वाला DFS इस्तेमाल करें: काले नोड (जिनकी पूरी जाँच चक्र पाए बिना हो चुकी है) सुरक्षित होते हैं। जो नोड चक्र का हिस्सा हैं या चक्र की ओर ले जाते हैं, वे सुरक्षित नहीं हैं।

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]

अनिर्देशित ग्राफ़ में अतिरिक्त किनारा

अतिरिक्त किनारा (LeetCode #684) उस किनारे को ढूँढ़ता है जो पहले से चक्र-रहित अनिर्देशित ग्राफ़ में जोड़ने पर चक्र बनाता है। इसे DFS द्वारा चक्र की पहचान से हल किया जा सकता है, लेकिन सबसे साफ़ समाधान यूनियन-फ़ाइंड (DSU) का उपयोग करता है: किनारों को एक-एक करके संसाधित करें; यदि दोनों अंतिम बिंदु पहले से जुड़े हुए हैं (एक ही घटक में हैं), तो वर्तमान किनारा चक्र बनाता है और यही उत्तर है। DSU प्रत्येक प्रक्रिया के लिए O(alpha(n)) देता है — व्यवहार में 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]

सारांश: चक्र की पहचान की रणनीतियाँ

चक्र की पहचान के साधनों का सारांश: अनिर्देशित ग्राफ़ के लिए मूल नोड का अभिलेख रखने वाला DFS या यूनियन-फ़ाइंड इस्तेमाल करें। निर्देशित ग्राफ़ के लिए 3-अवस्था वाला DFS (सफेद/धूसर/काला) या कान का BFS टोपोलॉजिकल क्रम इस्तेमाल करें। जब आप एक-एक करके किनारे जोड़ रहे हों (ऑनलाइन), तब यूनियन-फ़ाइंड चुनें। जब आपको टोपोलॉजिकल क्रम भी चाहिए हो, तब कान का एल्गोरिदम चुनें। जब आपको चक्र में शामिल विशिष्ट नोड्स पहचानने हों, तब 3-अवस्था वाला DFS चुनें। साक्षात्कार में चक्र की पहचान पर चर्चा करते समय हमेशा निर्देशित और अनिर्देशित ग्राफ़ों के बीच का अंतर स्पष्ट करें।

# 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')

त्वरित जाँच

इस पाठ में सिखाई गई डेटा संरचनाएँ और एल्गोरिदम — कोडिंग साक्षात्कार की तैयारी — से संबंधित अवधारणाओं की अपनी समझ जाँचें।

पाठ का पुनरावलोकन

इस पाठ में आपने सीखा: मूल नोड का अभिलेख रखने वाले DFS से अनिर्देशित चक्र की पहचान, 3-अवस्था वाले सफेद/धूसर/काले रंग-निर्धारण से निर्देशित चक्र की पहचान, निर्देशित ग्राफ़ के लिए कान का वैकल्पिक BFS, और पाठ्यक्रम अनुसूची, अतिरिक्त किनारा तथा अंततः सुरक्षित अवस्थाओं जैसे अनुप्रयोग। अब हम डायनेमिक प्रोग्रामिंग की बुनियादी बातों में गहराई से जाएँगे।

शुरुआत निःशुल्क

एआई शिक्षक के साथ Python सीखें — निःशुल्क

अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।

पाठ्यक्रम
30
पाठ
120

अक्सर पूछे जाने वाले प्रश्न

क्या “निर्देशित और अनिर्देशित ग्राफ में चक्र पहचान” पाठ निःशुल्क है?

हाँ — DSA Interview Prep अध्ययन पथ के 3 तक कोई भी पाठ, जिसमें “निर्देशित और अनिर्देशित ग्राफ में चक्र पहचान” भी शामिल है, यहाँ वेब पर पूरा पढ़ना निःशुल्क है। इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ इंटरैक्टिव अभ्यास भी उपलब्ध कराता है। DSA Interview Prep पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

“निर्देशित और अनिर्देशित ग्राफ में चक्र पहचान” में मैं क्या सीखूँगा?

अनिर्देशित ग्राफ में अभिभावक रिकॉर्ड रखकर और निर्देशित ग्राफ में DFS रंग-चिह्नन से चक्र पहचानिए; देखी गई तीन अवस्थाएँ सफ़ेद, धूसर और काली हैं। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ DSA Interview Prep का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।

क्या DSA Interview Prep शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?

पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर DSA Interview Prep शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 4वाँ पाठ है।

“निर्देशित और अनिर्देशित ग्राफ में चक्र पहचान” पाठ पूरा करने में कितना समय लगता है?

CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।

क्या मैं इस DSA Interview Prep पाठ में कोड लिख और चला सकता हूँ?

हाँ। हर DSA Interview Prep पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।

इस पाठ्यक्रम के सभी पाठ

  1. ग्राफ निरूपण और भ्रमण की तैयारी
  2. BFS: सबसे छोटा पथ और स्तर-भ्रमण
  3. DFS: जुड़े घटक और Flood Fill
  4. निर्देशित और अनिर्देशित ग्राफ में चक्र पहचान
← DSA Interview Prep पर वापस जाएँ