KD-Tree“列表中位数”构造

时间:2012-11-22 16:09:01

标签: java algorithm data-structures kdtree

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

来自维基百科:

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

enter image description here

来自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;
        }
    }

2 个答案:

答案 0 :(得分:2)

问题确实与坐标相等有关,而且是由于您将节点分成lessmore部分的方式。由于你有中位数索引,为什么不使用索引进行拆分而不是检查坐标?只需更改第116行createNode中的条件

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

if (i<medianIndex) {
顺便说一下:有更高效的算法可以将列表分成较低的,中位的,高于排序的。 (下部和上部不需要排序!参见例如C ++ stdlib中std::nth_element的实现 - 抱歉,我对Java编程非常重视)

答案 1 :(得分:0)

我认为此时的基本问题是:你究竟想用KD树做些什么?

  • 如果您只想使用X和Y距离找到最近的点,那么您拥有的算法非常精细 - 您将找到与示例相同的XY距离中的四个点中的至少一个。
  • 如果要在XY距离中找到所有最近的点,那么仍然保持KD树构建函数相同,但只需更改所有'&lt;'查找函数中的运算符为'&lt; ='。如果您在查询点处找到了一个KD树点,您仍然需要下降该树的任意子项,直到找到一个叶子。然后像往常一样沿着KD树上去树,如果它可能与你到目前为止找到的最短距离匹配,那么它总是沿着兄弟树下行。
  • 如果要使用涉及X,Y和Z坐标的距离,则需要使树成为三维KD树,其中X,Y和Z层交替(或可能)用一些聪明的方案来选择下一个细分的维度。