【问题标题】:n-D Trees - calculating the coordinates of hypercubesn-D Trees - 计算超立方体的坐标
【发布时间】:2016-06-21 14:22:59
【问题描述】:

我正在尝试实现一个通用的 n 维树来保存 n 维数据。通过 n 维数据,我的意思是在我的情况下具有 6-7 个坐标的数据点。这是树节点(一种复合数据类型)和树类:

#data = data points (i.e. [x,y,z,k,m,n])
#hypercube = set of tuples; coordinates [(x0,x1),(y0,y1)...]
class _Node:
    def __init__(self, data, hypercube):
        self.data = data
        self.hypercube = hypercube

class _nTree:
    def __init__(self, hypercube, depth = 0):
        self.node = []
        self.children = []
        self.depth = depth
        self.hypercube = hypercube

    def __insert__(self, data):
        if not self.node:
            self.node = _Node(data, self.hypercube)
            if (len(self.node.data) != 1):
                self.__split__()

在我的例子中,每个子节点都将包含包含在其父节点中的数据 - 这就是检查 len(self.node.data) 是否不等于 1 的原因。如果我们只包含 1 个数据点超立方体,然后我们停下来,我们有一个叶节点。如果我们有多个,我们会进一步分裂。只有当数据点位于超立方体坐标定义的边界内时,它才会被放置在超立方体中。

例如,假设您有一个坐标为 [(0,1),(0,1)] 的二维平面 - 我们的根节点。我们想用数据点 [(0.5, 0.1),(0.2,0.3)] 填充它。由于我们有两个数据点,我们将平面分成 2^n 个新的超立方体(在本例中为正方形),其中 n 是维数 - 在本例中为 2。从 1x1 的平方根我们得到 4 个较小的坐标平方 [[(0, 0.5), (0, 0.5)], [(0.5, 1), (0.5, 1)], [(0.5, 1),( 0, 0.5)], [(0, 0.5),(0.5, 1)] - 基本上是根节点的子节点。这是一个可以在此处可视化的四叉树示例:https://en.wikipedia.org/wiki/Quadtree

我正在尝试做同样的事情,但有多个维度。

既然我试图解释我想要做什么,我的问题是:

超立方体变量包含当前节点的坐标。如何以正确生成坐标的方式实现拆分功能?例如,如果我有 6 个维度,我必须为每个节点生成 64 个坐标( 2^n; n = 维度数)。提醒一下,它不是 k-D 树。

编辑:我想我应该发布我当前的 split 功能:

def __split__(self):
    n_of_children = 2**(len(self.node.hypercube[0]))
    vector = self.__get_vector__() #returns the coordinates of all 64 hypercubes/trees
    self.children = [_nTree(vector, self.depth+1) for i in range(n_of_children)[ 
    self.__insert_children__(self.data)

我将每个子节点声明为树结构,然后调用 insert_children 来决定每个数据点进入哪个子节点。如果一个child里面有多个数据点,我们就重复整个拆分和插入的过程。

【问题讨论】:

    标签: python data-structures tree


    【解决方案1】:

    我曾经用Java写过一个k维四叉树,代码如下:

    NodeKD(double[] min, double[] max, int maxDepth, NodeKD parent) {
        this.min = min;
        this.max = max;
        this.center = new double[min.length];
        for (int i = 0; i < min.length; i++) {
            this.center[i] = (max[i]+min[i])/2;
        }
        this.maxDepth = maxDepth == -1 ? 4 : maxDepth;
        this.children = new ArrayList<>();
        qA = new NodeKD[1 << min.length];
        this.parent = parent;
    }
    
    private void subdivide() {
        int dim = min.length;
        double[] min = new double[dim];
        double[] max = new double[dim];
        for (int i = 0; i < qA.length; i++) {
            long mask = 1L;
            for (int j = 0; j < dim; j++) {
                if ((j & mask) == 0) {
                    min[j] = this.min[j];
                    max[j] = this.center[j];
                } else {
                    min[j] = this.center[j];
                    max[j] = this.max[j];
                }
                mask <<= 1;
            }
            qA[i] = new NodeKD(min, max, maxDepth-1, this);
        }
    }
    

    但是,据我所知,四叉树 (2D) 和八叉树 (3D) 对于更高的维度不是很有效。根据您想做的事情(范围查询、最近邻查询、简单查找、大量插入...),我会选择不同的结构。 KD-Trees 非常简单,可以插入/删除。 R-Trees (R+tree, R*tree, X-tree) 非常适合范围查询和最近邻查询。但是原始的 R-Tree 对于以后修改添加/删除数据非常不利。

    我个人最喜欢的是我自己的PH-Tree。它类似于 k 维四叉树,但有一些区别:

    • 它本质上是一个“trie”或critbit 树。这意味着只要一个值为“0”而另一个值为“1”,它就会查看值的位表示。由于我在位级别上操作,因此节点内的导航和寻址非常有效,因为我可以简单地操作 k 位字符串(对于 k 维)来遍历本地子节点并检查它们对查询的适用性。这避免了高维可扩展性的许多问题
    • 它使用前缀共享来减少内存需求(每个节点只存储本地值彼此不同的位)。
    • 由于静态特性(根据位拆分),任何修改都不会影响两个以上的节点,也不需要重新平衡。
    • 虽然没有重新平衡,但树的深度限制为 64(假设为 64 位值),因此不会严重退化。

    更多细节可以在herehere找到。缺点是当前的open-source version 仅在 Java 中(不是 python)并且非常复杂。我正在开发一个相当改进的版本(更简单的代码),但我可能需要一段时间才能发布它。

    【讨论】:

    • 就我而言,我不能使用 k-D 树,因为它们依赖于数据。就我而言,我所要做的就是插入数据并计算包含特定级别数据点的节点数。感谢您分享您的想法!在我开始构建自己的数据结构之前,我确实阅读了您提到的数据结构,但它们都不能给我想要的结果。
    • 嗯,正如您所说,所有树状结构都“依赖于数据”。没有真正的解决方法:-)。我不确定您所说的“特定级别”是什么意思,您是指在某个值范围内吗?
    • 我所说的级别是指树的深度。插入数据完成后,我想计算特定深度的节点数,这些节点包含数据并忽略不包含数据的节点。
    • 对不起,可能我理解错了,我写的是k维四叉树,不是kD树。所以我认为上面的代码正是你所要求的?您只需将 'dim' 设置为 6 或 7。循环创建分裂点,以便每个节点获得 2^k 个子节点。
    • 我很抱歉。我真的以为你写了一个k-D。我确实修复了分割部分,现在我正在正确计算坐标。我用的方法和你的很相似。
    【解决方案2】:

    这是我为在超立方体空间中运行 cppn 查询而开发的类似实现。在回顾四叉树的算法时,我还注意到细分应该产生 2^n 个坐标,并且还发现这个坐标由维度长度之和的排列表示,并且 (+ 或 - ) 该值/2。由于它的值随着我们的排列而改变,只能处于两种状态,我在这里找到了一个非常优雅的解决方案来查找二进制字符串的排列:https://codereview.stackexchange.com/questions/24690/print-all-binary-strings-of-length-n 使用这个你可以循环 2^n 创建一个新点并使用位串在该索引处进行排列以确定该维度值总和中第二个数字的符号(1 = 正数和 0 = 负数)。

    下面我首先包含了 java 实现,因为我认为它更具可读性并且位串排列更优雅,下面是一个 python 实现,但不使用 int->bitstring 来生成排列。这两个都会将任何维度 n 坐标细分为 2^n 个子树

    public class FractalTree {
        public double[] coord;
        public double width;
        public double weight;
        public double lvl;
        public String[] signs;
        public FractalTree[] children;
        public FractalTree(double[] c, double width, int lvl) {
            this.coord = c;
            this.width = width;
            this.lvl = lvl;
            this.children = new FractalTree[(int)Math.pow(2.0, (double)c.length)];
            this.permute_signs(this.coord.length);
        }
        public void subdivide_into_children() {
            for(int idx = 0; idx < this.children.length; idx++) {
                String sign_pattern = this.signs[idx];
                double[]  new_coord = new double[this.coord.length];
                for(int idx_2 = 0; idx_2 < this.coord.length; idx_2++) {
                    char sign = sign_pattern.charAt(idx_2);
                    if(sign == '1') {
                        new_coord[idx_2] = coord[idx_2] + this.width/2.0;
                    } else {
                        new_coord[idx_2] = coord[idx_2] - this.width/2.0;
                    }
                }
            }
        }
    
        public void permute_signs(int coord_len) {
            String str_len = "%" + Integer.toString(coord_len) + "s";
            for(long ix = 0; ix < this.children.length; ix++) {
                this.signs[(int)ix] = String.format(str_len, Long.toBinaryString(ix)).replace(' ', '0');
            }
        }
    
    }
    
    class nDimensionTree:
    
        def __init__(self, in_coord, width, level):
            self.w = 0.0
            self.coord = in_coord
            self.width = width
            self.lvl = level
            self.num_children = 2**len(self.coord)
            self.cs = [None] * self.num_children
            self.signs = self.set_signs()
            print(self.signs)
        def set_signs(self):
            return list(itertools.product([1,-1], repeat=len(self.coord)))
    
        def divide_childrens(self):
            for x in range(self.num_children):
                new_coord = []
                for y in range(len(self.coord)):
                    new_coord.append(self.coord[y] + (self.width/(2*self.signs[x])))
                newby = nDimensionTree(new_coord, self.width/2, self.lvl+1)
            self.cs.append(newby)
    

    【讨论】:

      猜你喜欢
      • 2011-10-26
      • 1970-01-01
      • 2013-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多