搜索k-d树中的项目

时间:2017-02-17 12:34:42

标签: search tree lisp common-lisp

我正在用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。为什么这个函数没有返回节点?

3 个答案:

答案 0 :(得分:4)

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

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

(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

答案 1 :(得分:2)

您可以稍微简化一下,并在找到项目后停止搜索,方法是为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的答案结合起来。例如,您可以为:around定义kdnode方法:

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

请注意,此示例仅用于演示方法。它使代码变得有点过于聪明&#34 ;;我可能会在实践中明确地实现throw / catch行为。

答案 2 :(得分:0)

该函数继续测试节点,因为它是递归的。如果向左下方(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))))