อัลกอริธึมที่มีประสิทธิภาพที่สุดในการตรวจจับรอบทั้งหมดภายในกราฟที่กำกับคืออะไร?
ฉันมีกราฟกำกับที่แสดงถึงกำหนดการงานที่ต้องดำเนินการงานเป็นโหนดและการพึ่งพาเป็นขอบ ฉันต้องการตรวจสอบกรณีข้อผิดพลาดของวัฏจักรภายในกราฟนี้ซึ่งนำไปสู่การพึ่งพาแบบวนรอบ
อัลกอริธึมที่มีประสิทธิภาพที่สุดในการตรวจจับรอบทั้งหมดภายในกราฟที่กำกับคืออะไร?
ฉันมีกราฟกำกับที่แสดงถึงกำหนดการงานที่ต้องดำเนินการงานเป็นโหนดและการพึ่งพาเป็นขอบ ฉันต้องการตรวจสอบกรณีข้อผิดพลาดของวัฏจักรภายในกราฟนี้ซึ่งนำไปสู่การพึ่งพาแบบวนรอบ
คำตอบ:
อัลกอริธึมการเชื่อมต่อที่รุนแรงของ TarjanมีO(|E| + |V|)
ความซับซ้อนของเวลา
สำหรับอัลกอริทึมอื่น ๆ ให้ดูส่วนประกอบที่เชื่อมต่ออย่างมากใน Wikipedia
O(|E| + |V|)
นอกจากนี้ยังมีรันไทม์เดียวกัน ใช้สีขาว (ไม่เคยเยี่ยมชม), สีเทา (โหนดปัจจุบันถูกเยี่ยมชม แต่โหนดที่เข้าถึงได้ทั้งหมดยังไม่ได้เยี่ยมชม) และสีดำ (โหนดที่เข้าถึงได้ทั้งหมดจะถูกเยี่ยมชมพร้อมกับโหนดปัจจุบัน) การเข้ารหัสสีหากโหนดสีเทาพบโหนดสีเทาอื่น มีรอบ [ค่อนข้างดีสิ่งที่เราอยู่ในหนังสืออัลกอริทึมของ Cormen] สงสัยว่า 'อัลกอริทึมของ Tarjan' มีประโยชน์มากกว่า DFS เช่นนี้หรือไม่ !!
ระบุว่านี่เป็นตารางเวลาของงานฉันสงสัยว่าในบางจุดคุณจะเรียงลำดับงานตามคำสั่งที่เสนอ
หากเป็นเช่นนั้นการดำเนินการจัดเรียงโทโพโลยีอาจในกรณีใด ๆ ที่ตรวจพบรอบ ยูนิกซ์ทำtsort
อย่างแน่นอน ฉันคิดว่ามันเป็นไปได้ที่จะมีประสิทธิภาพมากขึ้นในการตรวจสอบรอบในเวลาเดียวกันกับการคัดแยกมากกว่าในขั้นตอนที่แยกต่างหาก
ดังนั้นคำถามอาจกลายเป็น "ฉันจะส่งผลอย่างมีประสิทธิภาพมากที่สุด" ได้อย่างไรแทนที่จะเป็น "ฉันจะตรวจจับลูปได้อย่างมีประสิทธิภาพมากที่สุด" ได้อย่างไร คำตอบที่น่าจะเป็น "ใช้ห้องสมุด" แต่ล้มเหลวที่บทความ Wikipedia ดังต่อไปนี้:
มีโค้ดหลอกสำหรับอัลกอริทึมหนึ่งและคำอธิบายสั้น ๆ ของอีกอันจาก Tarjan ทั้งสองมีO(|V| + |E|)
ความซับซ้อนของเวลา
วิธีที่ง่ายที่สุดที่จะทำก็คือการทำลึกสำรวจเส้นทางแรก (DFT) ของกราฟ
หากกราฟมีn
จุดยอดนี่เป็นO(n)
ขั้นตอนวิธีความซับซ้อนของเวลา O(n^2)
เนื่องจากคุณอาจจะต้องทำผิวเผินเริ่มต้นจากจุดสุดยอดแต่ละความซับซ้อนทั้งหมดจะกลายเป็น
คุณต้องรักษาสแต็คที่มีจุดยอดทั้งหมดในความลึกของการสำรวจเส้นทางแรกด้วยองค์ประกอบแรกที่เป็นโหนดรูต หากคุณเจอองค์ประกอบที่มีอยู่แล้วในสแต็กระหว่าง DFT แสดงว่าคุณมีรอบ
O(n)
ในขณะที่คุณแนะนำให้ตรวจสอบสแต็กเพื่อดูว่ามันมีโหนดที่เยี่ยมชมอยู่แล้ว? การสแกนสแต็กจะเพิ่มเวลาในการO(n)
รันไทม์เนื่องจากต้องสแกนสแต็กในแต่ละโหนดใหม่ คุณสามารถทำได้O(n)
ถ้าคุณทำเครื่องหมายโหนดที่เข้าชม
อ้างอิงจาก Lemma 22.11 ของCormen et al., Introduction to Algorithms (CLRS):
กราฟกำกับ G คือ acyclic ถ้าหากการค้นหาความลึกครั้งแรกของ G ไม่ทำให้เกิดขอบหลัง
สิ่งนี้ถูกกล่าวถึงในหลายคำตอบ ที่นี่ฉันจะให้ตัวอย่างรหัสตามบทที่ 22 ของ CLRS ตัวอย่างกราฟแสดงไว้ด้านล่าง
รหัสหลอกของ CLRS สำหรับการอ่านเชิงลึกครั้งแรก:
ในตัวอย่างในรูปที่ 22.4 CLRS กราฟประกอบด้วยสองต้นไม้ DFS: หนึ่งประกอบด้วยโหนดU , V , XและY , และอื่น ๆ ของโหนดWและZ แต่ละต้นประกอบด้วยหนึ่งขอบหลัง: หนึ่งจากxถึงvและอีกต้นจากzถึงz (self-loop)
การสำนึกหลักคือการที่ขอบด้านหลังจะเกิดขึ้นเมื่อในDFS-VISIT
ฟังก์ชั่นในขณะที่วนรอบเพื่อนบ้านv
ของu
โหนดพบกับGRAY
สี
รหัส Python ต่อไปนี้เป็นการดัดแปลง pseudocode ของ CLRS พร้อมกับif
clause ที่เพิ่มซึ่งตรวจพบรอบ:
import collections
class Graph(object):
def __init__(self, edges):
self.edges = edges
self.adj = Graph._build_adjacency_list(edges)
@staticmethod
def _build_adjacency_list(edges):
adj = collections.defaultdict(list)
for edge in edges:
adj[edge[0]].append(edge[1])
return adj
def dfs(G):
discovered = set()
finished = set()
for u in G.adj:
if u not in discovered and u not in finished:
discovered, finished = dfs_visit(G, u, discovered, finished)
def dfs_visit(G, u, discovered, finished):
discovered.add(u)
for v in G.adj[u]:
# Detect cycles
if v in discovered:
print(f"Cycle detected: found a back edge from {u} to {v}.")
# Recurse into DFS tree
if v not in finished:
dfs_visit(G, v, discovered, finished)
discovered.remove(u)
finished.add(u)
return discovered, finished
if __name__ == "__main__":
G = Graph([
('u', 'v'),
('u', 'x'),
('v', 'y'),
('w', 'y'),
('w', 'z'),
('x', 'v'),
('y', 'x'),
('z', 'z')])
dfs(G)
โปรดทราบว่าในตัวอย่างนี้time
pseudocode ใน CLRS ไม่ได้ถูกดักจับเพราะเราสนใจตรวจจับวงจรเท่านั้น นอกจากนี้ยังมีรหัสสำเร็จรูปสำหรับการสร้างการแสดงรายการ adjacency ของกราฟจากรายการของขอบ
เมื่อสคริปต์นี้ทำงานสคริปต์จะพิมพ์ผลลัพธ์ต่อไปนี้:
Cycle detected: found a back edge from x to v.
Cycle detected: found a back edge from z to z.
นี่คือขอบด้านหลังในตัวอย่างใน CLRS รูปที่ 22.4
เริ่มต้นด้วยการ DFS: วงจรที่มีอยู่และถ้าหากกลับขอบถูกค้นพบในช่วง DFS สิ่งนี้พิสูจน์ได้ว่าเป็นผลมาจากทฤษฎีเส้นทางสีขาว
ในความคิดของฉันอัลกอริทึมที่เข้าใจได้มากที่สุดสำหรับการตรวจสอบวงจรในกราฟกำกับคือกราฟสี - อัลกอริทึม
โดยทั่วไปอัลกอริทึมการระบายสีกราฟจะเดินกราฟในลักษณะ DFS (ค้นหาความลึกครั้งแรกซึ่งหมายความว่ามันสำรวจเส้นทางอย่างสมบูรณ์ก่อนที่จะสำรวจเส้นทางอื่น) เมื่อพบว่าขอบด้านหลังจะทำเครื่องหมายกราฟว่ามีลูป
สำหรับคำอธิบายเชิงลึกของอัลกอริทึมการระบายสีกราฟโปรดอ่านบทความนี้: http://www.geeksforgeeks.org/detect-cycle-direct-graph-using-colors/
นอกจากนี้ฉันยังให้การใช้งานการทำสีกราฟใน JavaScript https://github.com/dexcodeinc/graph_algorithm.js/blob/master/graph_algorithm.js
หากคุณไม่สามารถเพิ่มคุณสมบัติ "เยี่ยมชม" ให้กับโหนดให้ใช้ชุด (หรือแผนที่) และเพียงเพิ่มโหนดที่เยี่ยมชมทั้งหมดไปยังชุดยกเว้นว่ามันอยู่ในชุดแล้ว ใช้คีย์ที่ไม่ซ้ำกันหรือที่อยู่ของวัตถุเป็น "กุญแจ"
นอกจากนี้ยังให้ข้อมูลเกี่ยวกับโหนด "รูท" ของการพึ่งพาแบบวนรอบซึ่งจะมีประโยชน์เมื่อผู้ใช้ต้องแก้ไขปัญหา
วิธีแก้ไขปัญหาอื่นคือลองค้นหาการพึ่งพาถัดไปเพื่อเรียกใช้งาน สำหรับสิ่งนี้คุณต้องมีสแต็คบางส่วนที่คุณสามารถจำได้ว่าคุณอยู่ที่ไหนตอนนี้และสิ่งที่คุณต้องทำต่อไป ตรวจสอบว่าการอ้างอิงมีอยู่ในสแต็กนี้ก่อนที่คุณจะดำเนินการ ถ้าเป็นเช่นนั้นคุณจะพบวงจร
ในขณะนี้อาจดูเหมือนว่ามีความซับซ้อนของ O (N * M) คุณต้องจำไว้ว่าสแต็คมีความลึกที่ จำกัด มาก (ดังนั้น N จึงมีขนาดเล็ก) และ M นั้นเล็กลงด้วยการพึ่งพาแต่ละครั้งที่คุณสามารถทำเครื่องหมายเป็น "ดำเนินการ" บวก คุณสามารถหยุดการค้นหาเมื่อคุณพบใบไม้ (ดังนั้นคุณไม่จำเป็นต้องตรวจสอบทุกโหนด -> M จะเล็กเกินไป)
ใน MetaMake ฉันสร้างกราฟเป็นรายการและลบทุกโหนดเมื่อฉันประมวลผลซึ่งลดปริมาณการค้นหาโดยธรรมชาติ ฉันไม่เคยต้องเรียกใช้การตรวจสอบที่เป็นอิสระทุกอย่างเกิดขึ้นโดยอัตโนมัติในระหว่างการดำเนินการตามปกติ
หากคุณต้องการโหมด "ทดสอบเท่านั้น" เพียงเพิ่มแฟล็ก "dry-run" ซึ่งปิดใช้งานการเรียกใช้งานจริง
ไม่มีอัลกอริทึมที่สามารถค้นหารอบทั้งหมดในกราฟกำกับในเวลาพหุนาม สมมติว่ากราฟกำกับมีโหนดและโหนดทุกคู่มีการเชื่อมต่อซึ่งกันและกันซึ่งหมายความว่าคุณมีกราฟที่สมบูรณ์ ดังนั้นเซตย่อยที่ไม่ว่างเปล่าของโหนดเหล่านี้บ่งบอกถึงวัฏจักรและมีจำนวน 2 ^ n-1 ของเซตย่อยดังกล่าว ดังนั้นจึงไม่มีอัลกอริธึมเวลาพหุนาม ดังนั้นสมมติว่าคุณมีอัลกอริทึมที่มีประสิทธิภาพ (ไม่โง่) ซึ่งสามารถบอกจำนวนรอบการชี้นำในกราฟได้คุณสามารถค้นหาส่วนประกอบที่เชื่อมต่อที่แข็งแกร่งก่อนจากนั้นใช้อัลกอริทึมของคุณกับส่วนประกอบที่เชื่อมต่อเหล่านี้ เนื่องจากวงจรมีอยู่เฉพาะภายในส่วนประกอบเท่านั้น
ฉันใช้ปัญหานี้ใน sml (การเขียนโปรแกรมที่จำเป็น) นี่คือโครงร่าง ค้นหาโหนดทั้งหมดที่มี indegree หรือ outdegree เป็น 0 โหนดดังกล่าวไม่สามารถเป็นส่วนหนึ่งของวงจรได้ (โปรดลบออก) จากนั้นลบขอบขาเข้าหรือขาออกทั้งหมดออกจากโหนดดังกล่าว ใช้กระบวนการนี้ซ้ำ ๆ กับกราฟผลลัพธ์ หากในตอนท้ายคุณไม่ได้อยู่กับโหนดหรือขอบกราฟจะไม่มีวัฏจักรใด ๆ
วิธีที่ฉันทำคือจัดเรียงโทโพโลยีนับจำนวนจุดยอดเยี่ยม หากตัวเลขนั้นน้อยกว่าจำนวนยอดรวมทั้งหมดใน DAG คุณมีรอบ
/mathpro/16393/finding-a-cycle-of-fixed-lengthฉันชอบโซลูชันนี้ดีที่สุดโดยเฉพาะสำหรับความยาว 4 :)
ตัวช่วยสร้าง phys ยังบอกด้วยว่าคุณต้องทำ O (V ^ 2) ฉันเชื่อว่าเราต้องการเพียง O (V) / O (V + E) หากกราฟเชื่อมต่อแล้ว DFS จะไปที่โหนดทั้งหมด หากกราฟมีการเชื่อมต่อกราฟย่อยแล้วแต่ละครั้งที่เราเรียกใช้ DFS บนจุดยอดของกราฟย่อยนี้เราจะพบจุดยอดที่เชื่อมต่อและไม่ต้องพิจารณาสิ่งเหล่านี้สำหรับการเรียกใช้ DFS ครั้งถัดไป ดังนั้นความเป็นไปได้ในการทำงานสำหรับแต่ละจุดสุดยอดจึงไม่ถูกต้อง
หาก DFS พบขอบที่ชี้ไปยังจุดสุดยอดที่เยี่ยมชมแล้วคุณจะมีวัฏจักรอยู่ที่นั่น
ดังที่คุณกล่าวว่าคุณมีงานที่ต้องดำเนินการในลำดับที่แน่นอน Topological sort
ให้คุณต้องสั่งซื้อสำหรับการจัดตารางเวลาของงาน (หรือสำหรับปัญหาการพึ่งพาถ้ามันเป็นdirect acyclic graph
) รันdfs
และดูแลรักษารายการและเริ่มเพิ่มโหนดในตอนต้นของรายการและหากคุณพบโหนดที่มีการเยี่ยมชมแล้ว จากนั้นคุณจะพบวงจรในกราฟที่กำหนด
ถ้ากราฟตรงตามคุณสมบัตินี้
|e| > |v| - 1
ดังนั้นกราฟจะมีรอบอย่างน้อยที่สุด