อัลกอริทึมกราฟเพื่อค้นหาการเชื่อมต่อทั้งหมดระหว่างจุดยอดสองจุดตามอำเภอใจ


117

ฉันกำลังพยายามกำหนดอัลกอริทึมด้านเวลาที่มีประสิทธิภาพสูงสุดเพื่อทำงานที่อธิบายไว้ด้านล่างนี้

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

ระเบียนทั้งหมดในชุดมีข้อมูลการเชื่อมต่อ (เช่นไม่มีระเบียนเด็กกำพร้าอยู่แต่ละระเบียนในชุดจะเชื่อมต่อกับระเบียนอื่น ๆ ในชุด)

ฉันต้องการเลือกสองระเบียนจากชุดและสามารถแสดงเส้นทางแบบง่ายทั้งหมดระหว่างระเบียนที่เลือกได้ โดย "เส้นทางธรรมดา" ฉันหมายถึงเส้นทางที่ไม่มีการบันทึกซ้ำในเส้นทาง (เช่นเส้นทาง จำกัด เท่านั้น)

หมายเหตุ: ทั้งสองระเบียนที่เลือกจะแตกต่างกันเสมอ (เช่นจุดเริ่มต้นและจุดสิ้นสุดจะไม่เหมือนกันไม่มีรอบ)

ตัวอย่างเช่น:

    หากฉันมีบันทึกต่อไปนี้:
        A, B, C, D, E.

    และสิ่งต่อไปนี้แสดงถึงการเชื่อมต่อ: 
        (A, B), (A, C), (B, A) (B, D), (B, E), (B, F) (C, A), (C, E)
        (C, F) (D, B), (E, C), (E, F), (F, B), (F, C), (F, E)

        [โดยที่ (A, B) หมายถึงบันทึก A เชื่อมต่อกับบันทึก B]

ถ้าฉันเลือก B เป็นเร็กคอร์ดเริ่มต้นและ E เป็นเร็กคอร์ดสิ้นสุดของฉันฉันต้องการค้นหาพา ธ ง่ายๆทั้งหมดผ่านการเชื่อมต่อเร็กคอร์ดที่เชื่อมต่อเร็กคอร์ด B เพื่อบันทึก E

   เส้นทางทั้งหมดที่เชื่อมต่อ B ถึง E:
      B-> E
      B-> F-> E
      B-> F-> C-> E
      B-> A-> C-> E
      B-> A-> C-> F-> E

นี่คือตัวอย่างในทางปฏิบัติฉันอาจมีชุดที่มีบันทึกหลายแสนรายการ


การเชื่อมต่อเรียกว่าวัฏจักรและคำตอบนี้มีข้อมูลมากมายสำหรับคุณ
elhoim

3
โปรดบอกว่าคุณต้องการรายชื่อการเชื่อมต่อแบบไม่มีลูปที่ จำกัด หรือสตรีมการเชื่อมต่อที่ไม่มีที่สิ้นสุดพร้อมลูปที่เป็นไปได้ทั้งหมด cf เลย คำตอบของ Blorgbeard
Charles Stewart

มีใครช่วยได้บ้าง ??? stackoverflow.com/questions/32516706/…
tejas3006

คำตอบ:


116

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

ฉันสังเกตว่ากราฟที่คุณระบุไว้ด้านบนมีขอบเพียงด้านเดียวที่เป็นทิศทาง (B, E) นี่เป็นการพิมพ์ผิดหรือเป็นกราฟที่กำหนดทิศทางจริงๆ วิธีนี้ใช้ได้ผลโดยไม่คำนึงถึง ขออภัยฉันไม่สามารถทำใน C ได้ฉันอ่อนแอในด้านนั้นเล็กน้อย ฉันคาดหวังว่าคุณจะสามารถแปลโค้ด Java นี้ได้โดยไม่มีปัญหามากเกินไป

Graph.java:

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

public class Graph {
    private Map<String, LinkedHashSet<String>> map = new HashMap();

    public void addEdge(String node1, String node2) {
        LinkedHashSet<String> adjacent = map.get(node1);
        if(adjacent==null) {
            adjacent = new LinkedHashSet();
            map.put(node1, adjacent);
        }
        adjacent.add(node2);
    }

    public void addTwoWayVertex(String node1, String node2) {
        addEdge(node1, node2);
        addEdge(node2, node1);
    }

    public boolean isConnected(String node1, String node2) {
        Set adjacent = map.get(node1);
        if(adjacent==null) {
            return false;
        }
        return adjacent.contains(node2);
    }

    public LinkedList<String> adjacentNodes(String last) {
        LinkedHashSet<String> adjacent = map.get(last);
        if(adjacent==null) {
            return new LinkedList();
        }
        return new LinkedList<String>(adjacent);
    }
}

Search.java:

import java.util.LinkedList;

public class Search {

    private static final String START = "B";
    private static final String END = "E";

    public static void main(String[] args) {
        // this graph is directional
        Graph graph = new Graph();
        graph.addEdge("A", "B");
        graph.addEdge("A", "C");
        graph.addEdge("B", "A");
        graph.addEdge("B", "D");
        graph.addEdge("B", "E"); // this is the only one-way connection
        graph.addEdge("B", "F");
        graph.addEdge("C", "A");
        graph.addEdge("C", "E");
        graph.addEdge("C", "F");
        graph.addEdge("D", "B");
        graph.addEdge("E", "C");
        graph.addEdge("E", "F");
        graph.addEdge("F", "B");
        graph.addEdge("F", "C");
        graph.addEdge("F", "E");
        LinkedList<String> visited = new LinkedList();
        visited.add(START);
        new Search().depthFirst(graph, visited);
    }

    private void depthFirst(Graph graph, LinkedList<String> visited) {
        LinkedList<String> nodes = graph.adjacentNodes(visited.getLast());
        // examine adjacent nodes
        for (String node : nodes) {
            if (visited.contains(node)) {
                continue;
            }
            if (node.equals(END)) {
                visited.add(node);
                printPath(visited);
                visited.removeLast();
                break;
            }
        }
        for (String node : nodes) {
            if (visited.contains(node) || node.equals(END)) {
                continue;
            }
            visited.addLast(node);
            depthFirst(graph, visited);
            visited.removeLast();
        }
    }

    private void printPath(LinkedList<String> visited) {
        for (String node : visited) {
            System.out.print(node);
            System.out.print(" ");
        }
        System.out.println();
    }
}

ผลลัพธ์ของโปรแกรม:

B E 
B A C E 
B A C F E 
B F E 
B F C E 

5
โปรดทราบว่านี่ไม่ใช่การข้ามผ่านครั้งแรกในวงกว้าง ด้วยความกว้างก่อนอื่นให้คุณไปที่โหนดทั้งหมดที่มีระยะทาง 0 ถึงรากก่อนจากนั้นจึงมีระยะทาง 1 ตามด้วย 2 เป็นต้น
mweerden

14
ถูกต้องนี่คือ DFS BFS จะต้องใช้คิวเพื่อจัดคิวโหนดระดับ (N + 1) เพื่อประมวลผลหลังจากโหนดระดับ N ทั้งหมด อย่างไรก็ตามสำหรับวัตถุประสงค์ของ OP BFS หรือ DFS จะทำงานได้เนื่องจากไม่มีการระบุลำดับการเรียงลำดับของพา ธ ที่ต้องการ
Matt J

1
เคซี่ย์ฉันมองหาวิธีแก้ปัญหานี้มานานแล้ว ฉันเพิ่งติดตั้ง DFS นี้ใน C ++ และใช้งานได้ดี
AndyUK

6
ข้อเสียของการเรียกซ้ำคือถ้าคุณมีกราฟลึก (A-> B-> C -> ...-> N) คุณอาจมี StackOverflowError ใน java
Rrr

1
ฉันได้เพิ่มเวอร์ชันซ้ำใน C # ด้านล่างแล้ว
บั

23

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

พบเทคนิคอันชาญฉลาดโดยใช้ Petri Nets ได้ที่นี่


2
คุณช่วยฉันด้วยวิธีแก้ปัญหาที่ดีกว่านี้ได้ไหม DFS ใช้เวลาตลอดไปในการรัน: stackoverflow.com/q/8342101/632951
Pacerier

โปรดทราบว่าเป็นเรื่องง่ายที่จะสร้างกราฟซึ่ง DFS ไม่มีประสิทธิภาพมากแม้ว่าชุดของเส้นทางแบบง่ายทั้งหมดระหว่างโหนดทั้งสองจะมีขนาดเล็กและหาได้ง่าย ตัวอย่างเช่นพิจารณากราฟที่ไม่ได้กำหนดทิศทางโดยที่โหนดเริ่มต้น A มีเพื่อนบ้านสองตัว ได้แก่ โหนดเป้าหมาย B (ซึ่งไม่มีเพื่อนบ้านอื่นนอกเหนือจาก A) และโหนด C ที่เป็นส่วนหนึ่งของกลุ่มที่เชื่อมต่อทั้งหมดของโหนดn + 1 แม้ว่าจะมีเส้นทางง่ายๆเพียงเส้นทางเดียวจาก A ถึง B แต่ DFS ที่ไร้เดียงสาจะเสียเวลา O ( n !) ไปอย่างไร้ประโยชน์ในการสำรวจกลุ่ม ตัวอย่างที่คล้ายกัน (วิธีแก้ปัญหาเดียว DFS ใช้เวลาชี้แจง) สามารถพบได้ใน DAG เช่นกัน
Ilmari Karonen

NIST กล่าวว่า: "เส้นทางอาจถูกแจกแจงด้วยการค้นหาในเชิงลึกก่อน"
chomp

13

นี่คือรหัสเทียมที่ฉันคิดขึ้นมา นี่ไม่ใช่ภาษาถิ่นของรหัสเทียมใด ๆ แต่ควรง่ายพอที่จะปฏิบัติตาม

ใคร ๆ ก็อยากเลือกสิ่งนี้ออกจากกัน

  • [p] คือรายการของจุดยอดที่แสดงเส้นทางปัจจุบัน

  • [x] คือรายการของเส้นทางที่ตรงตามเกณฑ์

  • [s] คือจุดยอดต้นทาง

  • [d] คือจุดยอดปลายทาง

  • [c] คือจุดยอดปัจจุบัน (อาร์กิวเมนต์ของรูทีน PathFind)

สมมติว่ามีวิธีที่มีประสิทธิภาพในการค้นหาจุดยอดที่อยู่ติดกัน (บรรทัดที่ 6)

     1 PathList [p]
     2 ListOfPathLists [x]
     3 จุดยอด [s], [d]

     4 PathFind (จุดยอด [c])
     5 เพิ่ม [c] ที่ส่วนท้ายของรายการ [p]
     6 สำหรับแต่ละจุดยอด [v] ที่อยู่ติดกับ [c]
     7 ถ้า [v] เท่ากับ [d] แล้ว
     8 บันทึกรายการ [p] ใน [x]
     9 อื่น ๆ ถ้า [v] ไม่อยู่ในรายการ [p]
    10 PathFind ([v])
    11 ถัดไปสำหรับ
    12 เอาหางออกจาก [p]
    13 กลับ

คุณช่วยอธิบายขั้นตอนที่ 11 และขั้นตอนที่ 12 ได้
ไหม

บรรทัดที่ 11 หมายถึงบล็อกสุดท้ายที่ไปพร้อมกับ For loop ที่เริ่มต้นในบรรทัดที่ 6 บรรทัดที่ 12 หมายถึงการลบองค์ประกอบสุดท้ายของรายการพา ธ ก่อนที่จะกลับไปยังผู้เรียก
Robert Groves

การเรียก PathFind เริ่มต้นคืออะไร - คุณส่งผ่านจุดยอดต้นทาง [s] หรือไม่
ผู้ใช้ bozo

ในตัวอย่างนี้ใช่ แต่โปรดทราบว่าคุณอาจไม่ต้องการเขียนโค้ดจริงที่แมปแบบตัวต่อตัวกับรหัสเทียมนี้ มีความหมายมากกว่าที่จะแสดงให้เห็นถึงกระบวนการคิดมากกว่าโค้ดที่ออกแบบมาอย่างดี
Robert Groves

8

เนื่องจากการใช้งาน DFS แบบไม่เรียกซ้ำที่มีอยู่ในคำตอบนี้ดูเหมือนจะใช้งานไม่ได้ฉันขอให้ฉันระบุสิ่งที่ใช้งานได้จริง

ฉันเขียนสิ่งนี้ใน Python เพราะฉันพบว่ารายละเอียดการใช้งานค่อนข้างอ่านได้และไม่กระจาย (และเนื่องจากมีyieldคีย์เวิร์ดที่ใช้งานง่ายสำหรับการใช้งานเครื่องกำเนิดไฟฟ้า ) แต่มันควรจะง่ายพอที่จะพอร์ตไปยังภาษาอื่น ๆ

# a generator function to find all simple paths between two nodes in a
# graph, represented as a dictionary that maps nodes to their neighbors
def find_simple_paths(graph, start, end):
    visited = set()
    visited.add(start)

    nodestack = list()
    indexstack = list()
    current = start
    i = 0

    while True:
        # get a list of the neighbors of the current node
        neighbors = graph[current]

        # find the next unvisited neighbor of this node, if any
        while i < len(neighbors) and neighbors[i] in visited: i += 1

        if i >= len(neighbors):
            # we've reached the last neighbor of this node, backtrack
            visited.remove(current)
            if len(nodestack) < 1: break  # can't backtrack, stop!
            current = nodestack.pop()
            i = indexstack.pop()
        elif neighbors[i] == end:
            # yay, we found the target node! let the caller process the path
            yield nodestack + [current, end]
            i += 1
        else:
            # push current node and index onto stacks, switch to neighbor
            nodestack.append(current)
            indexstack.append(i+1)
            visited.add(neighbors[i])
            current = neighbors[i]
            i = 0

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

รหัสนี้ยังใช้visitedชุดแยกต่างหากซึ่งมีโหนดปัจจุบันและโหนดใด ๆ บนสแต็กเสมอเพื่อให้ฉันตรวจสอบได้อย่างมีประสิทธิภาพว่าโหนดนั้นเป็นส่วนหนึ่งของเส้นทางปัจจุบันหรือไม่ หากภาษาของคุณมีโครงสร้างข้อมูล "ชุดสั่งซื้อ" ที่ให้ทั้งการดำเนินการพุช / ป๊อปแบบสแต็กที่มีประสิทธิภาพและการสืบค้นข้อมูลสมาชิกที่มีประสิทธิภาพคุณสามารถใช้สิ่งนั้นสำหรับโหนดสแต็กและกำจัดvisitedชุดแยก

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

นี่คือรหัสทดสอบบางส่วนที่แสดงให้เห็นว่าฟังก์ชันที่ระบุข้างต้นทำงานอย่างไร:

# test graph:
#     ,---B---.
#     A   |   D
#     `---C---'
graph = {
    "A": ("B", "C"),
    "B": ("A", "C", "D"),
    "C": ("A", "B", "D"),
    "D": ("B", "C"),
}

# find paths from A to D
for path in find_simple_paths(graph, "A", "D"): print " -> ".join(path)

การรันโค้ดนี้บนกราฟตัวอย่างที่กำหนดจะสร้างผลลัพธ์ต่อไปนี้:

A -> B -> C -> ง
A -> B -> ง
A -> C -> B -> ง
ก -> ค -> ง

โปรดทราบว่าในขณะที่กราฟตัวอย่างนี้ไม่มีการกำหนดทิศทาง (เช่นขอบทั้งหมดไปทั้งสองทาง) อัลกอริทึมยังใช้กับกราฟที่กำหนดทิศทางโดยพลการ ตัวอย่างเช่นการลบC -> Bขอบ (โดยการลบออกBจากรายการเพื่อนบ้านของC) ให้ผลลัพธ์เดียวกันยกเว้นพา ธ ที่สาม ( A -> C -> B -> D) ซึ่งเป็นไปไม่ได้อีกต่อไป


ps เป็นเรื่องง่ายที่จะสร้างกราฟซึ่งอัลกอริทึมการค้นหาแบบง่ายเช่นนี้ (และอื่น ๆ ที่ระบุในเธรดนี้) ทำงานได้ไม่ดีนัก

ตัวอย่างเช่นพิจารณาภารกิจในการค้นหาเส้นทางทั้งหมดจาก A ถึง B บนกราฟที่ไม่ได้กำหนดทิศทางโดยที่โหนดเริ่มต้น A มีเพื่อนบ้านสองตัว: โหนดเป้าหมาย B (ซึ่งไม่มีเพื่อนบ้านอื่นนอกจาก A) และโหนด C ที่เป็นส่วนหนึ่งของกลุ่มของn +1 โหนดดังนี้:

graph = {
    "A": ("B", "C"),
    "B": ("A"),
    "C": ("A", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
    "D": ("C", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
    "E": ("C", "D", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
    "F": ("C", "D", "E", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
    "G": ("C", "D", "E", "F", "H", "I", "J", "K", "L", "M", "N", "O"),
    "H": ("C", "D", "E", "F", "G", "I", "J", "K", "L", "M", "N", "O"),
    "I": ("C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "O"),
    "J": ("C", "D", "E", "F", "G", "H", "I", "K", "L", "M", "N", "O"),
    "K": ("C", "D", "E", "F", "G", "H", "I", "J", "L", "M", "N", "O"),
    "L": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "M", "N", "O"),
    "M": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "N", "O"),
    "N": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "O"),
    "O": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N"),
}

เป็นเรื่องง่ายที่จะเห็นว่าเส้นทางเดียวระหว่าง A และ B เป็นเส้นทางตรง แต่ DFS ที่ไร้เดียงสาที่เริ่มต้นจากโหนด A จะเสียเวลา O ( n !) ไปอย่างไร้ประโยชน์ในการสำรวจเส้นทางภายในกลุ่มแม้ว่าจะชัดเจน (สำหรับมนุษย์) ก็ตาม ไม่มีเส้นทางใดที่สามารถนำไปสู่ ​​B ได้

นอกจากนี้เรายังสามารถสร้างDAG ที่มีคุณสมบัติคล้ายกันได้เช่นโดยให้โหนดเริ่มต้น A เชื่อมต่อโหนดเป้าหมาย B และอีกสองโหนด C 1และ C 2ซึ่งทั้งสองโหนดเชื่อมต่อกับโหนด D 1และ D 2ซึ่งทั้งสองเชื่อมต่อกับ E 1และ E 2และอื่น ๆ สำหรับnชั้นของโหนดที่จัดเรียงเช่นนี้การค้นหาอย่างไร้เดียงสาสำหรับเส้นทางทั้งหมดจาก A ถึง B จะทำให้เสียเวลา O (2 n ) ในการตรวจสอบจุดตายที่เป็นไปได้ทั้งหมดก่อนที่จะยอมแพ้

แน่นอนว่าการเพิ่มขอบให้กับโหนดเป้าหมาย B จากโหนดใดโหนดหนึ่งในกลุ่ม (นอกเหนือจาก C) หรือจากเลเยอร์สุดท้ายของ DAG จะสร้างเส้นทางที่เป็นไปได้จำนวนมากแบบทวีคูณจาก A ถึง B และ a อัลกอริธึมการค้นหาในพื้นที่อย่างแท้จริงไม่สามารถบอกล่วงหน้าได้ว่าจะพบขอบดังกล่าวหรือไม่ ดังนั้นในแง่หนึ่งความไวของผลลัพธ์ที่ไม่ดีของการค้นหาที่ไร้เดียงสาดังกล่าวเกิดจากการที่พวกเขาไม่รับรู้ถึงโครงสร้างทั่วโลกของกราฟ

แม้ว่าจะมีวิธีการก่อนการประมวลผลหลายวิธี (เช่นการกำจัดโหนดลีฟซ้ำ ๆ การค้นหาตัวคั่นจุดยอดโหนดเดียว ฯลฯ ) ที่สามารถใช้เพื่อหลีกเลี่ยง "จุดสิ้นสุดของเวลาเอกซ์โพเนนเชียล" บางส่วนได้ แต่ฉันไม่ทราบว่ามีทั่วไป เคล็ดลับการประมวลผลล่วงหน้าที่สามารถกำจัดได้ในทุกกรณี วิธีแก้ปัญหาทั่วไปคือการตรวจสอบในทุกขั้นตอนของการค้นหาว่ายังสามารถเข้าถึงโหนดเป้าหมายได้หรือไม่ (โดยใช้การค้นหาย่อย) และย้อนรอยก่อนหากไม่เป็นเช่นนั้น - แต่อนิจจานั่นจะทำให้การค้นหาช้าลงอย่างมาก (ที่แย่ที่สุด ตามสัดส่วนของขนาดของกราฟ) สำหรับกราฟจำนวนมากที่ไม่มีจุดตายทางพยาธิวิทยาดังกล่าว


1
นั่นคือสิ่งที่ฉันกำลังมองหาขอบคุณ :)
arslan

ขอบคุณสำหรับโซลูชันแบบไม่เรียกซ้ำ DFS ของคุณ เพียงสังเกตว่าการพิมพ์บรรทัดสุดท้ายผลลัพธ์มีข้อผิดพลาดทางไวยากรณ์ควรเป็นfor path in find_simple_paths(graph, "A", "D"): print(" -> ".join(path))เพราะprintไม่มีวงเล็บ
David Oliván Ubieto

1
@ DavidOlivánUbieto: มันคือรหัส Python 2 นั่นคือสาเหตุที่ไม่มีวงเล็บ :)
Ilmari Karonen

5

นี่คือเวอร์ชันเรียกซ้ำที่ดูดีขึ้นอย่างมีเหตุผลเมื่อเทียบกับชั้นสอง

public class Search {

private static final String START = "B";
private static final String END = "E";

public static void main(String[] args) {
    // this graph is directional
    Graph graph = new Graph();
    graph.addEdge("A", "B");
    graph.addEdge("A", "C");
    graph.addEdge("B", "A");
    graph.addEdge("B", "D");
    graph.addEdge("B", "E"); // this is the only one-way connection
    graph.addEdge("B", "F");
    graph.addEdge("C", "A");
    graph.addEdge("C", "E");
    graph.addEdge("C", "F");
    graph.addEdge("D", "B");
    graph.addEdge("E", "C");
    graph.addEdge("E", "F");
    graph.addEdge("F", "B");
    graph.addEdge("F", "C");
    graph.addEdge("F", "E");
    List<ArrayList<String>> paths = new ArrayList<ArrayList<String>>();
    String currentNode = START;
    List<String> visited = new ArrayList<String>();
    visited.add(START);
    new Search().findAllPaths(graph, seen, paths, currentNode);
    for(ArrayList<String> path : paths){
        for (String node : path) {
            System.out.print(node);
            System.out.print(" ");
        }
        System.out.println();
    }   
}

private void findAllPaths(Graph graph, List<String> visited, List<ArrayList<String>> paths, String currentNode) {        
    if (currentNode.equals(END)) { 
        paths.add(new ArrayList(Arrays.asList(visited.toArray())));
        return;
    }
    else {
        LinkedList<String> nodes = graph.adjacentNodes(currentNode);    
        for (String node : nodes) {
            if (visited.contains(node)) {
                continue;
            } 
            List<String> temp = new ArrayList<String>();
            temp.addAll(visited);
            temp.add(node);          
            findAllPaths(graph, temp, paths, node);
        }
    }
}
}

เอาต์พุตโปรแกรม

B A C E 

B A C F E 

B E

B F C E

B F E 

4

โซลูชันในรหัส C ขึ้นอยู่กับ DFS ซึ่งใช้หน่วยความจำขั้นต่ำ

#include <stdio.h>
#include <stdbool.h>

#define maxN    20  

struct  nodeLink
{

    char node1;
    char node2;

};

struct  stack
{   
    int sp;
    char    node[maxN];
};   

void    initStk(stk)
struct  stack   *stk;
{
    int i;
    for (i = 0; i < maxN; i++)
        stk->node[i] = ' ';
    stk->sp = -1;   
}

void    pushIn(stk, node)
struct  stack   *stk;
char    node;
{

    stk->sp++;
    stk->node[stk->sp] = node;

}    

void    popOutAll(stk)
struct  stack   *stk;
{

    char    node;
    int i, stkN = stk->sp;

    for (i = 0; i <= stkN; i++)
    {
        node = stk->node[i];
        if (i == 0)
            printf("src node : %c", node);
        else if (i == stkN)
            printf(" => %c : dst node.\n", node);
        else
            printf(" => %c ", node);
    }

}


/* Test whether the node already exists in the stack    */
bool    InStack(stk, InterN)
struct  stack   *stk;
char    InterN;
{

    int i, stkN = stk->sp;  /* 0-based  */
    bool    rtn = false;    

    for (i = 0; i <= stkN; i++)
    {
        if (stk->node[i] == InterN)
        {
            rtn = true;
            break;
        }
    }

    return     rtn;

}

char    otherNode(targetNode, lnkNode)
char    targetNode;
struct  nodeLink    *lnkNode;
{

    return  (lnkNode->node1 == targetNode) ? lnkNode->node2 : lnkNode->node1;

}

int entries = 8;
struct  nodeLink    topo[maxN]    =       
    {
        {'b', 'a'}, 
        {'b', 'e'}, 
        {'b', 'd'}, 
        {'f', 'b'}, 
        {'a', 'c'},
        {'c', 'f'}, 
        {'c', 'e'},
        {'f', 'e'},               
    };

char    srcNode = 'b', dstN = 'e';      

int reachTime;  

void    InterNode(interN, stk)
char    interN;
struct  stack   *stk;
{

    char    otherInterN;
    int i, numInterN = 0;
    static  int entryTime   =   0;

    entryTime++;

    for (i = 0; i < entries; i++)
    {

        if (topo[i].node1 != interN  && topo[i].node2 != interN) 
        {
            continue;   
        }

        otherInterN = otherNode(interN, &topo[i]);

        numInterN++;

        if (otherInterN == stk->node[stk->sp - 1])
        {
            continue;   
        }

        /*  Loop avoidance: abandon the route   */
        if (InStack(stk, otherInterN) == true)
        {
            continue;   
        }

        pushIn(stk, otherInterN);

        if (otherInterN == dstN)
        {
            popOutAll(stk);
            reachTime++;
            stk->sp --;   /*    back trace one node  */
            continue;
        }
        else
            InterNode(otherInterN, stk);

    }

        stk->sp --;

}


int    main()

{

    struct  stack   stk;

    initStk(&stk);
    pushIn(&stk, srcNode);  

    reachTime = 0;
    InterNode(srcNode, &stk);

    printf("\nNumber of all possible and unique routes = %d\n", reachTime);

}

2

อาจจะช้าไป แต่นี่คืออัลกอริทึม DFS เวอร์ชัน C # เดียวกันใน Java จาก Casey เพื่อสำรวจเส้นทางทั้งหมดระหว่างสองโหนดโดยใช้สแต็ก ความสามารถในการอ่านดีขึ้นด้วยการเรียกซ้ำเช่นเคย

    void DepthFirstIterative(T start, T endNode)
    {
        var visited = new LinkedList<T>();
        var stack = new Stack<T>();

        stack.Push(start);

        while (stack.Count != 0)
        {
            var current = stack.Pop();

            if (visited.Contains(current))
                continue;

            visited.AddLast(current);

            var neighbours = AdjacentNodes(current);

            foreach (var neighbour in neighbours)
            {
                if (visited.Contains(neighbour))
                    continue;

                if (neighbour.Equals(endNode))
                {
                    visited.AddLast(neighbour);
                    printPath(visited));
                    visited.RemoveLast();
                    break;
                }
            }

            bool isPushed = false;
            foreach (var neighbour in neighbours.Reverse())
            {
                if (neighbour.Equals(endNode) || visited.Contains(neighbour) || stack.Contains(neighbour))
                {
                    continue;
                }

                isPushed = true;
                stack.Push(neighbour);
            }

            if (!isPushed)
                visited.RemoveLast();
        }
    }
นี่คือกราฟตัวอย่างที่จะทดสอบ:

    // ตัวอย่างกราฟ. ตัวเลขคือรหัสขอบ
    // 1 3       
    // ก --- ข --- ค ----
    // | | 2 |
    // | 4 ----- D |
    // ------------------

1
ยอดเยี่ยม - เกี่ยวกับวิธีที่คุณแทนที่การเรียกซ้ำด้วยการทำซ้ำตามสแต็ก
Siddhartha Ghosh

ฉันยังไม่เข้าใจมันคือneighbours.Reverse()อะไร? มันคือList<T>.Reverse อะไร?

ฉันตรวจสอบเวอร์ชันที่ไม่เกิดซ้ำแล้ว แต่ดูเหมือนจะไม่ถูกต้อง เวอร์ชันเรียกซ้ำไม่เป็นไร บางทีเมื่อเปลี่ยนเป็นแบบไม่เกิดซ้ำความผิดพลาดเล็ก ๆ น้อย ๆ ก็เกิดขึ้น
arslan

@alim: ตกลงรหัสนี้เสียง่าย (มันไม่ได้ลบโหนดออกจากชุดที่เยี่ยมชมอย่างถูกต้องเมื่อทำการย้อนรอยและการจัดการสแต็กก็ดูเหมือนจะยุ่งเหยิงเช่นกันฉันพยายามดูว่าสามารถแก้ไขได้หรือไม่ แต่โดยพื้นฐานแล้วจะต้องมีการเขียนซ้ำทั้งหมด) ฉันเพิ่ง เพิ่มคำตอบด้วยวิธีแก้ปัญหาที่ถูกต้องและใช้งานไม่ได้ซ้ำ (ใน Python แต่ควรจะโอนไปยังภาษาอื่นได้ค่อนข้างง่าย)
Ilmari Karonen

@llmari Karonen ดีฉันจะไปตรวจเยี่ยมเยี่ยมมาก
arslan

1

ฉันได้แก้ไขปัญหาที่คล้ายกันกับสิ่งนี้เมื่อเร็ว ๆ นี้แทนที่จะเป็นวิธีแก้ปัญหาทั้งหมดที่ฉันสนใจเพียงสั้นที่สุด

ฉันใช้การค้นหาซ้ำแบบ "ความกว้างก่อน" ซึ่งใช้คิวของสถานะซึ่งแต่ละรายการมีบันทึกที่มีจุดปัจจุบันบนกราฟและเส้นทางที่ไปถึงที่นั่น

คุณเริ่มต้นด้วยเร็กคอร์ดเดียวในคิวซึ่งมีโหนดเริ่มต้นและเส้นทางว่าง

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

ตอนนี้คุณสามารถใช้สิ่งที่คล้ายกัน แต่เมื่อคุณพบวิธีแก้ปัญหาแทนที่จะหยุดให้เพิ่มโซลูชันนั้นใน 'รายการที่พบ' ของคุณและดำเนินการต่อ

คุณต้องติดตามรายการโหนดที่เยี่ยมชมเพื่อที่คุณจะได้ไม่ย้อนรอยตัวเองไม่เช่นนั้นคุณจะวนซ้ำไม่สิ้นสุด

หากคุณต้องการรหัสเทียมอีกเล็กน้อยโพสต์ความคิดเห็นหรืออะไรบางอย่างและฉันจะอธิบายอย่างละเอียด


6
ฉันเชื่อว่าหากคุณสนใจเพียงเส้นทางที่สั้นที่สุด Algorithm ของ Dijkstra ก็คือ "ทางออก" :)
vicatcu

1

ฉันคิดว่าคุณควรอธิบายปัญหาที่แท้จริงของคุณที่อยู่เบื้องหลังเรื่องนี้ ฉันพูดแบบนี้เพราะคุณขอเวลาที่มีประสิทธิภาพ แต่คำตอบที่ตั้งไว้สำหรับปัญหาดูเหมือนจะเพิ่มขึ้นอย่างทวีคูณ!

ดังนั้นฉันจะไม่คาดหวังว่าอัลกอริทึมที่ดีไปกว่าสิ่งที่เป็นเลขชี้กำลัง

ฉันจะย้อนรอยและดูกราฟทั้งหมด เพื่อหลีกเลี่ยงการวนรอบให้บันทึกโหนดที่เยี่ยมชมทั้งหมดระหว่างทาง เมื่อคุณย้อนกลับให้ยกเลิกการทำเครื่องหมายโหนด

ใช้การเรียกซ้ำ:

static bool[] visited;//all false
Stack<int> currentway; initialize empty

function findnodes(int nextnode)
{
if (nextnode==destnode)
{
  print currentway 
  return;
}
visited[nextnode]=true;
Push nextnode to the end of currentway.
for each node n accesible from nextnode:
  findnodes(n);
visited[nextnode]=false; 
pop from currenteay
}

หรือว่าผิด?

แก้ไข: อ้อและฉันลืม: คุณควรกำจัดการโทรซ้ำโดยใช้โหนดสแต็กนั้น


ปัญหาที่แท้จริงของฉันเป็นไปตามที่ฉันอธิบายไว้คือชุดที่ใหญ่กว่ามากเท่านั้น ฉันยอมรับว่าสิ่งนี้ดูเหมือนจะเติบโตขึ้นอย่างทวีคูณตามขนาดของชุด
Robert Groves

1

หลักการพื้นฐานคือคุณไม่ต้องกังวลกับกราฟนี่คือปัญหามาตรฐานที่เรียกว่าปัญหาการเชื่อมต่อแบบไดนามิก มีวิธีการดังต่อไปนี้ซึ่งคุณสามารถเชื่อมต่อโหนดได้หรือไม่:

  1. ค้นหาด่วน
  2. สหภาพด่วน
  3. อัลกอริทึมที่ปรับปรุงแล้ว (การรวมกันของทั้งสองอย่าง)

นี่คือรหัส C ที่ฉันได้ลองใช้ด้วยความซับซ้อนของเวลาขั้นต่ำ O (log * n) นั่นหมายความว่าสำหรับรายการขอบ 65536 ต้องค้นหา 4 ครั้งและสำหรับ 2 ^ 65536 ต้องใช้การค้นหา 5 ครั้ง ฉันกำลังแบ่งปันการใช้งานของฉันจากอัลกอริทึม: Algorithm Course จากมหาวิทยาลัยพรินซ์ตัน

เคล็ดลับ: คุณสามารถค้นหาโซลูชัน Java ได้จากลิงก์ที่แชร์ด้านบนพร้อมคำอธิบายที่เหมาะสม

/* Checking Connection Between Two Edges */

#include<stdio.h>
#include<stdlib.h>
#define MAX 100

/*
  Data structure used

vertex[] - used to Store The vertices
size - No. of vertices
sz[] - size of child's
*/

/*Function Declaration */
void initalize(int *vertex, int *sz, int size);
int root(int *vertex, int i);
void add(int *vertex, int *sz, int p, int q);
int connected(int *vertex, int p, int q);

int main() //Main Function
{ 
char filename[50], ch, ch1[MAX];
int temp = 0, *vertex, first = 0, node1, node2, size = 0, *sz;
FILE *fp;


printf("Enter the filename - "); //Accept File Name
scanf("%s", filename);
fp = fopen(filename, "r");
if (fp == NULL)
{
    printf("File does not exist");
    exit(1);
}
while (1)
{
    if (first == 0) //getting no. of vertices
    {
        ch = getc(fp);
        if (temp == 0)
        {
            fseek(fp, -1, 1);
            fscanf(fp, "%s", &ch1);
            fseek(fp, 1, 1);
            temp = 1;
        }
        if (isdigit(ch))
        {
            size = atoi(ch1);
            vertex = (int*) malloc(size * sizeof(int));     //dynamically allocate size  
            sz = (int*) malloc(size * sizeof(int));
            initalize(vertex, sz, size);        //initialization of vertex[] and sz[]
        }
        if (ch == '\n')
        {
            first = 1;
            temp = 0;
        }
    }
    else
    {
        ch = fgetc(fp);
        if (isdigit(ch))
            temp = temp * 10 + (ch - 48);   //calculating value from ch
        else
        {
            /* Validating the file  */

            if (ch != ',' && ch != '\n' && ch != EOF)
            {
                printf("\n\nUnkwown Character Detected.. Exiting..!");

                exit(1);
            }
            if (ch == ',')
                node1 = temp;
            else
            {
                node2 = temp;
                printf("\n\n%d\t%d", node1, node2);
                if (node1 > node2)
                {
                    temp = node1;
                    node1 = node2;
                    node2 = temp;
                }

                /* Adding the input nodes */

                if (!connected(vertex, node1, node2))
                    add(vertex, sz, node1, node2);
            }
            temp = 0;
        }

        if (ch == EOF)
        {
            fclose(fp);
            break;
        }
    }
}

do
{
    printf("\n\n==== check if connected ===");
    printf("\nEnter First Vertex:");
    scanf("%d", &node1);
    printf("\nEnter Second Vertex:");
    scanf("%d", &node2);

    /* Validating The Input */

    if( node1 > size || node2 > size )
    {
        printf("\n\n Invalid Node Value..");
        break;
    }

    /* Checking the connectivity of nodes */

    if (connected(vertex, node1, node2))
        printf("Vertex %d and %d are Connected..!", node1, node2);
    else
        printf("Vertex %d and %d are Not Connected..!", node1, node2);


    printf("\n 0/1:  ");

    scanf("%d", &temp);

} while (temp != 0);

free((void*) vertex);
free((void*) sz);


return 0;
}

void initalize(int *vertex, int *sz, int size) //Initialization of graph
{
int i;
for (i = 0; i < size; i++)
{
    vertex[i] = i;
    sz[i] = 0;
}
}
int root(int *vertex, int i)    //obtaining the root
{
while (i != vertex[i])
{
    vertex[i] = vertex[vertex[i]];
    i = vertex[i];
}
return i;
}

/* Time Complexity for Add --> logn */
void add(int *vertex, int *sz, int p, int q) //Adding of node
{
int i, j;
i = root(vertex, p);
j = root(vertex, q);

/* Adding small subtree in large subtree  */

if (sz[i] < sz[j])
{
    vertex[i] = j;
    sz[j] += sz[i];
}
else
{
    vertex[j] = i;
    sz[i] += sz[j];
}

}

/* Time Complexity for Search -->lg* n */

int connected(int *vertex, int p, int q) //Checking of  connectivity of nodes
{
/* Checking if root is same  */

if (root(vertex, p) == root(vertex, q))
    return 1;

return 0;
}

สิ่งนี้ดูเหมือนจะไม่สามารถแก้ปัญหาได้ตามที่ถาม OP ต้องการค้นหาเส้นทางที่เรียบง่ายทั้งหมดระหว่างสองโหนดไม่ใช่เพียงเพื่อตรวจสอบว่ามีเส้นทางอยู่หรือไม่
Ilmari Karonen

1

find_paths [s, t, d, k]

คำถามนี้เก่าและมีคำตอบแล้ว อย่างไรก็ตามไม่มีใครแสดงว่าอัลกอริทึมที่ยืดหยุ่นกว่าในการทำสิ่งเดียวกันให้สำเร็จ ดังนั้นฉันจะโยนหมวกของฉันเข้าไปในวงแหวน

ฉันเองพบว่าอัลกอริทึมของแบบฟอร์มfind_paths[s, t, d, k]มีประโยชน์โดยที่:

  • s คือโหนดเริ่มต้น
  • t คือโหนดเป้าหมาย
  • d คือความลึกสูงสุดในการค้นหา
  • k คือจำนวนเส้นทางที่จะค้นหา

การใช้รูปแบบอินฟินิตี้ของภาษาโปรแกรมของคุณdและkจะให้เส้นทางทั้งหมดแก่คุณ

§เห็นได้ชัดว่าหากคุณใช้กราฟกำกับและคุณต้องการเส้นทางที่ไม่ได้บอกทิศทางทั้งหมดระหว่างsและtคุณจะต้องเรียกใช้ทั้งสองวิธีนี้:

find_paths[s, t, d, k] <join> find_paths[t, s, d, k]

ฟังก์ชันตัวช่วย

โดยส่วนตัวแล้วฉันชอบการเรียกซ้ำแม้ว่าจะทำได้ยากในบางครั้ง แต่ก่อนอื่นให้กำหนดฟังก์ชันตัวช่วยของเรา:

def find_paths_recursion(graph, current, goal, current_depth, max_depth, num_paths, current_path, paths_found)
  current_path.append(current)

  if current_depth > max_depth:
    return

  if current == goal:
    if len(paths_found) <= number_of_paths_to_find:
      paths_found.append(copy(current_path))

    current_path.pop()
    return

  else:
    for successor in graph[current]:
    self.find_paths_recursion(graph, successor, goal, current_depth + 1, max_depth, num_paths, current_path, paths_found)

  current_path.pop()

ฟังก์ชั่นหลัก

ด้วยวิธีนี้ฟังก์ชันหลักจึงไม่สำคัญ:

def find_paths[s, t, d, k]:
  paths_found = [] # PASSING THIS BY REFERENCE  
  find_paths_recursion(s, t, 0, d, k, [], paths_found)

ก่อนอื่นให้สังเกตบางสิ่ง:

  • รหัสหลอกข้างต้นเป็นภาษาที่ผสมผสานกัน - แต่ส่วนใหญ่มีลักษณะคล้ายกับ python มากที่สุด (เนื่องจากฉันเพิ่งเข้ารหัส) การคัดลอกวางอย่างเข้มงวดจะไม่ทำงาน
  • [] เป็นรายการที่ไม่ได้กำหนดค่าเริ่มต้นแทนที่ด้วยค่าที่เทียบเท่ากับภาษาโปรแกรมที่คุณเลือก
  • paths_foundจะถูกส่งผ่านโดยการอ้างอิง เป็นที่ชัดเจนว่าฟังก์ชันการเรียกซ้ำไม่ส่งคืนอะไรเลย จัดการสิ่งนี้อย่างเหมาะสม
  • นี่graphคือสมมติว่ามีhashedโครงสร้างบางรูปแบบ มีหลายวิธีในการใช้กราฟ ไม่ว่าจะด้วยวิธีใดgraph[vertex]คุณจะได้รับรายการจุดยอดที่อยู่ติดกันในทิศทางกราฟ - ปรับตามนั้น
  • สิ่งนี้ถือว่าคุณได้ประมวลผลล่วงหน้าเพื่อลบ "หัวเข็มขัด" (ลูปในตัว) รอบและหลายขอบ

0

นี่คือความคิดที่อยู่ในหัวของฉัน:

  1. ค้นหาหนึ่งการเชื่อมต่อ (การค้นหาความลึกก่อนน่าจะเป็นอัลกอริทึมที่ดีสำหรับสิ่งนี้เนื่องจากความยาวเส้นทางไม่สำคัญ)
  2. ปิดการใช้งานส่วนสุดท้าย
  3. ลองค้นหาการเชื่อมต่ออื่นจากโหนดสุดท้ายก่อนการเชื่อมต่อที่ปิดใช้งานก่อนหน้านี้
  4. ไปที่ 2 จนกว่าจะไม่มีการเชื่อมต่ออีกต่อไป

สิ่งนี้จะใช้ไม่ได้โดยทั่วไป: เป็นไปได้ค่อนข้างมากที่เส้นทางระหว่างจุดยอดสองจุดจะมีขอบสุดท้ายเหมือนกัน วิธีการของคุณจะพบเพียงหนึ่งในเส้นทางดังกล่าว
Ilmari Karonen

0

เท่าที่ฉันสามารถบอกวิธีแก้ปัญหาที่ Ryan Fox มอบให้ ( 58343 , Christian ( 58444 ) และตัวคุณเอง ( 58461 ) นั้นดีพอ ๆ กับที่ได้รับฉันไม่เชื่อว่าการข้ามผ่านครั้งแรกแบบกว้างจะช่วยได้ในกรณีนี้อย่างที่คุณจะทำ ไม่ได้รับทุกเส้นทาง. ยกตัวอย่างเช่นมีขอบ(A,B), (A,C), (B,C), (B,D)และ(C,D)คุณจะได้รับเส้นทางABDและแต่ไม่ACDABCD


mweerden การข้ามผ่านครั้งแรกที่กว้างที่สุดที่ฉันส่งจะพบทุกเส้นทางในขณะที่หลีกเลี่ยงรอบใด ๆ สำหรับกราฟที่คุณระบุการใช้งานจะค้นหาทั้งสามเส้นทางได้อย่างถูกต้อง
Casey Watson

ฉันไม่ได้อ่านรหัสของคุณอย่างสมบูรณ์และถือว่าคุณใช้การข้ามผ่านครั้งแรกแบบกว้าง (เพราะคุณพูดอย่างนั้น) อย่างไรก็ตามจากการตรวจสอบอย่างใกล้ชิดหลังจากความคิดเห็นของคุณฉันสังเกตเห็นว่าแท้จริงแล้วมันไม่ได้เป็นเช่นนั้น จริงๆแล้วมันเป็นการข้ามผ่านเชิงลึกครั้งแรกที่ไร้ความทรงจำเช่นเดียวกับ Ryan, Christian และ Robert
mweerden

0

ฉันพบวิธีที่จะแจกแจงเส้นทางทั้งหมดรวมถึงเส้นทางที่ไม่มีที่สิ้นสุดที่มีลูป

http://blog.vjeux.com/2009/project/project-shortest-path.html

การค้นหาเส้นทางและวัฏจักรของอะตอม

Definition

สิ่งที่เราต้องการทำคือค้นหาเส้นทางที่เป็นไปได้ทั้งหมดจากจุด A ไปยังจุด B เนื่องจากมีวงจรที่เกี่ยวข้องคุณจึงไม่สามารถผ่านและแจกแจงทั้งหมดได้ แต่คุณจะต้องค้นหาเส้นทางอะตอมที่ไม่วนรอบและรอบที่เล็กที่สุดเท่าที่จะเป็นไปได้ (คุณไม่ต้องการให้วงจรของคุณเกิดซ้ำ)

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

คำจำกัดความนี้มีประโยชน์และใช้ได้กับวัฏจักร: วัฏจักรอะตอมของจุด A คือเส้นทางอะตอมที่ไปจากจุด A และสิ้นสุดไปยังจุด A

การดำเนินงาน

Atomic Paths A -> B

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

เมื่อมาถึงจุดปลายทางเราสามารถเก็บเส้นทางที่พบ

Freeing the list

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

แต่วิธีแก้ปัญหาที่ชาญฉลาดคือการใช้การนับอ้างอิง (ได้รับแรงบันดาลใจจาก Garbage Collection) ทุกครั้งที่คุณเพิ่มลิงก์ไปยังพาเรนต์คุณจะเพิ่มลิงก์ไปยังจำนวนการอ้างอิง จากนั้นเมื่อคุณมาถึงจุดสิ้นสุดของเส้นทางคุณจะถอยหลังและเป็นอิสระในขณะที่จำนวนอ้างอิงเท่ากับ 1 หากสูงกว่าคุณเพียงแค่ลบออกหนึ่งรายการแล้วหยุด

Atomic Cycle A

การค้นหาวัฏจักรอะตอมของ A นั้นเหมือนกับการมองหาเส้นทางอะตอมจาก A ถึง A อย่างไรก็ตามมีการปรับให้เหมาะสมหลายอย่างที่เราสามารถทำได้ อันดับแรกเมื่อเรามาถึงจุดปลายทางเราต้องการบันทึกเส้นทางก็ต่อเมื่อผลรวมของต้นทุนขอบเป็นลบ: เราต้องการผ่านรอบการดูดซับเท่านั้น

ดังที่คุณได้เห็นก่อนหน้านี้กราฟทั้งหมดจะถูกข้ามไปเมื่อมองหาเส้นทางปรมาณู แต่เราสามารถ จำกัด พื้นที่การค้นหาเฉพาะองค์ประกอบที่เชื่อมต่ออย่างแน่นหนาที่มี A. การค้นหาส่วนประกอบเหล่านี้จำเป็นต้องใช้การสำรวจกราฟอย่างง่ายด้วยอัลกอริทึมของ Tarjan

การรวมเส้นทางและวัฏจักรของอะตอม

ณ จุดนี้เรามีเส้นทางอะตอมทั้งหมดที่ไปจาก A ไป B และวงจรอะตอมทั้งหมดของแต่ละโหนดเหลือให้เราจัดระเบียบทุกอย่างเพื่อให้ได้เส้นทางที่สั้นที่สุด จากนี้ไปเราจะศึกษาวิธีการค้นหาการผสมผสานที่ดีที่สุดของวัฏจักรอะตอมในเส้นทางอะตอม


สิ่งนี้ดูเหมือนจะไม่ตอบโจทย์ตามที่ถาม
Ilmari Karonen

0

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

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

การค้นหาจะย้อนกลับไปยังโหนดล่าสุดที่ยังไม่เสร็จสิ้นการสำรวจ

ฉันเขียนบล็อกเกี่ยวกับหัวข้อนี้เมื่อไม่นานมานี้โดยโพสต์ตัวอย่างการใช้งาน C ++ ในกระบวนการ


0

การเพิ่มคำตอบของ Casey Watson นี่คือการใช้งาน Java อีกแบบหนึ่ง การเริ่มต้นโหนดที่เยี่ยมชมด้วยโหนดเริ่มต้น

private void getPaths(Graph graph, LinkedList<String> visitedNodes) {
                LinkedList<String> adjacent = graph.getAdjacent(visitedNodes.getLast());
                for(String node : adjacent){
                    if(visitedNodes.contains(node)){
                        continue;
                    }
                    if(node.equals(END)){
                        visitedNodes.add(node);
                        printPath(visitedNodes);
                        visitedNodes.removeLast();
                    }
                    visitedNodes.add(node);
                    getPaths(graph, visitedNodes);
                    visitedNodes.removeLast();  
                }
            }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.