如何使程序迭代Scheme中列表中的每个项目?

时间:2016-06-20 14:10:03

标签: graph scheme racket

我正在使用高级学生设置在Scheme中编写一个函数。该函数遍历图G并查看顶点X和Y之间是否存在路径。它"有点"有效,但并非在所有情况下都有效。我知道问题在哪里,但我不知道如何解决它。首先,我看看我正在查看的顶点X是否已被访问,如果不是我继续并将其标记为已访问。然后有功能find-adju。该函数返回给定顶点的所有邻居的列表。例如,如果我有这样的图表:

(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)

然后对于给定的顶点0,它将返回列表(1 2)。接下来,我查看列表并询问,如果我想要的顶点Y在其中。如果没有,请再次在列表中的第一个项目处查找。但在这样做的过程中,我失去了所有其他邻居的信息。在相同的情况下,它将查看顶点1,找到他的所有邻居,然后该过程将退出,因为它没有找到方法。但是顶点2仍未被访问。如何让过程看到lsit中的每个项目,而不仅仅是第一个项目?

(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]
    )
  )

我想到可能用cdr而不是car将整个列表返回到遍历函数,但我不知道如何实现它。我将如何处理X是数字而不是列表的第一步。

编辑:

我尝试添加for-each似乎工作正常,但结果并没有给我任何东西。没有真或假。如果我一步一步地调试它,我看到它可能正确遍历但是当它到达[(成员条件时,它会停止而不返回任何内容,即使条件为真。

(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]
    )
  )

2 个答案:

答案 0 :(得分:2)

你使用for-each走在了正确的道路上,但这个功能是一个必要的构造。对于列表中的每个边缘,它表示“执行此操作,执行此操作”,但不保留任何值。你可以通过map获得更多运气,它会在列表中进行迭代,并在列表中聚合结果。

使用map,您的遍历图的结果将是一个具有深度优先搜索遍历形状的树:

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

您可以使用(apply append (map (lambda …) (find-adju …))在每个步骤附加列表列表,并在cons前面添加当前节点的结果。不要忘记返回包含您的遍历的叶节点的单个元素的列表,即使用'(#t)'(#f)。然而,在最坏的情况下,时间复杂度O(N²)有一个很大的缺点。想象一个图表,其中每个节点都有两个子节点:一个叶子作为它的右子节点,另一个节点作为它的左子节点:

(→ 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)

使用该图表,您的遍历将开始向左钻取,直到它到达最左边的节点(100),

  • 然后从那里返回到节点98,向下转到99,然后返回到98,它会将'(result100)追加到'(result99)并且前置result98
  • 然后它将返回96,检查97,返回96,然后将'(result98 result100 result99)追加到'(result97) and prepend result96`,
  • ...
  • 然后它将返回0,检查1,返回0,然后将'(… many results here …)追加到'(result1),并在result0前加append 1}}。

由于n必须复制前缀中的所有元素,因此将长度m的列表附加到长度为n的列表中将花费O(n)次操作(第二次) list只是指向,并且不需要复制,因为它形成的尾部与原始的整个第二个列表相同),即它需要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 次操作。对append的调用顺序为:

O(n²)

所以总成本是1 + 3 + 5 + ... + N,其中N是节点总数,大致相当于N²乘​​常数因子,因此O(N²),这意味着节点数量很多,会很慢。有关此at wikipedia的更多信息。

为了避免(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))) 成本,您可以使用累加器:每个步骤都会将一个项目添加到累加器,然后传递给它。这意味着在处理节点时,必须将当前累加器赋予其第一个邻居,返回整个子树的修改累加器,将其传递给第二个邻居,返回一个新的修改累加器,将其传递给第三邻居等

为此,您可以在列表上编写自己的递归函数,将最新的累加器和列表作为参数,并使用修改后的累加器(通过处理整个子树获得)进行递归调用,以及列表的尾部。

您还可以使用对此模式进行抽象的foldl函数。然后,节点处理函数将遵循以下结构:

O(N)

由于这永远不会附加列表,而只是在每个步骤前面添加一个结果,因此它具有neighbours-of的复杂度(不计算#t函数的成本,请参阅关于哈希表的说明并在下面设置)。数据流有点复杂,遗憾的是,不可变数据结构没有更好的选择。

在您的特定情况下,由于您返回一个布尔值,您也可以简单地使用ormap,如果lambda返回#t,它将遍历列表并返回#f对于任何元素。

这是否具有map之类的O(N²)复杂度?想想它为什么没有。

作为旁注,您应该使用#t0代替VISITED#f,或'visited'(引用visited符号if,在#f语句中被视为真,与{{1}}以外的所有值一样。

为了获得更好的性能,请使用哈希表和集合来存储边缘,因为如果边缘很多,查找列表中的边缘会花费很多。

最后,我建议将一个代码块的右括号放在最后一行的末尾,而不是将它们单独放在它们的行上。这是Scheme,Racket和大多数其他Lisp变体的常见做法,并使代码更具可读性。

答案 1 :(得分:1)

“但当它到达[(成员条件时,它会停止而不返回任何内容,即使条件为真。”

首先检查列表是否由

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

是空的。然后检查列表是否为非空,以及Y是否在该列表中。然后有一个else情况,其中列表非空,但Y不在直接邻居列表中。