อัลกอริทึมของ 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- อัลกอริทึมของ Kahn: การเรียงลำดับเชิงทอพอโลยีด้วย BFS
- การเรียงลำดับเชิงทอพอโลยีด้วยลำดับหลังของ DFS
- ตารางเรียน I และ II
- องค์ประกอบเชื่อมโยงอย่างแน่นหนาด้วย Kosaraju