【问题标题】:Searching an item in a k-d tree在 k-d 树中搜索项目
【发布时间】:2017-07-06 23:47:00
【问题描述】:

我正在用 Lisp 开发一个 k-d 树。我正在编写一个允许我在 k-d 树中搜索节点的函数。该函数定义如下:

(defmethod find-node ((kdt kdtree) target &key (key #'value) (test #'equal))
  (unless (null (root kdt))
    (find-node (root kdt) target :key key :test test)))

(defmethod find-node ((node kdnode) target &key (key #'value) (test #'equal))
  (format t "Testing node ~a~%" (value node))
  (format t "Result is ~a~%" (funcall test (funcall key node) target))
  (if (funcall test (funcall key node) target)
      node
      (progn
        (unless (null (left node))
          (find-node (left node) target :key key :test test))
        (unless (null (right node))
          (find-node (right node) target :key key :test test)))))

我用以下数据构建了一棵树:'((2 3) (5 4) (9 6) (4 7) (8 1) (7 2))。所以现在,我正在使用这个函数来查找节点'(2 3)

(find-node kdt '(2 3))

使用format 语句,我得到以下输出:

Testing node (7 2)
Result is NIL
Testing node (5 4)
Result is NIL
Testing node (2 3)
Result is T
Testing node (4 7)
Result is NIL
Testing node (9 6)
Result is NIL
Testing node (8 1)
Result is NIL
NIL

因此,如您所见,由于测试结果为T,因此找到了该节点,但是继续搜索,结果为NIL。为什么这个函数不返回节点?

【问题讨论】:

    标签: search tree lisp common-lisp


    【解决方案1】:

    通过为nil 节点定义方法并使用or,您可以稍微简化一下,一旦找到项目就停止搜索:

    (defmethod find-node ((empty null) target &key &allow-other-keys)
      nil)
    
    (defmethod find-node ((node kdnode) target &key (key #'value) (test #'equal))
      (if (funcall test (funcall key node) target)
          node
          (or (find-node (left node) target :key key :test test)
              (find-node (right node) target :key key :test test))))
    

    当然,您可以将其与 Rainer Joswig 的回答结合使用。例如,您可以为kdnode 定义一个:around 方法:

    (defvar *searching* nil)
    
    (defmethod find-node :around ((x kdnode) target &key &allow-other-keys)
      (if *searching*
          (let ((result (call-next-method)))
            (when result
              (throw 'found result)))
          (let ((*searching* t))
            (catch 'found
              (call-next-method)))))
    

    您也可以在代码中显式放置catch 块。如果您确定您从未对 kdnode 发起搜索,而是始终在 kdtree 实例上发起搜索,那么您可以将 catch 放在 kdtree 周围,并去掉特殊变量 *searching*

    请注意,此示例仅用于演示方法。它使代码有点“太聪明了”;我可能会在实践中明确地实现 throw/catch 行为。

    【讨论】:

      【解决方案2】:

      当您找到某些内容时,您可能希望直接返回结果。

      使用以下示例改进您的代码:

      (defun find-it (item list)
        (labels ((find-it-aux (item list)
                   (when list
                     (if (eql item (first list))
                         (return-from find-it (first list))
                       (find-it-aux item (rest list))))))
          (find-it-aux item list)))
      
      CL-USER 89 > (find-it 1 '(2 3 1 5))
      1
      
      CL-USER 90 > (find-it 1 '(2 3 5))
      NIL
      

      (defun find-it (item list)
        (catch 'found-it
          (find-it-aux item list)))
      
      (defun find-it-aux (item list)
        (when list
          (if (eql item (first list))
              (throw 'found-it (first list))
            (find-it-aux item (rest list)))))
      
      
      CL-USER 91 > (find-it 1 '(2 3 1 5))
      1
      
      CL-USER 92 > (find-it 1 '(2 3 5))
      NIL
      

      【讨论】:

      • 我更喜欢第一种解决方案:)。
      【解决方案3】:

      该函数继续测试节点,因为它是递归的。如果您向左下降 (find-node (left node) ...),则右侧分支中的搜索将被放入堆栈 (find-node (right node) ...)。所以,被测试的节点都是因为递归。解决这个问题的一种方法是像这样重写函数:

      (defmethod find-node ((node kdnode) target &key (key #'value) (test #'equal))
        (let ((left (unless (null (left node))
                      (find-node (left node) target :key key :test test)))
              (right (unless (null (right node))
                       (find-node (right node) target :key key :test test)))
              (this (funcall test (funcall key node) target)))
          (if this
              node
              (if left
                  left
                  right))))
      

      【讨论】:

      • 这是遍历整个树,而不是一找到目标就返回。
      • @jkiiski 这只是一种确实会遍历整个树的解决方案。当然,我们总是欢迎更好的解决方案。
      猜你喜欢
      • 1970-01-01
      • 2012-11-10
      • 1970-01-01
      • 2011-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-23
      相关资源
      最近更新 更多