【问题标题】:KD Tree - Nearest Neighbor AlgorithmKD 树 - 最近邻算法
【发布时间】:2012-06-27 20:55:12
【问题描述】:

我不太了解wikipedia 的 O(log n) 最近邻算法。

  1. 算法展开树的递归,在每个节点执行以下步骤:
    1. ...
    2. 算法检查分割平面的另一侧是否有任何点比当前最佳点更靠近搜索点。在概念上,这是通过将分割超平面与围绕搜索点的超球面相交来完成的,该超球面的半径等于当前最近距离。由于超平面都是轴对齐的,因此这是一个简单的比较,以查看搜索点和当前节点的分割坐标之间的差异是否小于搜索点到当前最佳点的距离(整体坐标)。
      1. 如果超球面穿过平面,则平面的另一侧可能有更近的点,因此算法必须从当前节点向下移动树的另一个分支以寻找更近的点,遵循与以下相同的递归过程整个搜索。
      2. 如果超球面不与分割平面相交,则算法将继续向上遍历树,并消除该节点另一侧的整个分支。

让我困惑的是 3.2,我看到了this 的问题。我正在用 Java 实现算法,但不确定我是否正确。

//Search children branches, if axis aligned distance is less than current distance
if (node.lesser!=null) {
    KdNode lesser = node.lesser;
    int axis = lesser.depth % lesser.k;
    double axisAlignedDistance = Double.MAX_VALUE;
    if (axis==X_AXIS) axisAlignedDistance = Math.abs(lastNode.id.x-lesser.id.x);
    if (axis==Y_AXIS) axisAlignedDistance = Math.abs(lastNode.id.y-lesser.id.y);
    else if (axis==Z_AXIS) axisAlignedDistance = Math.abs(lastNode.id.z-lesser.id.z);

    //Continue down lesser branch
    if (axisAlignedDistance<=lastDistance && !set.contains(lesser)) {
        searchNode(value,lesser,set,K);
    }
}
if (node.greater!=null) {
    KdNode greater = node.greater;
    int axis = greater.depth % greater.k;
    double axisAlignedDistance = Double.MAX_VALUE;
    if (axis==X_AXIS) axisAlignedDistance = Math.abs(lastNode.id.x-greater.id.x);
    if (axis==Y_AXIS) axisAlignedDistance = Math.abs(lastNode.id.y-greater.id.y);
    else if (axis==Z_AXIS)axisAlignedDistance = Math.abs(lastNode.id.z-greater.id.z);

    //Continue down greater branch
    if (axisAlignedDistance<=lastDistance && !set.contains(greater)) {
        searchNode(value,greater,set,K);
    }
}

上面的代码是否完成了算法的 3.2 方面?特别是我填充“axisAlignedDistance”变量的位置。

您可以找到 KDTree here 的完整源代码。

感谢任何帮助/指点。

【问题讨论】:

  • 你测试过代码吗?为什么要问一个关于你认为会做它应该做的代码的问题?如果不自己测试代码,我会说它看起来应该可以完成这项工作。而且,如果可能的话,我会避免 while(true) 循环。特别是因为在你的情况下,循环之后它总是 current = node.
  • 最好先测试一下代码,然后再问一些关于理解算法的问题。如果它不起作用,那么您可以发布输出,我们可以从那里开始。
  • @kutschkem 我已经测试了代码,它返回了正确的结果,但这并不意味着它正确地实现了算法。我的意思是 O(n) 算法会返回正确的结果,但不会是正确的。我会更新问题以帮助缩小问题范围。
  • @VanDarg 我已经测试了代码,我也会更新问题以帮助缩小实际问题的范围。
  • @Justin:听起来不错。指定您正在寻找的 Big-O 界限也是一个好主意(如果 O(N) 不够)

标签: java algorithm data-structures nearest-neighbor kdtree


【解决方案1】:

是的,Wikipedia 上关于 KD 树中 NN(最近邻)搜索的描述有点难以理解。 NN KD Tree 搜索中的很多热门 Google 搜索结果都是完全错误的,这无济于事!

请参阅https://stackoverflow.com/a/37107030/591720 以获得正确的解释/算法。

【讨论】:

    【解决方案2】:

    我正在添加这个,希望它可以帮助其他搜索过同样问题的人。我最终用以下代码解决了 3.2。虽然,我不确定它是否 100% 正确。它通过了我提出的所有测试。上面的原始代码在许多相同的测试用例上都失败了。

    使用点、线、矩形和立方体对象的更明确的解决方案:

    int axis = node.depth % node.k;
    KdNode lesser = node.lesser;
    KdNode greater = node.greater;
    
    //Search children branches, if axis aligned distance is less than current distance
    if (lesser!=null && !examined.contains(lesser)) {
        examined.add(lesser);
    
        boolean lineIntersectsRect = false;
        Line line = null;
        Cube cube = null;
        if (axis==X_AXIS) {
            line = new Line(new Point(value.x-lastDistance,value.y,value.z), new Point(value.x+lastDistance,value.y,value.z));
            Point tul = new Point(Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tur = new Point(node.id.x,Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tlr = new Point(node.id.x,Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tll = new Point(Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Rectangle trect = new Rectangle(tul,tur,tlr,tll);
            Point bul = new Point(Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point bur = new Point(node.id.x,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point blr = new Point(node.id.x,Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point bll = new Point(Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY);
            Rectangle brect = new Rectangle(bul,bur,blr,bll);
            cube = new Cube(trect,brect);
            lineIntersectsRect = cube.inserects(line);
        } else if (axis==Y_AXIS) {
            line = new Line(new Point(value.x,value.y-lastDistance,value.z), new Point(value.x,value.y+lastDistance,value.z));
            Point tul = new Point(Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tur = new Point(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tlr = new Point(Double.POSITIVE_INFINITY,node.id.y,Double.NEGATIVE_INFINITY);
            Point tll = new Point(Double.NEGATIVE_INFINITY,node.id.y,Double.NEGATIVE_INFINITY);
            Rectangle trect = new Rectangle(tul,tur,tlr,tll);
            Point bul = new Point(Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point bur = new Point(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point blr = new Point(Double.POSITIVE_INFINITY,node.id.y,Double.POSITIVE_INFINITY);
            Point bll = new Point(Double.NEGATIVE_INFINITY,node.id.y,Double.POSITIVE_INFINITY);
            Rectangle brect = new Rectangle(bul,bur,blr,bll);
            cube = new Cube(trect,brect);
            lineIntersectsRect = cube.inserects(line);
        } else {
            line = new Line(new Point(value.x,value.y,value.z-lastDistance), new Point(value.x,value.y,value.z+lastDistance));
            Point tul = new Point(Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tur = new Point(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tlr = new Point(Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tll = new Point(Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Rectangle trect = new Rectangle(tul,tur,tlr,tll);
            Point bul = new Point(Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY,node.id.z);
            Point bur = new Point(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,node.id.z);
            Point blr = new Point(Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY,node.id.z);
            Point bll = new Point(Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY,node.id.z);
            Rectangle brect = new Rectangle(bul,bur,blr,bll);
            cube = new Cube(trect,brect);
            lineIntersectsRect = cube.inserects(line);
        }
    
        //Continue down lesser branch
        if (lineIntersectsRect) {
            searchNode(value,lesser,K,results,examined);
        }
    }
    if (greater!=null && !examined.contains(greater)) {
        examined.add(greater);
    
        boolean lineIntersectsRect = false;
        Line line = null;
        Cube cube = null;
        if (axis==X_AXIS) {
            line = new Line(new Point(value.x-lastDistance,value.y,value.z), new Point(value.x+lastDistance,value.y,value.z));
            Point tul = new Point(node.id.x,Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tur = new Point(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tlr = new Point(Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tll = new Point(node.id.x,Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Rectangle trect = new Rectangle(tul,tur,tlr,tll);
            Point bul = new Point(node.id.x,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point bur = new Point(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point blr = new Point(Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point bll = new Point(node.id.x,Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY);
            Rectangle brect = new Rectangle(bul,bur,blr,bll);
            cube = new Cube(trect,brect);
            lineIntersectsRect = cube.inserects(line);
        } else if (axis==Y_AXIS) {
            line = new Line(new Point(value.x,value.y-lastDistance,value.z), new Point(value.x,value.y+lastDistance,value.z));
            Point tul = new Point(Double.NEGATIVE_INFINITY,node.id.y,Double.NEGATIVE_INFINITY);
            Point tur = new Point(Double.POSITIVE_INFINITY,node.id.y,Double.NEGATIVE_INFINITY);
            Point tlr = new Point(Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Point tll = new Point(Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY);
            Rectangle trect = new Rectangle(tul,tur,tlr,tll);
            Point bul = new Point(Double.NEGATIVE_INFINITY,node.id.y,Double.POSITIVE_INFINITY);
            Point bur = new Point(Double.POSITIVE_INFINITY,node.id.y,Double.POSITIVE_INFINITY);
            Point blr = new Point(Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point bll = new Point(Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY);
            Rectangle brect = new Rectangle(bul,bur,blr,bll);
            cube = new Cube(trect,brect);
            lineIntersectsRect = cube.inserects(line);
        } else {
            line = new Line(new Point(value.x,value.y,value.z-lastDistance), new Point(value.x,value.y,value.z+lastDistance));
            Point tul = new Point(Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY,node.id.z);
            Point tur = new Point(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,node.id.z);
            Point tlr = new Point(Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY,node.id.z);
            Point tll = new Point(Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY,node.id.z);
            Rectangle trect = new Rectangle(tul,tur,tlr,tll);
            Point bul = new Point(Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point bur = new Point(Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point blr = new Point(Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY);
            Point bll = new Point(Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY);
            Rectangle brect = new Rectangle(bul,bur,blr,bll);
            cube = new Cube(trect,brect);
            lineIntersectsRect = cube.inserects(line);
        }
    
        //Continue down greater branch
        if (lineIntersectsRect) {
            searchNode(value,greater,K,results,examined);
        }
    }
    

    我认为this 更简单的代码也应该可以工作,它已经通过了与上述代码相同的所有测试。

    int axis = node.depth % node.k;
    KdNode lesser = node.lesser;
    KdNode greater = node.greater;
    
    //Search children branches, if axis aligned distance is less than current distance
    if (lesser!=null && !examined.contains(lesser)) {
        examined.add(lesser);
    
        double p1 = Double.MIN_VALUE;
        double p2 = Double.MIN_VALUE;
        if (axis==X_AXIS) {
            p1 = node.id.x;
            p2 = value.x-lastDistance;
        } else if (axis==Y_AXIS) {
            p1 = node.id.y;
            p2 = value.y-lastDistance;
        } else {
            p1 = node.id.z;
            p2 = value.z-lastDistance;
        }
        boolean lineIntersectsCube = ((p2<=p1)?true:false);
    
        //Continue down lesser branch
        if (lineIntersectsCube) {
            searchNode(value,lesser,K,results,examined);
        }
    }
    if (greater!=null && !examined.contains(greater)) {
        examined.add(greater);
    
        double p1 = Double.MIN_VALUE;
        double p2 = Double.MIN_VALUE;
        if (axis==X_AXIS) {
            p1 = node.id.x;
            p2 = value.x+lastDistance;
        } else if (axis==Y_AXIS) {
            p1 = node.id.y;
            p2 = value.y+lastDistance;
        } else {
            p1 = node.id.z;
            p2 = value.z+lastDistance;
        }
        boolean lineIntersectsCube = ((p2>=p1)?true:false);
    
        //Continue down greater branch
        if (lineIntersectsCube) {
            searchNode(value,greater,K,results,examined);
        }
    }
    

    【讨论】:

      【解决方案3】:

      从 3 中注意以下几点很重要:

      算法展开树的递归,执行 在每个节点执行以下步骤:"

      您的实现似乎只是进行递归调用,而不是在展开递归时进行任何操作。

      尽管您似乎正确地检测到了交叉点,但您并没有在递归退出(展开)时执行这些步骤。进行递归调用后,执行了 0 个步骤(包括 3.2)。

      3.2 状态:“如果超球面不与分裂平面相交,那么 算法继续向上走,整个分支都在 该节点的另一侧被消除”

      这是什么意思?到达基本情况(算法到达叶节点)后,递归开始展开。在展开每一级递归后,算法会检查子树是否可能包含更近的邻居。如果可以,则在该子树上进行另一个递归调用,否则算法继续展开(沿着树向上走)。

      你应该从这个开始:

      "从根节点开始,算法顺着树向下移动 递归地,就像如果搜索点是 正在插入”

      然后开始考虑在解开递归期间要采取哪些步骤以及何时采取。

      这是一个具有挑战性的算法。如果你已经完成了这个数据结构的 insert() 方法,你可以把它作为这个算法的开始框架。

      编辑:

      //Used to not re-examine nodes
      Set<KdNode> examined = new HashSet<KdNode>();
      

      在您可以标记为“已访问”的每个节点中简单地放置一个标志可能会更容易、更快,并且会使用更少的内存。理想情况下,HashSets 有一个恒定的时间查找,但实际上没有办法保证这一点。这样,您不必在每次访问时搜索一组节点。在搜索树之前,您可以在 O(N) 时间将所有这些值初始化为 false。

      【讨论】:

      • 展开应该发生在“nearestNeighbourSearch()”方法调用中,该方法调用最初在叶节点上调用“searchNode()”。在叶子节点上的“searchNode()”完成后,它返回到“nearestNeighbourSearch()”方法,该方法移动到叶子的父节点并在其上调用“searchNode()”。
      • @Justin:我现在明白了。但是这里有两个单独的方法遍历树。您需要在每个级别的递归展开后执行这些检查,以确保 O(log(N)) 的运行时间。
      • 关于被检查的集合。当我让算法正常工作时,我打算这样做。
      猜你喜欢
      • 2015-03-17
      • 2018-03-12
      • 2015-02-03
      • 2011-05-24
      • 1970-01-01
      • 2014-01-17
      • 2012-11-10
      • 1970-01-01
      • 2012-06-11
      相关资源
      最近更新 更多