【问题标题】:Algorithm balanced K-D tree with O(kn log n)O(kn log n) 的算法平衡 K-D 树
【发布时间】:2019-04-21 07:03:43
【问题描述】:

我尝试用 O(kn log n) 实现平衡的 K-D 树,我使用预排序的 K 数组(每个索引的排序数组)来获得 O(kn log n),并使用中值来获得平衡树。

我面临的问题是,主要是某个级别的中值,例如 x 轴的中值,可能会在另一个后续级别再次选择,例如 y 轴。

我尝试通过使用选择的 x 值作为枢轴将 y 排序数组分成两个数组来解决这个问题,但这种方式不会产生平衡树。

知道如何获得 O(kn log n) 的 K-D 平衡树吗?

编辑

引用自维基 https://en.wikipedia.org/wiki/K-d_tree

构建平衡 k-d 树的替代算法 构建树之前的数据。然后他们维持秩序 在树构建期间进行预分类,从而消除昂贵的步骤 找到每个细分级别的中位数。两个这样的 算法构建一个平衡的 k-d 树来对三角形进行排序,以便 提高三维光线追踪的执行时间 计算机图形学。这些算法先对 n 个三角形进行预排序 构建 k-d 树,然后在 O(n log n) 时间内构建树 最好的情况。[5][6]一种构建平衡k-d树进行排序的算法 点的最坏情况复杂度为 O(kn log n)。[7]这个算法 使用 O(n log n) 排序在 k 个维度中的每个维度中对 n 个点进行预排序 例如在构建树之前的 Heapsort 或 Mergesort。那么它 在树构建期间保持这 k 个预排序的顺序,并且 从而避免在每个细分级别找到中位数。

谁能提供上述算法?

编辑

想出了一个办法,但是如果中位数的特定轴有任何重复值,它就不起作用了。

例如

x1 = [ (0, 7), (1, 3), (3, 0), (3, 1), (6, 2) ] y1 = [ (3, 0), (3, 1) , (6, 2), (1, 3), (0, 7) ]

x 轴的中位数为 3。 因此,当我们想要拆分数组 y11 和 y12 时,我们必须使用 > 和

如果特定轴上的中位数 a 重复,则无法保证其中一个是正确的

考虑x轴上的分区,完成上述第一步分区示例后,x1数组没有问题:

median=(3,0)
The pivot = 3 // is it's the median of x axis
y11[],y12[] 
for(i = 0 ; i < x1.size;i++)
  if(y1[i].getX()<pivot)
    y11.add(y1[i])
  else 
    if(y1[i].getX()>pivot)
     y12.add(y1[i])

这将导致 y11 = [(2 ,1) , (1, 3), (0, 7) ] y12 = [ (6,2) ]

知道如何处理这种情况吗? 或者有没有其他的预排序kd-tree预排序算法O(kn log n)?

标签: arrays algorithm tree kdtree


【解决方案1】:

详细说明我的评论(可能还有Anony-Mousse's answer):

在构建 KD-trees 中预排序的关键思想是在拆分期间保持顺序。开销看起来相当高,使用 re-sorting(和 k-select)的比较基准似乎是有序的。
一些证明原理的Java源代码:

package net.*.coder.greybeard.sandbox;

import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;

/** finger exercise pre-sorting & split for KD-tree construction
 *  (re. https://stackoverflow.com/q/35225509/3789665) */
public class KDPreSort {
 /** K-dimensional key, dimensions fixed
  *   by number of coordinates in construction */
    static class KKey {
        public static KKey[] NONE = {};
        final Comparable[]coordinates;
        public KKey(Comparable ...coordinates) {
            this.coordinates = coordinates;
        }
    /** @return {@code Comparator<KKey>} for coordinate {@code n}*/
        static Comparator<KKey> comparator(int n) { // could be cached
            return new Comparator<KDPreSort.KKey>() { @Override
                    public int compare(KKey l, KKey r) {
                        return l.coordinates[n]
                            .compareTo(r.coordinates[n]);
                    }
                };
        }
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder(
                Arrays.deepToString(coordinates));
            sb.setCharAt(0, '(');
            sb.setCharAt(sb.length()-1, ')');
            return sb.toString();
        }
    }

 // static boolean trimLists = true; // introduced when ArrayList was used in interface

/** @return two arrays of {@code KKey}s: comparing smaller than
 *    or equal to {@code pivot} (according to {@code comp)},
 *    and greater than pivot -
 *    in the same order as in {@code keys}. */
    static KKey[][] split(KKey[] keys, KKey pivot, Comparator<KKey> comp) {
        int length = keys.length;
        ArrayList<KKey>
            se = new ArrayList<>(length),
            g = new ArrayList<>(length);
        for (KKey k: keys) {
        // pick List to add to
            List<KKey>d = comp.compare(k, pivot) <= 0 ? se : g;
            d.add(k);
        }
//      if (trimLists) { se.trimToSize(); g.trimToSize(); }
        return new KKey[][] { se.toArray(KKey.NONE), g.toArray(KKey.NONE) };
    }
 /** @return two arrays of <em>k</em> arrays of {@code KKey}s:
  *  comparing smaller than or equal to {@code pivot}
  *   (according to {@code comp)}, and greater than pivot,
  *  in the same order as in {@code keysByCoordinate}. */
    static KKey[][][]
        splits(KKey[][] keysByCoordinate, KKey pivot, Comparator<KKey> comp) {
        final int length = keysByCoordinate.length;
        KKey[][]
            se = new KKey[length][],
            g = new KKey[length][],
            splits;
        for (int i = 0 ; i < length ; i++) {
            splits = split(keysByCoordinate[i], pivot, comp);
            se[i] = splits[0];
            g[i] = splits[1];
        }
        return new KKey[][][] { se, g };
    }
 // demo
    public static void main(String[] args) {
    // from https://stackoverflow.com/q/17021379/3789665
        Integer [][]coPairs = {// {0, 7}, {1, 3}, {3, 0}, {3, 1}, {6, 2},
                {12, 21}, {13, 27}, {19, 5}, {39, 5}, {49, 63}, {43, 45}, {41, 22}, {27, 7}, {20, 12}, {32, 11}, {24, 56},
            };
        KKey[] someKeys = new KKey[coPairs.length];
        for (int i = 0; i < coPairs.length; i++) {
            someKeys[i] = new KKey(coPairs[i]);
        }
    //presort
        Arrays.sort(someKeys, KKey.comparator(0));
        List<KKey> x = new ArrayList<>(Arrays.asList(someKeys));
        System.out.println("by x: " + x);
        KKey pivot = someKeys[someKeys.length/2];
        Arrays.sort(someKeys, KKey.comparator(1));
        System.out.println("by y: " + Arrays.deepToString(someKeys));
    // split by x
        KKey[][] allOrdered = new KKey[][] { x.toArray(KKey.NONE), someKeys },
            xSplits[] = splits(allOrdered, pivot, KKey.comparator(0));
        for (KKey[][] c: xSplits)
            System.out.println("split by x of " + pivot + ": "
                + Arrays.deepToString(c));
    // split "higher x" by y
        pivot = xSplits[1][1][xSplits[1][1].length/2];
        KKey[][] ySplits[] = splits(xSplits[1], pivot, KKey.comparator(1));
        for (KKey[][] c: ySplits)
            System.out.println("split by y of " + pivot + ": "
                + Arrays.deepToString(c));
    }
}

(在没有投入太多精力的情况下,没有在 SE 上找到合适的答案/实现。输出的示例无法令人信服,对于较长的示例,我不得不重新格式化以相信它。
代码看起来很丑,很可能是因为它是:如果愿意重新阅读licence of code posted on SE,请访问Code Review。) (考虑有投票、接受和奖励赏金,并重新访问 Anony-Mousse 的答案。)

【讨论】:

    【解决方案2】:

    拆分数据时,需要保留排序顺序。

    例如使用数据(x,y)我们构建

    x1 = [ (0, 7), (1, 3), (3, 0), (4, 2), (6, 1) ]
    y1 = [ (3, 0), (6, 1), (3, 2), (1, 3), (0, 7) ]
    

    如果我们现在在 x 处拆分,我们需要根据x=3,y=0 处的记录过滤两个集合。

    即拆分两个列表,删除(3,0),所有带有x&lt;3 的项目都进入第一个列表,所有带有x&gt;3 的项目进入第二个列表(顺序不变):

    x1 -> filter to  x11 = [ (0, 7), (1, 3) ]  x12 = [ (4, 2), (6, 1) ]
    y1 -> filter to  y11 = [ (1, 3), (0, 7) ]  y12 = [ (6, 1), (4, 2) ]
    

    重点是通过 x 值过滤每个排序列表,同时 保持 排序顺序(所以这是在每个 O(log n) 级别中的 O(n*k) 中)。如果您只使用 x1,并从 x1 重构 y11 和 y12,那么您将需要再次排序。必要时,这与您按 x 排序一次,按 y 排序一次是一样的。除了我们没有再次排序,只是选择。

    我认为这在实践中并没有好得多。排序比额外的内存更便宜。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-19
      • 2011-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多