【问题标题】:KD-Tree "median of list" constructionKD-Tree“列表中位数”构造
【发布时间】:2013-11-22 22:46:13
【问题描述】:

我使用"median of list" 算法在Java 中编写了一个KD-Tree,用于构建一个更平衡的树。使用 wiki 提供的数据时,它似乎工作正常,请注意,wikipedia 示例仅使用 X、Y 值,因此它不评估 Z 深度。

来自维基百科:

point_list = [(2,3), (5,4), (9,6), (4,7), (8,1), (7,2)]

来自my java program

depth=0 id=(7.0, 2.0, 0.0)
├── [left] depth=1 id=(5.0, 4.0, 0.0)
│   ├── [left] depth=2 id=(2.0, 3.0, 0.0)
│   └── [right] depth=2 id=(4.0, 7.0, 0.0)
└── [right] depth=1 id=(9.0, 6.0, 0.0)
    └── [left] depth=2 id=(8.0, 1.0, 0.0)

但是当我对这些数据使用“列表中位数”方法时,它似乎无法正常工作。

point list = [(1,0,-1), (1,0,-2), (1,0,1), (1,0,2)]

我得到一棵这样的树:

depth=0 id=(1.0, 0.0, 1.0)
├── [left] depth=1 id=(1.0, 0.0, -2.0)
│   └── [left] depth=2 id=(1.0, 0.0, -1.0)
└── [right] depth=1 id=(1.0, 0.0, 2.0)

这看起来不正确,因为 (1.0, 0.0, 2.0) 在 (1.0, 0.0, 1.0) 的右侧,但它们本质上是相等的,因为它们的 Y 值相等。此外, (1.0, 0.0, -1.0) 在 (1.0, 0.0, -2.0) 的左侧,它应该在右侧,因为它的 Z 值更大。

我认为问题源于相同的 X 和 Y 值,并且只有可变的 Z 值,因此列表的中位数并没有真正准确地拆分列表。

... wiki 的 python 代码之后的原始代码...

private static KdNode createNode(List<XYZPoint> list, int k, int depth) {
    if (list == null || list.size() == 0) return null;

    int axis = depth % k;
    if (axis == X_AXIS) Collections.sort(list, X_COMPARATOR);
    else if (axis == Y_AXIS) Collections.sort(list, Y_COMPARATOR);
    else Collections.sort(list, Z_COMPARATOR);

    KdNode node = null;
    if (list.size() > 0) {
        int mediaIndex = list.size() / 2;
        node = new KdNode(k, depth, list.get(mediaIndex));
        if ((mediaIndex - 1) >= 0) {
            List<XYZPoint> less = list.subList(0, mediaIndex);
            if (less.size() > 0) {
                node.lesser = createNode(less, k, depth + 1);
                node.lesser.parent = node;
            }
        }
        if ((mediaIndex + 1) <= (list.size() - 1)) {
            List<XYZPoint> more = list.subList(mediaIndex + 1, list.size());
            if (more.size() > 0) {
                node.greater = createNode(more, k, depth + 1);
                node.greater.parent = node;
            }
        }
    }

    return node;
}

...基于我的评论的新代码...

private static KdNode createNode(List<XYZPoint> list, int k, int depth) {
    if (list == null || list.size() == 0) return null;

    int axis = depth % k;
    if (axis == X_AXIS) Collections.sort(list, X_COMPARATOR);
    else if (axis == Y_AXIS) Collections.sort(list, Y_COMPARATOR);
    else Collections.sort(list, Z_COMPARATOR);

    KdNode node = null;
    if (list.size() > 0) {
        int medianIndex = list.size() / 2;
        node = new KdNode(k, depth, list.get(medianIndex));
        List<XYZPoint> less = new ArrayList<XYZPoint>(list.size()-1);
        List<XYZPoint> more = new ArrayList<XYZPoint>(list.size()-1);
        //Process list to see where each non-median point lies
        for (int i=0; i<list.size(); i++) {
            if (i==medianIndex) continue;
            XYZPoint p = list.get(i);
            if (KdNode.compareTo(depth, k, p, node.id)<=0) {
                less.add(p);
            } else {
                more.add(p);
            }
        }
        if (less.size() > 0) {
            node.lesser = createNode(less, k, depth + 1);
            node.lesser.parent = node;
        }
        if (more.size() > 0) {
            node.greater = createNode(more, k, depth + 1);
            node.greater.parent = node;
        }
    }

【问题讨论】:

  • 似乎在我选择了中位数之后,我必须处理列表以查看每个点相对于中位数的位置。这不会使创建 KD-Tree 成为一个 n*((n log n) + (n)) 过程吗? (n log n) 对列表进行排序, (n) 查看每个元素相对于中位数的位置。

标签: java algorithm data-structures kdtree


【解决方案1】:

问题确实与坐标相等有关,并且源于您如何将节点拆分为lessmore 部分。既然你有中位数索引,为什么不使用索引来分割而不是检查坐标呢?只需将第 116 行 createNode 中的条件从

if (KdNode.compareTo(depth, k, p, node.id)<=0) {

if (i<medianIndex) {

顺便说一句:与排序相比,有更有效的算法可以将列表划分为下、中、上。 (上下部分不需要排序!参见例如 C++ 标准库中 std::nth_element 的实现 - 抱歉,我非常喜欢 Java 编程)

【讨论】:

  • 您建议的方法是我的代码最初的样子。假设数据是 (1,0,-2), (1,0,-1), (1,0,0), (1,0,1), (1,0,2),如果我对它进行排序根据 X(第一个)值并寻找中位数,我将得到 (1,0,0) 点。您不能假设具有较大索引 [(1,0,1), (1,0,2)] 的点确实位于中位数的右侧。当所有点在同一轴上具有相同的值时,就会出现问题。
  • 另外,感谢 nth_element 的建议。似乎 Java 缺少“快速选择”类型的算法,但也许我会自己动手。
  • 将坐标相同的点也放在more 一侧有什么问题?您只需要相应地调整搜索方法。我在 C++ 中有一个非常有效的 kd-tree 实现,它完美平衡,即less.size()-more.size() == 0 or 1 始终成立。这显然只有在您允许在两侧具有相同坐标的点时才能实现(lessmore);否则你要求太多了。
【解决方案2】:

我认为此时的基本问题是:您究竟想用 KD-tree 做什么?

  • 如果您只想仅使用 X 和 Y 距离找到最近的点,那么您所拥有的算法非常好 - 您将找到与您的示例具有相等 XY 距离的四个点中的至少一个。
  • 如果您想在 XY 距离中找到 所有 个最近点,则仍然保持 KD-tree 构建函数相同,但只需将查找函数中的所有 '
  • 如果要使用涉及 X、Y 和 Z 坐标的距离,则需要使树成为 3 维 KD 树,其中 X、Y 和 Z 层交替(或可能有一些巧妙的方案来选择接下来要细分的维度)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-22
    • 2011-02-25
    • 2014-01-27
    • 2013-12-29
    • 1970-01-01
    • 2012-12-31
    • 1970-01-01
    相关资源
    最近更新 更多