0Pricing
DSA Interview Prep · บทเรียน

อัลกอริทึมของ Kahn: การเรียงลำดับเชิงทอพอโลยีด้วย BFS

คำนวณดีกรีขาเข้าของโหนดทั้งหมด ใส่โหนดที่มีดีกรีขาเข้าเป็นศูนย์ลงคิว และประมวลผลคิวเพื่อสร้างลำดับเชิงทอพอโลยีพร้อมตรวจจับวงจร

อัลกอริทึมของ Kahn: การเรียงลำดับเชิงทอพอโลยีด้วย BFS เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

การเรียงลำดับเชิงทอพอโลยีคืออะไร

การเรียงลำดับเชิงทอพอโลยีของกราฟมีทิศทางแบบไม่มีวัฏจักร (DAG) คือการจัดลำดับโหนดโดยที่เส้นเชื่อมแบบมีทิศทางทุกเส้น u → v หมายความว่า u ต้องมาก่อน v ในลำดับนั้น ลำดับนี้แทนลำดับการทำงานที่ถูกต้องสำหรับงานซึ่งมีข้อกำหนดก่อนหน้า เช่น ระบบสร้างโปรแกรม การจัดตารางเรียน หรือการจัดการแพ็กเกจ เฉพาะ DAG เท่านั้นที่มีลำดับเชิงทอพอโลยีที่ถูกต้อง หากมีวงจรจะไม่สามารถจัดลำดับได้

อัลกอริทึมคาห์น: แนวคิดหลัก

อัลกอริทึมคาห์นเป็นวิธีเรียงลำดับเชิงทอพอโลยีที่อาศัย BFS แนวคิดสำคัญคือ โหนดที่มีดีกรีขาเข้าเป็น 0 (ไม่มีข้อกำหนดเบื้องต้น) สามารถวางไว้เป็นลำดับแรกได้ หลังจากวางโหนดนั้นแล้ว ให้ลบโหนดออกและลดดีกรีขาเข้าของโหนดข้างเคียงลง โหนดใหม่ที่มีดีกรีขาเข้าเป็นศูนย์จะพร้อมใช้งาน ทำซ้ำจนกว่าจะจัดวางโหนดครบทั้งหมดหรือตรวจพบวงจร (ยังมีโหนดที่มีดีกรีขาเข้าไม่เป็นศูนย์เหลืออยู่)

การคำนวณดีกรีขาเข้า

ขั้นแรก สร้างรายการการเชื่อมโยงและคำนวณดีกรีขาเข้า (จำนวนเส้นเชื่อมขาเข้า) ของแต่ละโหนด โหนดที่มีดีกรีขาเข้าเป็น 0 คือจุดเริ่มต้น เพราะไม่มีข้อกำหนดที่ต้องทำก่อน สำหรับกราฟที่มีเส้นเชื่อม [(0,1),(0,2),(1,3),(2,3)] ดีกรีขาเข้าคือ: 0→0, 1→1, 2→1, 3→2 มีเพียงโหนด 0 เท่านั้นที่เริ่มต้นด้วยดีกรีขาเข้าเป็น 0

from collections import deque, defaultdict

def compute_in_degree(n, edges):
    in_degree = [0] * n
    graph = defaultdict(list)
    for u, v in edges:
        graph[u].append(v)
        in_degree[v] += 1
    return graph, in_degree

graph, ind = compute_in_degree(4, [(0,1),(0,2),(1,3),(2,3)])
print('In-degrees:', ind)  # [0, 1, 1, 2]

การนำอัลกอริทึมคาห์นไปใช้งาน

ใส่โหนดทั้งหมดที่มีดีกรีขาเข้าเป็นศูนย์ลงในคิว ประมวลผลแต่ละโหนดโดยเพิ่มโหนดนั้นลงในผลลัพธ์ จากนั้นลดดีกรีขาเข้าของโหนดข้างเคียงแต่ละโหนดลง และใส่โหนดนั้นลงคิวหากดีกรีลดลงจนเป็น 0 หากรายการผลลัพธ์มีโหนดน้อยกว่าจำนวนโหนดในกราฟ แสดงว่ามีวงจรอยู่ เพราะมีโหนดบางส่วนที่ไม่สามารถนำออกจากคิวได้

from collections import deque, defaultdict

def kahn_topological_sort(n, edges):
    graph = defaultdict(list)
    in_degree = [0] * n
    for u, v in edges:
        graph[u].append(v)
        in_degree[v] += 1
    
    queue = deque(i for i in range(n) if in_degree[i] == 0)
    order = []
    
    while queue:
        node = queue.popleft()
        order.append(node)
        for nxt in graph[node]:
            in_degree[nxt] -= 1
            if in_degree[nxt] == 0:
                queue.append(nxt)
    
    if len(order) == n:
        return order   # valid topological sort
    return []          # cycle detected

print(kahn_topological_sort(4, [(0,1),(0,2),(1,3),(2,3)]))

การตรวจจับวงจรด้วยอัลกอริทึมคาห์น

อัลกอริทึมคาห์นมีการตรวจจับวงจรได้โดยไม่ต้องเพิ่มขั้นตอน: หาก len(order) < n แสดงว่ามีโหนดบางส่วนไม่เคยถูกเพิ่มลงคิว เพราะดีกรีขาเข้าของโหนดเหล่านั้นไม่เคยลดลงจนเป็น 0 โหนดเหล่านี้จึงเป็นส่วนหนึ่งของวงจร วิธีนี้สะอาดกว่าการดูแลอาร์เรย์ที่ทำเครื่องหมายการเยี่ยมชมด้วยสีต่าง ๆ ให้คืนค่ารายการว่างเพื่อสื่อว่ามีวงจรอยู่

# Cyclic graph: 0->1->2->0
edges_cycle = [(0,1),(1,2),(2,0)]
result = kahn_topological_sort(3, edges_cycle)
print(result)  # [] (cycle detected)

# Acyclic graph
edges_dag = [(0,1),(1,2)]
result = kahn_topological_sort(3, edges_dag)
print(result)  # [0, 1, 2]

ความซับซ้อนด้านเวลาและพื้นที่

อัลกอริทึมคาห์นประมวลผลแต่ละโหนดหนึ่งครั้ง (นำออกจากคิวหนึ่งครั้ง) และประมวลผลแต่ละเส้นเชื่อมหนึ่งครั้ง (ลดดีกรีขาเข้าหนึ่งครั้ง) ความซับซ้อนด้านเวลา: O(V + E) พื้นที่: O(V + E) สำหรับรายการการเชื่อมโยงและอาร์เรย์ดีกรีขาเข้า รวมถึง O(V) สำหรับคิว นี่เป็นค่าที่เหมาะสมที่สุด เพราะอย่างน้อยต้องอ่านโหนดและเส้นเชื่อมทั้งหมดเพื่อสร้างลำดับที่ถูกต้อง

ลำดับเชิงทอพอโลยีที่เล็กที่สุดตามลำดับพจนานุกรม

การใช้อัลกอริทึมคาห์นร่วมกับฮีปค่าต่ำสุดแทนคิวจะสร้างลำดับเชิงทอพอโลยีที่เล็กที่สุดตามลำดับพจนานุกรม ให้แทนที่ deque ด้วย heapq โดยเพิ่ม (node) และประมวลผลโหนดที่มีค่าต่ำที่สุดซึ่งพร้อมใช้งานก่อนเสมอ วิธีนี้รับประกันลำดับที่ถูกต้องและมีค่าต่ำที่สุดตามลำดับพจนานุกรมเมื่อเทียบกับการเรียงลำดับเชิงทอพอโลยีที่เป็นไปได้ทั้งหมด

import heapq
from collections import defaultdict

def kahn_lex_order(n, edges):
    graph = defaultdict(list)
    in_degree = [0] * n
    for u, v in edges:
        graph[u].append(v)
        in_degree[v] += 1
    
    heap = [i for i in range(n) if in_degree[i] == 0]
    heapq.heapify(heap)
    order = []
    
    while heap:
        node = heapq.heappop(heap)
        order.append(node)
        for nxt in graph[node]:
            in_degree[nxt] -= 1
            if in_degree[nxt] == 0:
                heapq.heappush(heap, nxt)
    
    return order if len(order) == n else []

print(kahn_lex_order(6, [(5,2),(5,0),(4,0),(4,1),(2,3),(3,1)]))

การประยุกต์ใช้: การจัดตารางเรียน I

การจัดตารางเรียน (LeetCode 207): เมื่อกำหนดรายวิชา n วิชาและข้อกำหนดก่อนเรียน ให้ตรวจสอบว่าสามารถเรียนให้ครบทุกวิชาได้หรือไม่ จำลองข้อกำหนดก่อนเรียนเป็นเส้นเชื่อมแบบมีทิศทาง แล้วตรวจสอบว่ามีการเรียงลำดับเชิงทอพอโลยีที่ถูกต้องหรือไม่ (กล่าวคือ ไม่มีวงจร) คืนค่าเป็นจริงหากผลลัพธ์ของอัลกอริทึมคาห์นมีลำดับยาว n และคืนค่าเป็นเท็จหากตรวจพบวงจร

from collections import deque, defaultdict

def canFinish(numCourses, prerequisites):
    graph = defaultdict(list)
    in_degree = [0] * numCourses
    for a, b in prerequisites:   # b must be taken before a
        graph[b].append(a)
        in_degree[a] += 1
    
    queue = deque(i for i in range(numCourses) if in_degree[i] == 0)
    count = 0
    while queue:
        node = queue.popleft()
        count += 1
        for nxt in graph[node]:
            in_degree[nxt] -= 1
            if in_degree[nxt] == 0:
                queue.append(nxt)
    
    return count == numCourses

print(canFinish(2, [[1,0]]))       # True
print(canFinish(2, [[1,0],[0,1]])) # False (cycle)

การประยุกต์ใช้: การจัดตารางเรียน II

การจัดตารางเรียน II (LeetCode 210): ให้คืนค่าลำดับจริงที่ควรเรียนรายวิชา ใช้วิธีเดียวกับด้านบน แต่คืนค่ารายการ order แทนค่าบูลีน หากมีวงจร ให้คืนค่ารายการว่าง วิธีนี้ใช้ผลลัพธ์ของอัลกอริทึมคาห์นเป็นคำตอบโดยตรง

from collections import deque, defaultdict

def findOrder(numCourses, prerequisites):
    graph = defaultdict(list)
    in_degree = [0] * numCourses
    for a, b in prerequisites:
        graph[b].append(a)
        in_degree[a] += 1
    
    queue = deque(i for i in range(numCourses) if in_degree[i] == 0)
    order = []
    while queue:
        node = queue.popleft()
        order.append(node)
        for nxt in graph[node]:
            in_degree[nxt] -= 1
            if in_degree[nxt] == 0:
                queue.append(nxt)
    
    return order if len(order) == numCourses else []

print(findOrder(4, [[1,0],[2,0],[3,1],[3,2]]))

การจัดตารางงานแบบขนาน

การใช้งานขั้นสูงขึ้นคือ เมื่อกำหนดงานที่มีข้อกำหนดก่อนหน้า ให้หาจำนวนรอบขั้นต่ำที่ต้องใช้ หากงานที่ไม่มีข้อกำหนดก่อนหน้าสามารถทำงานพร้อมกันได้ ประมวลผลอัลกอริทึมคาห์นทีละระดับ (คล้ายการประมวลผลตามระดับของ BFS) ใส่โหนดทั้งหมดที่มีดีกรีขาเข้าเป็นศูนย์ลงคิว ประมวลผลคิวปัจจุบันทั้งหมดเป็นหนึ่งรอบ จากนั้นใส่โหนดที่เพิ่งพร้อมใช้งานลงคิวเป็นรอบถัดไป แล้วนับจำนวนรอบ

from collections import deque, defaultdict

def min_rounds(n, edges):
    graph = defaultdict(list)
    in_degree = [0] * n
    for u, v in edges:
        graph[u].append(v)
        in_degree[v] += 1
    
    queue = deque(i for i in range(n) if in_degree[i] == 0)
    rounds = 0
    while queue:
        rounds += 1
        for _ in range(len(queue)):  # process current level
            node = queue.popleft()
            for nxt in graph[node]:
                in_degree[nxt] -= 1
                if in_degree[nxt] == 0:
                    queue.append(nxt)
    return rounds

print(min_rounds(4, [(0,2),(1,2),(2,3)]))  # 3

การเรียงลำดับเชิงทอพอโลยีและ DP บน DAG

การเรียงลำดับเชิงทอพอโลยีช่วยให้ทำการเขียนโปรแกรมแบบพลวัตบน DAGได้ โดยประมวลผลโหนดตามลำดับเชิงทอพอโลยี และเมื่อคำนวณ dp[v] ค่า dp[u] ของโหนดก่อนหน้าทั้งหมดจะถูกคำนวณเสร็จสมบูรณ์แล้ว วิธีนี้ผสานการเรียงลำดับเชิงทอพอโลยีเข้ากับ DP สำหรับปัญหาอย่างเส้นทางยาวที่สุดใน DAG ค่าใช้จ่ายต่ำสุดในการไปถึงทุกโหนด หรือกำไรสูงสุดจากสายโซ่ของข้อกำหนดก่อนหน้า ลำดับดังกล่าวรับประกันว่าค่า DP ของแต่ละโหนดจะถูกคำนวณเพียงครั้งเดียว หลังจากประมวลผลข้อกำหนดที่เกี่ยวข้องทั้งหมดแล้ว

from collections import deque, defaultdict

def longest_path_dag(V, edges):
    graph = defaultdict(list)
    in_degree = [0] * V
    for u, v, w in edges:
        graph[u].append((v, w))
        in_degree[v] += 1
    queue = deque(i for i in range(V) if in_degree[i] == 0)
    dp = [0] * V
    while queue:
        u = queue.popleft()
        for v, w in graph[u]:
            dp[v] = max(dp[v], dp[u] + w)
            in_degree[v] -= 1
            if in_degree[v] == 0: queue.append(v)
    return max(dp)

print(longest_path_dag(4, [(0,1,3),(0,2,2),(1,3,4),(2,3,1)]))  # 7

ตรวจสอบความเข้าใจ

ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้

ทบทวนบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า อัลกอริทึมคาห์นคำนวณการเรียงลำดับเชิงทอพอโลยีด้วยการลบโหนดที่มีดีกรีขาเข้าเป็นศูนย์ซ้ำ ๆ โดยใช้ BFS การตรวจจับวงจรทำได้โดยไม่ต้องเพิ่มขั้นตอน — หากจำนวนสมาชิกในลำดับน้อยกว่า n แสดงว่ามีวงจรอยู่ และ การแทนที่คิวด้วยฮีปค่าต่ำสุดจะให้ลำดับเชิงทอพอโลยีที่เล็กที่สุดตามลำดับพจนานุกรม บทถัดไป เราจะสำรวจการเรียงลำดับเชิงทอพอโลยีแบบหลังการเยี่ยมชมด้วย DFS ซึ่งเป็นทางเลือกอีกแบบนอกเหนือจากอัลกอริทึมคาห์น

คำถามที่พบบ่อย

บทเรียน “อัลกอริทึมของ Kahn: การเรียงลำดับเชิงทอพอโลยีด้วย BFS” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “อัลกอริทึมของ Kahn: การเรียงลำดับเชิงทอพอโลยีด้วย BFS” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “อัลกอริทึมของ Kahn: การเรียงลำดับเชิงทอพอโลยีด้วย BFS”

คำนวณดีกรีขาเข้าของโหนดทั้งหมด ใส่โหนดที่มีดีกรีขาเข้าเป็นศูนย์ลงคิว และประมวลผลคิวเพื่อสร้างลำดับเชิงทอพอโลยีพร้อมตรวจจับวงจร คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “อัลกอริทึมของ Kahn: การเรียงลำดับเชิงทอพอโลยีด้วย BFS” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม

ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. อัลกอริทึมของ Kahn: การเรียงลำดับเชิงทอพอโลยีด้วย BFS
  2. การเรียงลำดับเชิงทอพอโลยีด้วยลำดับหลังของ DFS
  3. ตารางเรียน I และ II
  4. องค์ประกอบเชื่อมโยงอย่างแน่นหนาด้วย Kosaraju
← กลับไปที่ DSA Interview Prep