2016-06-20 4 views
1

Ich schreibe eine Funktion in Scheme mit Advanced Student-Einstellungen. Die Funktion durchläuft das Diagramm G und schaut, ob es einen Pfad zwischen dem Knoten X und Y gibt. Es funktioniert "irgendwie", aber nicht in allen Fällen. Ich weiß, wo das Problem liegt, aber ich bin mir nicht sicher, wie ich es beheben soll. Zuerst schaue ich, ob der Scheitelpunkt X, auf den ich schaue, besucht wurde, wenn nicht, fahre ich fort und markiere ihn als besucht. Dann gibt es die Funktion find-adju. Diese Funktion gibt eine Liste aller Nachbarn für einen gegebenen Eckpunkt zurück. Zum Beispiel, wenn ich hatte eine grafische Darstellung wie folgt aus:Wie wird die Prozedur für jedes Element in einer Liste in Scheme iteriert?

(define-struct graph (vertices edges)) 
(define-struct vertice (name visited)) 
(define-struct edge (start-vertice end-vertice length)) 

(define vertices-list 
    (list (make-vertice 0 0) 
     (make-vertice 1 0) 
     (make-vertice 2 0) 
     (make-vertice 3 0) 
     (make-vertice 4 0) 
     ) 
) 

(define edges-list 
    (list (make-edge 0 1 0) 
     (make-edge 0 2 0) 
     (make-edge 1 3 0) 
     (make-edge 2 0 0) 
     (make-edge 2 4 0) 
     (make-edge 4 2 0) 
     ) 
) 
(define G (make-graf vertices-list edge-list)) 
(traverse-graph? 0 4 G) 

Dann für gegebenen Knoten 0 würde zurückkehren Liste (1 2). Als nächstes schaue ich mir die Liste an und frage, ob mein gewünschter Vertex Y darin ist. Wenn nicht, suchen Sie erneut rekursiv nach dem ersten Element in der Liste. Aber dabei verliere ich Informationen über alle anderen Nachbarn. Im selben Fall würde es den Knoten 1 betrachten, alle seine Nachbarn finden und dann würde die Prozedur aufhören, weil sie keinen Weg gefunden hat. Aber der Scheitelpunkt 2 bleibt dann unbesucht. Wie kann ich den Prozess auf jeden Artikel in der Lsit und nicht nur auf den ersten schauen lassen?

(define (traverse-graph X Y G) 
    (cond 
    [(not(eq? (vertex-visited (find-vertex X (graph-vertices G))) VISITED)) 
    (begin 
     (set-vertex-visited! (find-vertex X (graph-vertices G)) VISITED) 
     (cond 
     [(member Y (find-adju X (graph-edges G))) #t] 
     [(not (empty? (find-adju X (graph-edges G)))) (traverse-graph (car (find-adju X (graph-edges G))) Y G) ] 
     [else #f] 
     ) 
     ) 
    ] 
    [else #f] 
    ) 
) 

dachte ich vielleicht die ganze Liste mit cdr Rückkehr statt Auto in die Traverse-Funktion, aber ich weiß nicht, wie das realisieren. Und wie würde ich mit dem ersten Schritt umgehen, wo X eine Zahl und keine Liste ist.

EDIT:

I for-each versucht, das Hinzufügen, das in Ordnung zu sein scheint zu funktionieren, aber das Ergebnis ist mir nichts zu geben. Kein wahr oder falsch. Wenn ich es Schritt für Schritt zu debuggen, sehe ich, dass es wahrscheinlich richtig überquert, aber wenn es das erreicht [(Mitglied Bedingung, stoppt er, ohne irgendetwas zurückkehrt, auch wenn die Bedingung erfüllt ist.

(define (traverse-graph X Y G) 
    (cond 
    [(not(eq? (vertex-visited (find-vertex X (graph-vertices G))) VISITED)) 
    (begin 
     (set-vertex-visited! (find-vertex X (graph-vertices G)) VISITED) 
     (cond 
     [(member Y (find-adju X (graph-edges G))) #t] 
     [(not (empty? (find-adju X (graph-edges G)))) 
      (for-each (lambda (h) 
        (traverse-graph h Y G) 
       ) (find-adju X (graph-edges G)) 
        ) 
      ] 
     [else #f] 
     ) 
     ) 
    ] 
    [else #f] 
    ) 
) 

Antwort

1

„aber wenn es die [(Mitglied Zustand erreicht, stoppt er, ohne irgendetwas zurückkehrt, auch wenn die Bedingung wahr ist.“

prüfen sie zuerst, ob die Liste produziert von

(find-adju X (graph-edges G))) 

leer ist. Dann prüfen sie, ob die Liste nicht ist leer und wenn Y in dieser Liste ist, dann habe einen anderen Fall, wo die Liste ist nicht leer, aber Y ist nicht in der Liste der unmittelbaren Nachbarn.

+0

Ich habe irgendwie verloren. Können Sie mir einen genauen Code zeigen? – Arcane

2

Sie waren auf dem richtigen Weg mit for-each, aber diese Funktion ist ein Imperativ Konstrukt. Für jede Kante in Ihrer Liste heißt es "tu das, tu das", behält aber keinen Wert. Sie hätten mehr Glück mit map, die über die Liste iteriert und die Ergebnisse in einer Liste aggregiert.

Mit map, das Ergebnis Ihrer Verfahrweg-Diagramm wird ein Baum mit der Form einer Depth-First-Suche Traversal sein:

'(result0 (result1 (result3)) 
      (result2 (result4))) 

Sie (apply append (map (lambda …) (find-adju …)) verwenden könnte die Liste der Listen anhängen in jedem Schritt und mit cons das Ergebnis für den aktuellen Knoten voranstellen. Vergessen Sie nicht, eine Liste mit einem einzelnen Element für die Blattknoten Ihres Traversales zurückzugeben, d. H. Verwenden Sie '(#t) und '(#f). Dies hat jedoch einen großen Nachteil, die Zeitkomplexität ist O(N²) im schlimmsten Fall.Stellen Sie sich ein Diagramm, in dem jeder Knoten zwei Kinder hat: ein Blatt wie sein rechtes Kind, und den Rest des Graphen als linkes Kind:

(→ 0 2) (→ 0 1) 
(→ 2 4) (→ 2 3) 
(→ 4 6) (→ 4 5) 
(→ 6 8) (→ 6 7) 
(→ 8 10) (→ 8 9) 
… 
(→ 96 98) (→ 96 97) 
(→ 98 100) (→ 98 99) 

Mit diesem Diagramm, Ihre Traversal wird das Bohren nach unten den linken Rand, bis sie beginnen erreicht den linken Knoten (100),

  • dann von der Rückkehr zum Knoten 98, gehen sie auf 99 und zurück bis zu 98, wo es '(result100) zu '(result99) anhängt und prepend result98,
  • dann wird es bis zu 96 untersuchen zu bewegen zurück 97, bewegen zurück zu 96, und fügen Sie '(result98 result100 result99)-'(result97) and prepend result96`,
  • ...
  • dann wird es wieder auf 0 untersuchen 1, bewegen zurück zu 0 bewegen, und fügen Sie '(… many results here …) an '(result1) an und fügen Sie result0 voran.

Da append hat alle Elemente aus dem Präfix zu kopieren, um eine Liste der Länge n auf eine Liste der Länge anhängt mn Operationen kosten wird (die zweite Liste wird einfach zugespitzt, und muss nicht sein kopiert, da der Schwanz, den es bildet, identisch mit der ursprünglichen ganzen zweiten Liste ist), dh es kostet O(n) Operationen. Die Reihenfolge der Anrufe an append sein:

append 1 element to 1 element 
append 3 elements to 1 element 
append 5 elements to 1 element 
append 7 elements to 1 element 
append 9 elements to 1 element 
… 
append 99 elements to 1 element 

So beträgt der Gesamtaufwand 1 + 3 + 5 + ... + N, wobei N die Gesamtanzahl von Knoten ist, die N² mal konstanter Faktor ungefähr äquivalent ist, Daher die O(n²), was bedeutet, dass für eine große Anzahl von Knoten wird es sehr langsam sein. Mehr dazu at wikipedia.


die O(N²) Kosten zu vermeiden, können Sie einen Speicher verwenden: jeder Schritt ein einzelnes Element zu dem Akkumulator prepend wird, und es herumreichen. Dies bedeutet, dass Sie bei der Behandlung eines Knotens den aktuellen Akkumulator an seinen ersten Nachbarn übergeben müssen, den modifizierten Akkumulator für den gesamten Sub-Baum zurückholen, diesen an den zweiten Nachbarn übergeben, einen neuen modifizierten Akkumulator zurückholen und an den dritter Nachbar usw.

Dafür könnten Sie Ihre eigene rekursive Funktion über die Liste schreiben, den letzten Akkumulator und die Liste als Argument nehmen und einen rekursiven Aufruf mit dem modifizierten Akkumulator machen (erhalten durch Verarbeiten des gesamten Subs) -tree) und der Schwanz der Liste.

Sie könnten auch die foldl Funktion verwenden, die über dieses Muster abstrahiert. Die Knoten-Verarbeitungsfunktion wird dann diese Struktur folgen:

(define (process node accumulator) 
    (foldl ; the function applied to each neighbour of the list, 
     ; it is passed the neighbour and the accumulator returned 
     ; by the previous iteration 
     (lambda (neighbour latest-accumulator) 
      (if (visited? neighbour) 
       (process neighbour latest-accumulator) 
       latest-accumulator)) ; return unchanged 
     ; initial accumulator for the first iteration, 
     ; we already prepend the result for the current node, 
     ; but that could be done afterwards, by prepending 
     ; to the final result of `foldl`. 
     (cons (compute-result-for node) accumulator) 
     ; the list of neighbours: 
     (neighbours-of node))) 

Da dies nicht nie Listen anhänge, sondern nur ein einziges Ergebnis bei jedem Schritt vorangestellt wird, hat es eine Komplexität von O(N) (ohne die Kosten der neighbours-of Funktion zu Zählen , siehe den Hinweis zu den Hashtabellen und Sets unten). Der Datenfluss ist ein wenig verworren, leider gibt es keine bessere Option mit unveränderlichen Datenstrukturen.


In Ihrem speziellen Fall, da Sie eine boolean zurückgeben, können Sie auch einfach ormap verwenden, die über die Liste iterieren werden und das Rück #t wenn die lambda#t für jedes Element zurückgegeben.

Hat dies die O (N²) -Komplexität wie map? Denke darüber nach, warum es nicht so ist.


Als Randbemerkung, sollten Sie #f und #t statt 0 und VISITED oder #f und 'visited (das zitierte ' Symbol visited verwenden, die in einer if Aussage wahr betrachtet wird, wie alle anderen Werte als #f).

Verwenden Sie zur besseren Leistung Hashtabellen und -sätze, um die Kanten zu speichern, da das Suchen nach Kanten in einer Liste bei vielen Kanten sehr viel kostet.

Schließlich würde ich empfehlen, die schließende Klammer eines Codeblocks am Ende der letzten Zeile zu setzen, anstatt sie alleine auf ihrer Zeile zu haben. Dies ist die gängige Praxis in Scheme, Racket und den meisten anderen Lisp-Varianten und macht den Code IMHO besser lesbar.

Verwandte Themen