ฉันจะค้นหา (วนซ้ำ) รอบทั้งหมดในกราฟที่มีทิศทางจาก / ไปยังโหนดที่ระบุได้อย่างไร
ตัวอย่างเช่นฉันต้องการสิ่งนี้:
A->B->A
A->B->C->A
แต่ไม่ใช่: B-> C-> B
ฉันจะค้นหา (วนซ้ำ) รอบทั้งหมดในกราฟที่มีทิศทางจาก / ไปยังโหนดที่ระบุได้อย่างไร
ตัวอย่างเช่นฉันต้องการสิ่งนี้:
A->B->A
A->B->C->A
แต่ไม่ใช่: B-> C-> B
คำตอบ:
ฉันพบหน้านี้ในการค้นหาของฉันและเนื่องจากรอบไม่เหมือนกับส่วนประกอบที่เชื่อมต่ออย่างมากฉันจึงทำการค้นหาและในที่สุดฉันก็พบว่าอัลกอริทึมที่มีประสิทธิภาพซึ่งแสดงรายการทั้งหมด (ระดับประถมศึกษา) ของกราฟกำกับ มันมาจาก Donald B. Johnson และกระดาษสามารถพบได้ในลิงค์ต่อไปนี้:
http://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF
การใช้งานจาวาสามารถพบได้ใน:
http://normalisiert.de/code/java/elementaryCycles.zip
การสาธิตทางคณิตศาสตร์ของอัลกอริทึมของจอห์นสันสามารถพบได้ที่นี่สามารถดาวน์โหลดการใช้งานได้จากด้านขวา ( "ดาวน์โหลดรหัสผู้แต่ง" )
หมายเหตุ: จริงๆแล้วมีอัลกอริทึมมากมายสำหรับปัญหานี้ บางส่วนของพวกเขาอยู่ในบทความนี้:
http://dx.doi.org/10.1137/0205007
อ้างอิงจากบทความอัลกอรึทึมของจอห์นสันนั้นเร็วที่สุด
A->B->C->A
ประถมไม่ใช่หรือ?
simple_cycle
ใน networkx
การค้นหาความลึกครั้งแรกด้วยการย้อนรอยควรทำงานที่นี่ เก็บอาร์เรย์ของค่าบูลีนเพื่อติดตามว่าคุณเคยเข้าชมโหนดมาก่อนหรือไม่ หากคุณไม่มีโหนดใหม่ให้ไปที่ (โดยไม่ต้องกดปุ่มที่คุณเคยไปแล้ว) ให้ลองย้อนกลับและลองสาขาอื่น
DFS นั้นง่ายต่อการนำไปใช้หากคุณมีรายการ adjacency เพื่อแสดงกราฟ ตัวอย่างเช่น adj [A] = {B, C} ระบุว่า B และ C เป็นลูกของ A
ตัวอย่างเช่นหลอกรหัสด้านล่าง "start" เป็นโหนดที่คุณเริ่มต้น
dfs(adj,node,visited):
if (visited[node]):
if (node == start):
"found a path"
return;
visited[node]=YES;
for child in adj[node]:
dfs(adj,child,visited)
visited[node]=NO;
เรียกใช้ฟังก์ชันข้างต้นด้วยโหนดเริ่มต้น:
visited = {}
dfs(adj,start,visited)
if (node == start):
- สิ่งที่อยู่node and start
ในสายแรก
start
) มันเริ่มต้นที่จุดสุดยอดนั้นและทำ DFS จนกว่ามันจะกลับไปที่จุดยอดนั้นอีกครั้งจากนั้นก็รู้ว่ามันพบวัฏจักร แต่มันไม่ได้ออกรอบจริง ๆ เพียงแค่นับพวกมัน (แต่การดัดแปลงเพื่อทำอย่างนั้นแทนที่จะไม่ยากเกินไป)
start
ใช่มันจะตรวจสอบรอบเฉพาะจาก visited[node]=NO;
คุณไม่จำเป็นจริงๆที่จะล้างธงเยือนเป็นแต่ละธงเยี่ยมชมจะถูกลบเนื่องจาก แต่โปรดจำไว้ว่าหากคุณมีวัฏจักรA->B->C->A
คุณจะตรวจจับได้ 3 ครั้งซึ่งstart
สามารถเป็น 3 อย่างใดอย่างหนึ่ง แนวคิดหนึ่งที่จะป้องกันไม่ให้เกิดขึ้นคือมีอาร์เรย์ที่เข้าเยี่ยมชมอีกจุดหนึ่งซึ่งทุกโหนดที่เคยเป็นstart
โหนดในบางจุดถูกตั้งค่าไว้
ก่อนอื่น - คุณไม่ต้องการลองหาวัฏจักรจริงอย่างแท้จริงเพราะถ้ามี 1 ก็จะมีจำนวนอนันต์ ตัวอย่างเช่น ABA, ABABA เป็นต้นหรืออาจเป็นไปได้ที่จะรวมกัน 2 รอบเป็น 8 รอบเหมือน ฯลฯ ฯลฯ ... แนวทางที่มีความหมายคือการมองหาวัฏจักรธรรมดา ๆ ที่เรียกว่าวัฏจักรธรรมดา - สิ่งที่ไม่ข้ามตัวเองยกเว้น ในจุดเริ่มต้น / สิ้นสุด จากนั้นถ้าคุณต้องการคุณสามารถสร้างชุดของรอบง่าย
หนึ่งในอัลกอริธึมพื้นฐานสำหรับการค้นหาวัฏจักรง่าย ๆ ทั้งหมดในกราฟกำกับคือ: ทำการสำรวจเส้นทางแรกที่มีความลึกของเส้นทางที่เรียบง่ายทั้งหมด (ที่ไม่ข้ามตัวเอง) ในกราฟ ทุกครั้งที่โหนดปัจจุบันมีตัวตายตัวแทนบนสแต็กจะพบวัฏจักรอย่างง่าย ประกอบด้วยองค์ประกอบบนสแต็กเริ่มต้นด้วยตัวตายตัวแทนที่ระบุและลงท้ายด้วยส่วนบนสุดของสแต็ก การสำรวจเส้นทางแรกที่มีความลึกของเส้นทางแบบง่าย ๆ ทั้งหมดจะคล้ายกับการค้นหาความลึกครั้งแรก แต่คุณไม่ได้ทำเครื่องหมาย / บันทึกการเยี่ยมชมโหนดอื่น ๆ นอกเหนือจากที่อยู่ในสแต็กเป็นจุดหยุด
อัลกอริทึมแรงเดรัจฉานด้านบนไม่มีประสิทธิภาพอย่างมากและนอกจากนั้นยังสร้างสำเนาหลายรอบ อย่างไรก็ตามมันเป็นจุดเริ่มต้นของอัลกอริธึมเชิงปฏิบัติหลายอย่างที่ใช้การปรับปรุงต่าง ๆ เพื่อปรับปรุงประสิทธิภาพและหลีกเลี่ยงการทำซ้ำวงจร ฉันรู้สึกประหลาดใจที่รู้เมื่อไม่นานมานี้ว่าอัลกอริธึมเหล่านี้ไม่พร้อมใช้งานในตำราและบนเว็บ ดังนั้นผมจึงได้บางวิจัยและดำเนินการ 4 ขั้นตอนวิธีการดังกล่าวและ 1 อัลกอริทึมสำหรับรอบในกราฟไม่มีทิศทางในโอเพนซอร์ส Java ห้องสมุดที่นี่: http://code.google.com/p/niographs/
BTW เนื่องจากฉันพูดถึงกราฟที่ไม่ได้บอกทิศทาง: อัลกอริทึมสำหรับสิ่งนั้นแตกต่างกัน สร้างต้นไม้ที่ทอดแล้วขอบที่ไม่ได้เป็นส่วนหนึ่งของต้นไม้เป็นวัฏจักรที่เรียบง่ายพร้อมกับขอบบางส่วนในต้นไม้ รอบพบวิธีนี้ในรูปแบบฐานที่เรียกว่า สามารถพบวัฏจักรธรรมดาทั้งหมดได้โดยรวมวงจรฐานตั้งแต่ 2 รอบขึ้นไป ดูรายละเอียดเพิ่มเติมเช่นนี้: http://dspace.mit.edu/bitstream/handle/1721.1/68106/FTL_R_1982_07.pdf
jgrapht
ที่ใช้ในhttp://code.google.com/p/niographs/
คุณสามารถรับตัวอย่างจากgithub.com/jgrapht/jgrapht/wiki/DirectedGraphDemo
ทางเลือกที่ง่ายที่สุดผมพบว่าการแก้ปัญหานี้ได้รับการใช้ lib networkx
หลามที่เรียกว่า
มันใช้อัลกอริทึมของจอห์นสันที่กล่าวถึงในคำตอบที่ดีที่สุดของคำถามนี้ แต่มันค่อนข้างง่ายที่จะดำเนินการ
ในระยะสั้นคุณต้องต่อไปนี้:
import networkx as nx
import matplotlib.pyplot as plt
# Create Directed Graph
G=nx.DiGraph()
# Add a list of nodes:
G.add_nodes_from(["a","b","c","d","e"])
# Add a list of edges:
G.add_edges_from([("a","b"),("b","c"), ("c","a"), ("b","d"), ("d","e"), ("e","a")])
#Return a list of cycles described as a list o nodes
list(nx.simple_cycles(G))
คำตอบ: [['a', 'b', 'd', 'e'], ['a', 'b', 'c']]
nx.DiGraph({'a': ['b'], 'b': ['c','d'], 'c': ['a'], 'd': ['e'], 'e':['a']})
เพื่อชี้แจง:
คอมโพเนนต์ที่เชื่อมต่ออย่างแน่นหนาจะค้นหากราฟย่อยทั้งหมดที่มีรอบอย่างน้อยหนึ่งรอบไม่ใช่รอบที่เป็นไปได้ทั้งหมดในกราฟ เช่นถ้าคุณใช้ส่วนประกอบที่เชื่อมต่ออย่างยิ่งทั้งหมดและยุบ / กลุ่ม / รวมแต่ละองค์ประกอบเข้าด้วยกันเป็นหนึ่งโหนด (เช่นโหนดต่อองค์ประกอบ) คุณจะได้รับต้นไม้ที่ไม่มีวงจร (DAG จริง) แต่ละองค์ประกอบ (ซึ่งโดยทั่วไปจะมีกราฟย่อยที่มีอย่างน้อยหนึ่งรอบในนั้น) สามารถมีรอบที่เป็นไปได้มากมายภายในดังนั้น SCC จะไม่พบรอบที่เป็นไปได้ทั้งหมดมันจะค้นหากลุ่มที่เป็นไปได้ทั้งหมดที่มีรอบอย่างน้อยหนึ่งรอบ พวกเขาแล้วกราฟจะไม่มีรอบ
เพื่อหาวัฏจักรอย่างง่ายทั้งหมดในกราฟดังที่คนอื่น ๆ กล่าวถึงอัลกอริทึมของจอห์นสันเป็นตัวเลือก
ฉันได้รับนี่เป็นคำถามสัมภาษณ์หนึ่งครั้งฉันสงสัยว่าสิ่งนี้ได้เกิดขึ้นกับคุณและคุณมาที่นี่เพื่อขอความช่วยเหลือ แบ่งปัญหาออกเป็นสามคำถามและง่ายขึ้น
ปัญหา 1) ใช้รูปแบบตัววนซ้ำเพื่อกำหนดวิธีการวนซ้ำผลลัพธ์เส้นทาง สถานที่ที่ดีในการวางตรรกะเพื่อให้ได้เส้นทางถัดไปน่าจะเป็น "moveNext" ของตัววนซ้ำของคุณ เพื่อหาเส้นทางที่ถูกต้องมันขึ้นอยู่กับโครงสร้างข้อมูลของคุณ สำหรับฉันมันเป็นตาราง sql ที่เต็มไปด้วยความเป็นไปได้ของเส้นทางที่ถูกต้องดังนั้นฉันจึงต้องสร้างแบบสอบถามเพื่อให้ได้ปลายทางที่ถูกต้องตามแหล่งที่มา
ปัญหาที่ 2) ผลักดันแต่ละโหนดเมื่อคุณพบพวกมันลงในคอลเลกชั่นที่คุณได้รับซึ่งหมายความว่าคุณสามารถดูว่าคุณ "ย้อนกลับ" สองจุดผ่านจุดที่ง่ายมากโดยการซักถามคอลเลกชันที่คุณกำลังสร้าง
ปัญหา 3) หาก ณ จุดใดที่คุณเห็นว่าคุณกำลังเพิ่มกลับเป็นสองเท่าคุณสามารถเปิดสิ่งต่าง ๆ ออกจากคอลเลกชันและ "สำรอง" จากนั้นลองพยายาม "ก้าวไปข้างหน้า" อีกครั้ง
แฮ็ค: ถ้าคุณใช้ Sql Server 2008 มี "ลำดับชั้น" ใหม่บางอย่างที่คุณสามารถใช้เพื่อแก้ไขได้อย่างรวดเร็วหากคุณจัดโครงสร้างข้อมูลของคุณในทรี
ตัวแปรที่ใช้ DFS ที่มีขอบด้านหลังจะพบวัฏจักรจริง ๆ แต่ในหลาย ๆ กรณีมันจะไม่เป็นวัฏจักรขั้นต่ำสุด โดยทั่วไป DFS จะให้คุณตั้งค่าสถานะว่ามีรอบ แต่มันไม่ดีพอที่จะหารอบจริง ตัวอย่างเช่นลองนึกภาพ 5 รอบที่ต่างกันที่แชร์สองขอบ ไม่มีวิธีง่ายๆในการระบุรอบโดยใช้เพียง DFS (รวมถึงตัวแปรย้อนรอย)
อัลกอริธึมของ Johnson นั้นมอบวัฏจักรเรียบง่ายที่เป็นเอกลักษณ์และมีความซับซ้อนของเวลาและพื้นที่
แต่ถ้าคุณต้องการหารอบน้อยที่สุด (หมายความว่าอาจมีมากกว่าหนึ่งรอบผ่านจุดสุดยอดใด ๆ และเราสนใจที่จะหารอบน้อยที่สุด) และกราฟของคุณไม่ใหญ่มากคุณสามารถลองใช้วิธีง่าย ๆ ด้านล่าง มันง่ายมาก แต่ค่อนข้างช้าเมื่อเทียบกับของจอห์นสัน
ดังนั้นหนึ่งในอย่างวิธีที่ง่ายที่สุดที่จะหารอบน้อยที่สุดคือการใช้อัลกอริทึมของฟลอยด์เพื่อหาเส้นทางที่น้อยที่สุดระหว่างจุดทั้งหมดที่ใช้ถ้อยคำเมทริกซ์ อัลกอริธึมนี้ไม่มีที่ใดใกล้เคียงกับของ Johnson แต่มันง่ายมากและลูปด้านในของมันแน่นจนกราฟขนาดเล็ก (<= 50-100 โหนด) มันสมเหตุสมผลอย่างยิ่งที่จะใช้มัน ความซับซ้อนของเวลาคือ O (n ^ 3), ความซับซ้อนของพื้นที่ O (n ^ 2) หากคุณใช้การติดตามผู้ปกครองและ O (1) หากคุณไม่ต้องการ ก่อนอื่นเรามาหาคำตอบของคำถามว่ามีวัฏจักรอยู่หรือเปล่า อัลกอริทึมนั้นตายง่าย ด้านล่างเป็นตัวอย่างข้อมูลใน Scala
val NO_EDGE = Integer.MAX_VALUE / 2
def shortestPath(weights: Array[Array[Int]]) = {
for (k <- weights.indices;
i <- weights.indices;
j <- weights.indices) {
val throughK = weights(i)(k) + weights(k)(j)
if (throughK < weights(i)(j)) {
weights(i)(j) = throughK
}
}
}
แต่เดิมอัลกอริทึมนี้ทำงานบนกราฟถ่วงน้ำหนักเพื่อหาเส้นทางที่สั้นที่สุดระหว่างโหนดทั้งหมดคู่ เพื่อให้ทำงานได้อย่างถูกต้องคุณต้องระบุ 1 หากมีขอบกำกับระหว่างโหนดหรือ NO_EDGE เป็นอย่างอื่น หลังจากรันอัลกอริทึมแล้วคุณสามารถตรวจสอบเส้นทแยงมุมหลักได้หากมีค่าน้อยกว่า NO_EDGE กว่าโหนดนี้มีส่วนร่วมในรอบความยาวเท่ากับค่า โหนดอื่น ๆ ในวัฏจักรเดียวกันจะมีค่าเท่ากัน (บนเส้นทแยงมุมหลัก)
ในการสร้างวงจรใหม่เราจำเป็นต้องใช้อัลกอริธึมเวอร์ชันดัดแปลงเล็กน้อยกับการติดตามผู้ปกครอง
def shortestPath(weights: Array[Array[Int]], parents: Array[Array[Int]]) = {
for (k <- weights.indices;
i <- weights.indices;
j <- weights.indices) {
val throughK = weights(i)(k) + weights(k)(j)
if (throughK < weights(i)(j)) {
parents(i)(j) = k
weights(i)(j) = throughK
}
}
}
ผู้ปกครองเมทริกซ์เริ่มแรกควรมีดัชนีจุดสุดยอดแหล่งที่มาในเซลล์ขอบถ้ามีขอบระหว่างจุดยอดและ -1 อย่างอื่น หลังจากฟังก์ชันคืนค่าขอบแต่ละอันคุณจะมีการอ้างอิงไปยังโหนดพาเรนต์ในแผนผังพา ธ ที่สั้นที่สุด แล้วมันก็กู้คืนรอบจริงได้ง่าย
สรุปเรามีโปรแกรมดังต่อไปนี้เพื่อค้นหารอบน้อยที่สุด
val NO_EDGE = Integer.MAX_VALUE / 2;
def shortestPathWithParentTracking(
weights: Array[Array[Int]],
parents: Array[Array[Int]]) = {
for (k <- weights.indices;
i <- weights.indices;
j <- weights.indices) {
val throughK = weights(i)(k) + weights(k)(j)
if (throughK < weights(i)(j)) {
parents(i)(j) = parents(i)(k)
weights(i)(j) = throughK
}
}
}
def recoverCycles(
cycleNodes: Seq[Int],
parents: Array[Array[Int]]): Set[Seq[Int]] = {
val res = new mutable.HashSet[Seq[Int]]()
for (node <- cycleNodes) {
var cycle = new mutable.ArrayBuffer[Int]()
cycle += node
var other = parents(node)(node)
do {
cycle += other
other = parents(other)(node)
} while(other != node)
res += cycle.sorted
}
res.toSet
}
และวิธีการหลักขนาดเล็กเพียงเพื่อทดสอบผลลัพธ์
def main(args: Array[String]): Unit = {
val n = 3
val weights = Array(Array(NO_EDGE, 1, NO_EDGE), Array(NO_EDGE, NO_EDGE, 1), Array(1, NO_EDGE, NO_EDGE))
val parents = Array(Array(-1, 1, -1), Array(-1, -1, 2), Array(0, -1, -1))
shortestPathWithParentTracking(weights, parents)
val cycleNodes = parents.indices.filter(i => parents(i)(i) < NO_EDGE)
val cycles: Set[Seq[Int]] = recoverCycles(cycleNodes, parents)
println("The following minimal cycle found:")
cycles.foreach(c => println(c.mkString))
println(s"Total: ${cycles.size} cycle found")
}
และผลลัพธ์คือ
The following minimal cycle found:
012
Total: 1 cycle found
ในกรณีของกราฟที่ไม่ได้บอกทิศทางกระดาษที่ตีพิมพ์เมื่อเร็ว ๆ นี้ ( รายการที่เหมาะสมของวัฏจักรและ st-path ในกราฟที่ไม่ได้บอกทิศทาง ) เสนอวิธีแก้ปัญหาที่เหมาะสมที่สุดแบบ asymptotically คุณสามารถอ่านได้ที่นี่http://arxiv.org/abs/1205.2766หรือที่นี่http://dl.acm.org/citation.cfm?id=2627951 ฉันรู้ว่ามันไม่ได้ตอบคำถามของคุณ แต่เนื่องจากชื่อของ คำถามของคุณไม่ได้กล่าวถึงทิศทาง แต่อาจเป็นประโยชน์สำหรับการค้นหาโดย Google
เริ่มต้นที่โหนด X และตรวจสอบโหนดชายด์ทั้งหมด (พาเรนต์และโหนดชายน์เทียบเท่ากันหากไม่ถูกกำหนดทิศทาง) ทำเครื่องหมายโหนดลูกเหล่านั้นว่าเป็นลูกของ X จากโหนดลูกใด ๆ เช่นนั้นทำเครื่องหมายว่าเป็นลูกของการเป็นลูกของ A, X 'โดยที่ X' ถูกทำเครื่องหมายว่าอยู่ห่างออกไป 2 ก้าว) หากคุณกด X ในภายหลังและทำเครื่องหมายว่าเป็นลูกของ X '' นั่นหมายความว่า X อยู่ในรอบ 3 โหนด การย้อนรอยไปสู่ผู้ปกครองนั้นเป็นเรื่องง่าย (ตามที่เป็นอยู่อัลกอริทึมไม่สนับสนุนสิ่งนี้ดังนั้นคุณจะพบว่าผู้ปกครองคนใดที่มี X ')
หมายเหตุ: หากกราฟไม่ได้ถูกบอกทิศทางหรือมีขอบสองทิศทางอัลกอริทึมนี้จะซับซ้อนมากขึ้นโดยสมมติว่าคุณไม่ต้องการข้ามขอบเดียวกันสองรอบสำหรับรอบ
หากสิ่งที่คุณต้องการคือการหาวงจรระดับประถมศึกษาทั้งหมดในกราฟคุณสามารถใช้อัลกอริทึม EC โดย JAMES C. TIERNAN ที่พบบนกระดาษตั้งแต่ปี 1970
เดิมมากขั้นตอนวิธี EC เป็นฉันจัดการเพื่อใช้มันใน PHP (ความหวังมีความผิดพลาดไม่ได้แสดงให้เห็นด้านล่าง) มันสามารถหาลูปได้เช่นกันหากมี วงจรในการนำไปใช้นี้ (ที่พยายามโคลนต้นฉบับ) เป็นองค์ประกอบที่ไม่ใช่ศูนย์ ศูนย์ที่นี่หมายถึงการไม่มีตัวตน (เป็นโมฆะอย่างที่เรารู้)
นอกเหนือจากด้านล่างแล้วยังมีการใช้งานอื่น ๆ ที่ให้อัลกอริธึมอิสระมากขึ้นซึ่งหมายความว่าโหนดสามารถเริ่มจากที่ใดก็ได้แม้จากจำนวนลบเช่น -4, -3, -2, .. เป็นต้น
ในทั้งสองกรณีจำเป็นต้องใช้โหนดนั้นเรียงลำดับ
คุณอาจต้องศึกษาบทความต้นฉบับอัลกอริทึมวงจรเบื้องต้นของเจมส์ซีเทียร์นัน
<?php
echo "<pre><br><br>";
$G = array(
1=>array(1,2,3),
2=>array(1,2,3),
3=>array(1,2,3)
);
define('N',key(array_slice($G, -1, 1, true)));
$P = array(1=>0,2=>0,3=>0,4=>0,5=>0);
$H = array(1=>$P, 2=>$P, 3=>$P, 4=>$P, 5=>$P );
$k = 1;
$P[$k] = key($G);
$Circ = array();
#[Path Extension]
EC2_Path_Extension:
foreach($G[$P[$k]] as $j => $child ){
if( $child>$P[1] and in_array($child, $P)===false and in_array($child, $H[$P[$k]])===false ){
$k++;
$P[$k] = $child;
goto EC2_Path_Extension;
} }
#[EC3 Circuit Confirmation]
if( in_array($P[1], $G[$P[$k]])===true ){//if PATH[1] is not child of PATH[current] then don't have a cycle
$Circ[] = $P;
}
#[EC4 Vertex Closure]
if($k===1){
goto EC5_Advance_Initial_Vertex;
}
//afou den ksana theoreitai einai asfales na svisoume
for( $m=1; $m<=N; $m++){//H[P[k], m] <- O, m = 1, 2, . . . , N
if( $H[$P[$k-1]][$m]===0 ){
$H[$P[$k-1]][$m]=$P[$k];
break(1);
}
}
for( $m=1; $m<=N; $m++ ){//H[P[k], m] <- O, m = 1, 2, . . . , N
$H[$P[$k]][$m]=0;
}
$P[$k]=0;
$k--;
goto EC2_Path_Extension;
#[EC5 Advance Initial Vertex]
EC5_Advance_Initial_Vertex:
if($P[1] === N){
goto EC6_Terminate;
}
$P[1]++;
$k=1;
$H=array(
1=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
2=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
3=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
4=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
5=>array(1=>0,2=>0,3=>0,4=>0,5=>0)
);
goto EC2_Path_Extension;
#[EC5 Advance Initial Vertex]
EC6_Terminate:
print_r($Circ);
?>
แล้วนี่คือการใช้งานอื่น ๆ ที่เป็นอิสระมากขึ้นของกราฟโดยไม่ต้อง goto และไม่มีค่าอาร์เรย์ แต่จะใช้คีย์อาร์เรย์, เส้นทาง, กราฟและวงจรจะถูกเก็บไว้เป็นคีย์อาร์เรย์ (ใช้ค่าอาร์เรย์หากคุณต้องการเพียงแค่เปลี่ยนค่าที่ต้องการ บรรทัด) กราฟตัวอย่างเริ่มต้นจาก -4 เพื่อแสดงความเป็นอิสระ
<?php
$G = array(
-4=>array(-4=>true,-3=>true,-2=>true),
-3=>array(-4=>true,-3=>true,-2=>true),
-2=>array(-4=>true,-3=>true,-2=>true)
);
$C = array();
EC($G,$C);
echo "<pre>";
print_r($C);
function EC($G, &$C){
$CNST_not_closed = false; // this flag indicates no closure
$CNST_closed = true; // this flag indicates closure
// define the state where there is no closures for some node
$tmp_first_node = key($G); // first node = first key
$tmp_last_node = $tmp_first_node-1+count($G); // last node = last key
$CNST_closure_reset = array();
for($k=$tmp_first_node; $k<=$tmp_last_node; $k++){
$CNST_closure_reset[$k] = $CNST_not_closed;
}
// define the state where there is no closure for all nodes
for($k=$tmp_first_node; $k<=$tmp_last_node; $k++){
$H[$k] = $CNST_closure_reset; // Key in the closure arrays represent nodes
}
unset($tmp_first_node);
unset($tmp_last_node);
# Start algorithm
foreach($G as $init_node => $children){#[Jump to initial node set]
#[Initial Node Set]
$P = array(); // declare at starup, remove the old $init_node from path on loop
$P[$init_node]=true; // the first key in P is always the new initial node
$k=$init_node; // update the current node
// On loop H[old_init_node] is not cleared cause is never checked again
do{#Path 1,3,7,4 jump here to extend father 7
do{#Path from 1,3,8,5 became 2,4,8,5,6 jump here to extend child 6
$new_expansion = false;
foreach( $G[$k] as $child => $foo ){#Consider each child of 7 or 6
if( $child>$init_node and isset($P[$child])===false and $H[$k][$child]===$CNST_not_closed ){
$P[$child]=true; // add this child to the path
$k = $child; // update the current node
$new_expansion=true;// set the flag for expanding the child of k
break(1); // we are done, one child at a time
} } }while(($new_expansion===true));// Do while a new child has been added to the path
# If the first node is child of the last we have a circuit
if( isset($G[$k][$init_node])===true ){
$C[] = $P; // Leaving this out of closure will catch loops to
}
# Closure
if($k>$init_node){ //if k>init_node then alwaya count(P)>1, so proceed to closure
$new_expansion=true; // $new_expansion is never true, set true to expand father of k
unset($P[$k]); // remove k from path
end($P); $k_father = key($P); // get father of k
$H[$k_father][$k]=$CNST_closed; // mark k as closed
$H[$k] = $CNST_closure_reset; // reset k closure
$k = $k_father; // update k
} } while($new_expansion===true);//if we don't wnter the if block m has the old k$k_father_old = $k;
// Advance Initial Vertex Context
}//foreach initial
}//function
?>
ฉันวิเคราะห์และบันทึก EC แล้ว แต่น่าเสียดายที่เอกสารเป็นภาษากรีก
มีสองขั้นตอน (อัลกอริทึม) ที่เกี่ยวข้องในการค้นหารอบทั้งหมดใน DAG
ขั้นตอนแรกคือการใช้อัลกอริทึมของ Tarjan เพื่อค้นหาชุดของส่วนประกอบที่เชื่อมต่ออย่างยิ่ง
ขั้นตอนที่สองคือการค้นหารอบ (เส้นทาง) ภายในส่วนประกอบที่เชื่อมต่อ คำแนะนำของฉันคือใช้อัลกอริทึมของ Hierholzer รุ่นที่แก้ไข
ความคิดคือ:
นี่คือลิงค์สำหรับการนำ Java ไปใช้กับกรณีทดสอบ:
http://stones333.blogspot.com/2013/12/find-cycles-in-directed-graph-dag.html
ฉันสะดุดอัลกอริทึมต่อไปนี้ซึ่งดูเหมือนว่าจะมีประสิทธิภาพมากกว่าอัลกอริทึมของจอห์นสัน (อย่างน้อยสำหรับกราฟขนาดใหญ่) อย่างไรก็ตามฉันไม่แน่ใจเกี่ยวกับประสิทธิภาพของมันเทียบกับอัลกอริทึมของ Tarjan
นอกจากนี้ฉันตรวจสอบเพียงสามเหลี่ยม หากสนใจโปรดดูที่ "ขั้นตอนวิธีการขึ้นทะเบียนและ Subgraph" โดย Norishige Chiba และ Takao Nishizeki ( http://dx.doi.org/10.1137/0214017 )
โซลูชัน Javascript โดยใช้ disjoint ตั้งค่ารายการที่ลิงก์ สามารถอัปเกรดเป็น disjoint ตั้งค่าฟอเรสต์สำหรับเวลาที่เร็วขึ้น
var input = '5\nYYNNN\nYYYNN\nNYYNN\nNNNYN\nNNNNY'
console.log(input);
//above solution should be 3 because the components are
//{0,1,2}, because {0,1} and {1,2} therefore {0,1,2}
//{3}
//{4}
//MIT license, authored by Ling Qing Meng
//'4\nYYNN\nYYYN\nNYYN\nNNNY'
//Read Input, preformatting
var reformat = input.split(/\n/);
var N = reformat[0];
var adjMatrix = [];
for (var i = 1; i < reformat.length; i++) {
adjMatrix.push(reformat[i]);
}
//for (each person x from 1 to N) CREATE-SET(x)
var sets = [];
for (var i = 0; i < N; i++) {
var s = new LinkedList();
s.add(i);
sets.push(s);
}
//populate friend potentials using combinatorics, then filters
var people = [];
var friends = [];
for (var i = 0; i < N; i++) {
people.push(i);
}
var potentialFriends = k_combinations(people,2);
for (var i = 0; i < potentialFriends.length; i++){
if (isFriend(adjMatrix,potentialFriends[i]) === 'Y'){
friends.push(potentialFriends[i]);
}
}
//for (each pair of friends (x y) ) if (FIND-SET(x) != FIND-SET(y)) MERGE-SETS(x, y)
for (var i = 0; i < friends.length; i++) {
var x = friends[i][0];
var y = friends[i][1];
if (FindSet(x) != FindSet(y)) {
sets.push(MergeSet(x,y));
}
}
for (var i = 0; i < sets.length; i++) {
//sets[i].traverse();
}
console.log('How many distinct connected components?',sets.length);
//Linked List data structures neccesary for above to work
function Node(){
this.data = null;
this.next = null;
}
function LinkedList(){
this.head = null;
this.tail = null;
this.size = 0;
// Add node to the end
this.add = function(data){
var node = new Node();
node.data = data;
if (this.head == null){
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
this.tail = node;
}
this.size++;
};
this.contains = function(data) {
if (this.head.data === data)
return this;
var next = this.head.next;
while (next !== null) {
if (next.data === data) {
return this;
}
next = next.next;
}
return null;
};
this.traverse = function() {
var current = this.head;
var toPrint = '';
while (current !== null) {
//callback.call(this, current); put callback as an argument to top function
toPrint += current.data.toString() + ' ';
current = current.next;
}
console.log('list data: ',toPrint);
}
this.merge = function(list) {
var current = this.head;
var next = current.next;
while (next !== null) {
current = next;
next = next.next;
}
current.next = list.head;
this.size += list.size;
return this;
};
this.reverse = function() {
if (this.head == null)
return;
if (this.head.next == null)
return;
var currentNode = this.head;
var nextNode = this.head.next;
var prevNode = this.head;
this.head.next = null;
while (nextNode != null) {
currentNode = nextNode;
nextNode = currentNode.next;
currentNode.next = prevNode;
prevNode = currentNode;
}
this.head = currentNode;
return this;
}
}
/**
* GENERAL HELPER FUNCTIONS
*/
function FindSet(x) {
for (var i = 0; i < sets.length; i++){
if (sets[i].contains(x) != null) {
return sets[i].contains(x);
}
}
return null;
}
function MergeSet(x,y) {
var listA,listB;
for (var i = 0; i < sets.length; i++){
if (sets[i].contains(x) != null) {
listA = sets[i].contains(x);
sets.splice(i,1);
}
}
for (var i = 0; i < sets.length; i++) {
if (sets[i].contains(y) != null) {
listB = sets[i].contains(y);
sets.splice(i,1);
}
}
var res = MergeLists(listA,listB);
return res;
}
function MergeLists(listA, listB) {
var listC = new LinkedList();
listA.merge(listB);
listC = listA;
return listC;
}
//access matrix by i,j -> returns 'Y' or 'N'
function isFriend(matrix, pair){
return matrix[pair[0]].charAt(pair[1]);
}
function k_combinations(set, k) {
var i, j, combs, head, tailcombs;
if (k > set.length || k <= 0) {
return [];
}
if (k == set.length) {
return [set];
}
if (k == 1) {
combs = [];
for (i = 0; i < set.length; i++) {
combs.push([set[i]]);
}
return combs;
}
// Assert {1 < k < set.length}
combs = [];
for (i = 0; i < set.length - k + 1; i++) {
head = set.slice(i, i+1);
tailcombs = k_combinations(set.slice(i + 1), k - 1);
for (j = 0; j < tailcombs.length; j++) {
combs.push(head.concat(tailcombs[j]));
}
}
return combs;
}
DFS จากโหนดเริ่มต้นติดตามเส้นทาง DFS ระหว่างการสำรวจเส้นทางและบันทึกเส้นทางหากคุณพบขอบจากโหนด v ในเส้นทางไปยัง s (v, s) เป็นแบ็คเอนด์ในทรี DFS และดังนั้นจึงแสดงถึงวงจรที่มี s
เกี่ยวกับคำถามของคุณเกี่ยวกับวงจรการเปลี่ยนแปลงอ่านเพิ่มเติมได้ที่นี่: https://www.codechef.com/problems/PCYCLE
คุณสามารถลองใช้รหัสนี้ (ป้อนขนาดและหมายเลขหลัก):
# include<cstdio>
using namespace std;
int main()
{
int n;
scanf("%d",&n);
int num[1000];
int visited[1000]={0};
int vindex[2000];
for(int i=1;i<=n;i++)
scanf("%d",&num[i]);
int t_visited=0;
int cycles=0;
int start=0, index;
while(t_visited < n)
{
for(int i=1;i<=n;i++)
{
if(visited[i]==0)
{
vindex[start]=i;
visited[i]=1;
t_visited++;
index=start;
break;
}
}
while(true)
{
index++;
vindex[index]=num[vindex[index-1]];
if(vindex[index]==vindex[start])
break;
visited[vindex[index]]=1;
t_visited++;
}
vindex[++index]=0;
start=index+1;
cycles++;
}
printf("%d\n",cycles,vindex[0]);
for(int i=0;i<(n+2*cycles);i++)
{
if(vindex[i]==0)
printf("\n");
else
printf("%d ",vindex[i]);
}
}
รุ่น DFS c ++ สำหรับโค้ดหลอกในคำตอบของชั้นสอง:
void findCircleUnit(int start, int v, bool* visited, vector<int>& path) {
if(visited[v]) {
if(v == start) {
for(auto c : path)
cout << c << " ";
cout << endl;
return;
}
else
return;
}
visited[v] = true;
path.push_back(v);
for(auto i : G[v])
findCircleUnit(start, i, visited, path);
visited[v] = false;
path.pop_back();
}