পাঠ ৩১ · ৫৭-এর মধ্যে · মডিউল ৭
Home / Courses / Design and Analysis of Algorithms / টপোলজিক্যাল সর্ট

টপোলজিক্যাল সর্ট

Topological sort
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • টপোলজিক্যাল সর্টের আনুষ্ঠানিক সংজ্ঞা এবং এর অস্তিত্বের শর্ত (DAG হওয়া আবশ্যক)
  • Kahn's অ্যালগরিদমের সম্পূর্ণ ইমপ্লিমেন্টেশন (ইন-ডিগ্রি ট্র্যাকিং + BFS queue)
  • কেন Kahn's অ্যালগরিদম সাইকেল থাকলে স্বয়ংক্রিয়ভাবে তা সনাক্ত করে
  • একটি এক্সপ্লিসিট এজ-চেক লুপ দিয়ে আউটপুট অর্ডারের সঠিকতা যাচাইয়ের পদ্ধতি

১ · টপোলজিক্যাল সর্ট কী এবং কেন এটি গুরুত্বপূর্ণ

একটি ডাইরেক্টেড গ্রাফ $G=(V,E)$-এর একটি টপোলজিক্যাল অর্ডারTopological Orderভার্টেক্সগুলোর একটি লিনিয়ার সিকোয়েন্স যেখানে প্রতিটি ডাইরেক্টেড এজ $u \to v$-এর জন্য $u$, $v$-এর আগে আসে। হলো ভার্টেক্সগুলোর এমন একটি লিনিয়ার সাজানো $v_1, v_2, \ldots, v_n$ যেখানে প্রতিটি এজ $(u,v) \in E$-এর জন্য $u$, $v$-এর আগে আসে। বাস্তব-জীবনের উদাহরণ: কোর্স প্রি-রিকুইজিট (একটি কোর্স নেওয়ার আগে তার প্রি-রিকুইজিট কোর্স শেষ করতে হবে), বিল্ড সিস্টেম ডিপেন্ডেন্সি (একটি ফাইল কম্পাইল করার আগে তার ডিপেন্ডেন্সি কম্পাইল করতে হবে), স্প্রেডশিট সেল ইভ্যালুয়েশন অর্ডার ইত্যাদি।

মূল দাবি: একটি গ্রাফের অন্তত একটি টপোলজিক্যাল অর্ডার থাকে যদি এবং শুধুমাত্র যদি গ্রাফে কোনো ডাইরেক্টেড সাইকেল না থাকে — অর্থাৎ এটি একটি DAG (Directed Acyclic Graph)। এটি স্বজ্ঞাতভাবেই বোঝা যায়: যদি একটি সাইকেল $v_1 \to v_2 \to \cdots \to v_k \to v_1$ থাকে, তাহলে $v_1$-কে $v_2$-এর আগে আসতে হবে (এজ $v_1 \to v_2$), কিন্তু আবার $v_k \to v_1$ এজের কারণে $v_k$-কেও $v_1$-এর আগে আসতে হবে, এবং $v_1,\ldots,v_k$ চেইনের মাধ্যমে $v_1$-কে নিজের আগে আসতে হবে — একটি স্ববিরোধিতা। তাই সাইকেলযুক্ত গ্রাফের কোনো টপোলজিক্যাল অর্ডার থাকতে পারে না।

২ · Kahn's অ্যালগরিদম — ইন-ডিগ্রি ট্র্যাকিং

প্রতিটি ভার্টেক্স $v$-এর ইন-ডিগ্রি হলো তার দিকে আসা এজের সংখ্যা। মূল অন্তর্দৃষ্টি: যদি একটি ভার্টেক্সের ইন-ডিগ্রি ০ হয়, তার মানে কোনো এজ তার আগে থাকার দাবি করছে না — তাই এটিকে নিরাপদে অর্ডারে এখনই বসানো যায়। এটি বসানোর পর, এর আউটগোয়িং এজগুলো "সরিয়ে ফেলা" (এবং সেই প্রতিবেশীদের ইন-ডিগ্রি ১ কমানো) যায়, যা নতুন শূন্য-ইন-ডিগ্রি ভার্টেক্স তৈরি করতে পারে।

সব ভার্টেক্সের ইন-ডিগ্রি গণনা ইন-ডিগ্রি ০ এমন সব ভার্টেক্স queue-এ রাখা dequeue করে অর্ডারে যোগ + প্রতিবেশীর ইন-ডিগ্রি -১ queue খালি না হওয়া পর্যন্ত পুনরাবৃত্তি
প্রতিবার একটি ইন-ডিগ্রি-০ ভার্টেক্স সরানো হলে নতুন ইন-ডিগ্রি-০ ভার্টেক্স তৈরি হতে পারে — এই প্রক্রিয়া চলতে থাকে যতক্ষণ না queue খালি হয়ে যায়।

যদি সব ভার্টেক্স অর্ডারে যোগ হয়ে যায় (আউটপুট লেন্থ $=|V|$), গ্রাফটি একটি DAG ছিল এবং আমরা একটি বৈধ টপোলজিক্যাল অর্ডার পেয়েছি। কিন্তু যদি প্রক্রিয়া থেমে যাওয়ার পরও কিছু ভার্টেক্স বাকি থেকে যায় (তাদের ইন-ডিগ্রি কখনো ০-তে পৌঁছায়নি), তার মানে সেই বাকি ভার্টেক্সগুলো একটি সাইকেলের অংশ — Kahn's অ্যালগরিদম বাড়তি কাজ ছাড়াই সাইকেল ডিটেকশন বিনামূল্যে দেয়। এই কোর্সে আমরা Kahn's অ্যালগরিদম বেছে নিয়েছি (বিকল্প: DFS-ভিত্তিক ফিনিশ-টাইম রিভার্সাল পদ্ধতি, যা L32-এর কোসারাজু অ্যালগরিদমে ব্যবহৃত ধারণার সাথে সম্পর্কিত)।

৩ · ইমপ্লিমেন্টেশন ও যাচাই — প্রতিটি এজ চেক করা

একটি আউটপুট অর্ডার "সঠিক" প্রমাণ করার সবচেয়ে বিশ্বাসযোগ্য উপায় হলো প্রতিটি এজ $(u,v)$-এর জন্য সরাসরি চেক করা যে অর্ডারে $u$-এর পজিশন $v$-এর পজিশনের আগে কি না — এটি সংজ্ঞা থেকে সরাসরি আসা একটি এক্সহস্টিভ চেক, তাই এটি অ্যালগরিদমের নিজস্ব লজিকের উপর নির্ভর না করেই সঠিকতা প্রমাণ করে।

Python
from collections import deque

def kahn_topo_sort(edges, vertices):
    in_degree = {v: 0 for v in vertices}
    for u in vertices:
        for v in edges.get(u, []):
            in_degree[v] += 1

    queue = deque([v for v in vertices if in_degree[v] == 0])
    order = []
    while queue:
        u = queue.popleft()
        order.append(u)
        for v in edges.get(u, []):
            in_degree[v] -= 1
            if in_degree[v] == 0:
                queue.append(v)
    return order

def verify_topo_order(edges, order):
    # সংজ্ঞা থেকে সরাসরি: প্রতিটি এজ (u, v)-এর জন্য order-এ u অবশ্যই v-এর আগে থাকতে হবে
    position = {v: i for i, v in enumerate(order)}
    violations = []
    for u in edges:
        for v in edges[u]:
            if position[u] > position[v]:
                violations.append((u, v))
    return violations

test_dags = [
    {  # DAG ১: কোর্স প্রি-রিকুইজিট চেইন
        "vertices": ["A", "B", "C", "D", "E", "F"],
        "edges": {"A": ["B", "C"], "B": ["D"], "C": ["D", "E"], "D": ["F"], "E": ["F"], "F": []},
    },
    {  # DAG ২: প্রশস্ত (wide) গ্রাফ, একাধিক ইন-ডিগ্রি-০ ভার্টেক্স একসাথে
        "vertices": list(range(8)),
        "edges": {0: [1, 2], 1: [3], 2: [3, 4], 3: [5], 4: [5, 6], 5: [7], 6: [7], 7: []},
    },
    {  # DAG ৩: একাধিক ডিসকানেক্টেড কম্পোনেন্ট + একটি আইসোলেটেড ভার্টেক্স
        "vertices": ["x", "y", "z", "w", "iso"],
        "edges": {"x": ["y"], "y": ["z"], "z": [], "w": ["z"], "iso": []},
    },
]

for i, dag in enumerate(test_dags, 1):
    vertices, edges = dag["vertices"], dag["edges"]
    order = kahn_topo_sort(edges, vertices)
    violations = verify_topo_order(edges, order)
    complete = len(order) == len(vertices)
    print(f"DAG {i}: order = {order}")
    print(f"  সব ভার্টেক্স অর্ডারে আছে: {complete} | এজ লঙ্ঘন: {violations if violations else 'নেই'}")
    assert complete, f"DAG {i}: কিছু ভার্টেক্স অর্ডারে অনুপস্থিত -- সাইকেল সন্দেহজনক!"
    assert not violations, f"DAG {i}: এজ লঙ্ঘন পাওয়া গেছে -- অর্ডার ভুল!"

print("\nসব DAG-এ Kahn's অ্যালগরিদমের আউটপুট প্রতিটি এজ সম্মান করেছে।")

# একটি সাইকেলযুক্ত গ্রাফে Kahn's অ্যালগরিদম যে অসম্পূর্ণ অর্ডার দেয় তা দেখা
cyclic_edges = {"P": ["Q"], "Q": ["R"], "R": ["P"]}
cyclic_vertices = ["P", "Q", "R"]
cyclic_order = kahn_topo_sort(cyclic_edges, cyclic_vertices)
print(f"\nসাইকেলযুক্ত গ্রাফে Kahn's আউটপুট: {cyclic_order} (দৈর্ঘ্য {len(cyclic_order)}, মোট ভার্টেক্স {len(cyclic_vertices)})")
print("সাইকেল ডিটেক্টেড:", len(cyclic_order) < len(cyclic_vertices))

    
লক্ষ্য করুন DAG ২-তে একাধিক ভার্টেক্সের ইন-ডিগ্রি একসাথে ০ হতে পারে (যেমন শুরুতে শুধু ভার্টেক্স ০), এবং পরবর্তীতে একই সাথে একাধিক ভার্টেক্স queue-তে যোগ হতে পারে — এর মানে একাধিক বৈধ টপোলজিক্যাল অর্ডার থাকতে পারে একই DAG-এর জন্য (queue-এর ভেতরে কোন ভার্টেক্স আগে dequeue হবে তার উপর নির্ভর করে)। verify_topo_order যেকোনো বৈধ অর্ডারকে সঠিক হিসেবে গ্রহণ করে, কারণ এটি একটি নির্দিষ্ট অর্ডারের সাথে তুলনা করে না — শুধু এজ কনস্ট্রেইন্ট মেনে চলা হয়েছে কি না তা চেক করে।
মূল কথা · Key takeaway

Kahn's অ্যালগরিদমের কমপ্লেক্সিটি $\Theta(V+E)$ (L30-এর BFS-এর মতোই — প্রতিটি ভার্টেক্স ও এজ ঠিক একবার প্রসেস হয়)। এর সঠিকতা এজ-চেক লুপ দিয়ে সরাসরি ভেরিফাই করা সম্ভব, এবং এটি একইসাথে সাইকেল ডিটেকশনও দেয় — এই দ্বৈত ব্যবহারিকতা এটিকে বাস্তব-জীবনের ডিপেন্ডেন্সি-রিজলিউশন সিস্টেমে (প্যাকেজ ম্যানেজার, বিল্ড টুল) খুবই জনপ্রিয় করে তুলেছে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ একই DAG-এর একাধিক বৈধ টপোলজিক্যাল অর্ডার থাকতে পারে কেন — এবং কখন একটি DAG-এর ঠিক একটিই টপোলজিক্যাল অর্ডার থাকবে?

যদি কোনো মুহূর্তে queue-তে একাধিক ইন-ডিগ্রি-০ ভার্টেক্স থাকে, তাদের যেকোনো ক্রমে dequeue করা বৈধ — তাই একাধিক সঠিক অর্ডার সম্ভব। একটি DAG-এর ঠিক একটিই টপোলজিক্যাল অর্ডার থাকবে যদি এবং শুধুমাত্র যদি প্রতিটি ধাপে queue-তে সবসময় ঠিক একটি ভার্টেক্স থাকে — অর্থাৎ গ্রাফে একটি হ্যামিল্টোনিয়ান পাথ থাকে যা প্রতিটি ভার্টেক্স জোড়ার মধ্যে একটি ডাইরেক্ট বা ইনডাইরেক্ট অর্ডারিং সম্পর্ক বাধ্য করে।

প্র ০২ DFS-ভিত্তিক টপোলজিক্যাল সর্ট (ফিনিশ-টাইম রিভার্সাল) কীভাবে কাজ করে, এবং Kahn's-এর তুলনায় এটি কেন সমতুল্য?

DFS-ভিত্তিক পদ্ধতিতে প্রতিটি ভার্টেক্সের উপর DFS চালানো হয় এবং প্রতিটি ভার্টেক্স তার সব প্রতিবেশী সম্পূর্ণ এক্সপ্লোর হওয়ার পর একটি স্ট্যাকে "ফিনিশ" হিসেবে পুশ করা হয় — শেষে স্ট্যাক রিভার্স করলেই টপোলজিক্যাল অর্ডার পাওয়া যায়। এটি সমতুল্য কারণ একটি এজ $u \to v$-এর জন্য $v$ সবসময় $u$-এর আগে ফিনিশ হবে (DAG-তে $v$ $u$-এর সাবট্রিতে থাকতে পারবে না), তাই রিভার্স ফিনিশ-অর্ডারে $u$ সবসময় $v$-এর আগে আসবে।

প্র ০৩ যদি একটি ভার্টেক্সের কোনো ইনকামিং বা আউটগোয়িং এজ না থাকে (সম্পূর্ণ আইসোলেটেড), তাহলে এটি টপোলজিক্যাল অর্ডারে কোথায় থাকতে পারে?

যেকোনো জায়গায় — যেহেতু কোনো এজ এটিকে কোনো অন্য ভার্টেক্সের সাপেক্ষে অবস্থান নিতে বাধ্য করছে না, তাই এটি অর্ডারের শুরুতে, মাঝে বা শেষে যেকোনো জায়গায় বসালে সব এজ কনস্ট্রেইন্ট মেনে চলবে। উপরের DAG ৩-এর "iso" ভার্টেক্সটি এর একটি উদাহরণ — Kahn's অ্যালগরিদম এটিকে প্রথম দিকেই queue-তে পাবে (ইন-ডিগ্রি শুরু থেকেই ০) এবং প্রসেস করার সময় যেখানেই থাকুক তা বৈধ থাকবে।

অনুশীলন

  1. চিন্তা করুন: DAG ১-এ যদি একটি নতুন এজ "F" -> "A" যোগ করা হতো, তাহলে kahn_topo_sort-এর আউটপুট দৈর্ঘ্য কী হতো বলে আপনার ধারণা?

    এই নতুন এজটি একটি সাইকেল তৈরি করবে (A → B/C → ... → F → A), তাই গ্রাফটি আর DAG থাকবে না — আউটপুট অর্ডারের দৈর্ঘ্য মোট ভার্টেক্স সংখ্যা (৬)-এর চেয়ে কম হবে, কারণ সাইকেলের অংশ ভার্টেক্সগুলোর ইন-ডিগ্রি কখনো ০-তে পৌঁছাবে না।

  2. পরীক্ষা করুন: কোড সেলে DAG ১-এর edges ডিকশনারিতে "F": ["A"] পরিবর্তন করে (মূল খালি লিস্টের বদলে) Run চেপে আপনার অনুমান যাচাই করুন।

    আউটপুটে দেখা যাবে order-এর দৈর্ঘ্য ৬-এর কম (মূল ইন-ডিগ্রি-শূন্য ভার্টেক্স "A"-ও আর প্রথমে queue-তে ঢুকবে না, কারণ তার ইন-ডিগ্রি এখন ১), এবং complete ভ্যারিয়েবল False দেখাবে — সাইকেল ডিটেক্টেড।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ SCC, শর্টেস্ট পাথ, ম্যাক্স ফ্লো, ব্যাকট্র্যাকিং, স্ট্রিং অ্যালগরিদম, অ্যামর্টাইজড অ্যানালাইসিস, NP-কমপ্লিটনেস ও অ্যাপ্রক্সিমেশন — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • Data Structures & Algorithms কোর্স সহোদর কোর্স টপোলজিক্যাল সর্টের মৌলিক ইমপ্লিমেন্টেশন ও প্র্যাকটিস প্রবলেম সেই কোর্সে কভার করা হয়েছে।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks, Mobile App Development, Ethics in Computing & AI Safety, Software Testing & Quality Assurance ও Design and Analysis of Algorithms — সব এক জায়গায়।
পূর্ববর্তী পাঠ
BFS/DFS কমপ্লেক্সিটি ও সঠিকতা