使用数组

时间:2016-03-14 01:31:20

标签: c++ arrays algorithm cuda quadtree

我正在尝试使用Barnes-Hut树算法编写用于模拟n体问题的代码。我打算将来使用CUDA,因此我希望我的四叉树数据结构不是由堆对象组成的。

来自论文"基于树的Barnes Hut n-Body算法的高效CUDA实现"作者说:Martin Burtscher和Keshav Pingali(对不起找不到链接):

  

树等动态数据结构通常是从堆对象构建的,其中每个堆对象包含多个字段,例如子指针和数据字段,并且是动态分配的。因为堆对象的动态分配和访问往往很慢,所以我们使用基于数组的数据结构。如果数组元素是具有多个字段的对象,则无法合并对数组的访问,因此我们使用多个对齐的标量数组,每个字段一个,如图6.6所示。因此,我们的代码使用数组索引而不是指向树节点的指针。

我理解有关对齐标量数组的部分(即并行计算中的SOA与AOS习语),但不幸的是,作者没有解释如何使用数组构造四叉树。

我的问题是如何使用数组实现四叉树数据结构(使用插入空间点的方法)?我知道如何使用节点结构和子节点指针以传统方式实现四叉树等。有人可以提供一个参考,详细说明如何使用数组执行此操作。甚至有关如何使用数组实现二叉树(或任何树)的信息在这里也很有用。

3 个答案:

答案 0 :(得分:3)

使用数组实现二叉树非常简单,首先我们要从1开始索引数组,即根节点将为1,然后是

左边的孩子将是:leftChildIndex = 2 * parentIndex;

正确的孩子将在:rightChildIndex = 2 * parentIndex + 1;

现在,如果想要找到当前节点的父节点:parentIndex = currIndex/2;

我编写了一个c ++代码来执行树的前序遍历:

#include<iostream>

using namespace std;

int binaryTree[20], lengthOfTree;
int leftChild(int idx){ return 2*idx; }
int rightChild(int idx){ return 2*idx+1; }
int parentIndex(int idx){ return idx/2; }

void traverseTree(int idx){
    if(idx >= lengthOfTree) return;
    cout << binaryTree[idx] << " ";
    traverseTree(leftChild(idx));
    traverseTree(rightChild(idx));
}

int main(){

    lengthOfTree = 15;
    for(int i = 1;i <= lengthOfTree;i++){
        cin >> binaryTree[i];  
    }
    traverseTree(1);
    cout << endl;

return 0;
}

指向Ideone上的解决方案:http://ideone.com/ZpTJCa

----------------------------------------------------------------------------

对四叉树进行索引可能会稍微复杂一些,因此我们可以做的是再次将树索引为1,对于每个节点,我们可以找到该节点的级别,例如:the level of node 1 is 0, level of node 4 is 1, level of node 11 is 2

用于查找级别的伪代码:O(log n)

int findLevel(int nodeNo){
    int level = 0;
    int currNode = 1;
    while(currNode < nodeNo){
        currNode = currNode + pow(4, level++);
    }
    return level;
}

类似地,当前级别的最左边节点和currentlevel的最右边节点可以使用上面的伪代码计算,然后找到我们可以做的当前节点的4个子节点:

当前节点的第一个孩子:child1 = (rightmostNode - currentNode) + 4 * (currentNode - leftmostNode);

当前节点的第二个孩子:child2 = child1 + 1;

当前节点的第3个子节点:child3 = child2 + 1;

当前节点的第4个子节点:child4 = child3 + 1;

您还可以创建用于查找父级的映射。

答案 1 :(得分:3)

表示为数组的四叉树称为“线性四叉树”。 使用这个术语你会发现一些文献。

来自Hannan Samet的论文建议首先使用传统的四叉树实现应用程序,然后检查线性四叉树方法是否有效。 并非所有应用程序都可以使用线性四叉树。

“(使用插入空间点的方法)”

这种线性方法通常需要静态四叉树,即不改变其内容的四叉树。 同样适用于GPU应用程序,它们需要(巨大的)静态数据集,其中执行数百万次操作。对GPU的访问(上传时间)相对较慢,因此应用程序的类型应该是大多数数据不会改变的地方(一段时间)。

答案 2 :(得分:2)

一种解决方案可能是将四叉树打包成二叉树,例如使用空间填充曲线。我发现最容易使用的z曲线(morton order / z-order)。对于z排序,您需要交错坐标的位,以便例如将两个64值(x,y)交织成单个128位值。然后可以将128位值存储在二叉树或trie中。在C ++中应该有用于高效交错比特的操作码(我认为它被称为&#39;打包&#39;?)。

您也可以使用浮点值进行交错,请参阅Section 3.3 in the linked PDF。它显示了如何将浮点值快速转换为整数格式并返回而不会损失精度。示例代码(Java)取自here,我相信C ++,而不是使用Double.doubleToRawLongBits,您可以简单地将一个浮点数转换为整数,然后应用如下所示的相同转换:

public static long toSortableLong(double value) {
    long r = Double.doubleToRawLongBits(value);
    return (r >= 0) ? r : r ^ 0x7FFFFFFFFFFFFFFFL;
}

public static double toDouble(long value) {
    return Double.longBitsToDouble(value >= 0.0 ? value : value ^ 0x7FFFFFFFFFFFFFFFL);
}

修改

交错位的代码可能如下所示(来自here):

unsigned short x;   // Interleave bits of x and y, so that all of the
unsigned short y;   // bits of x are in the even positions and y in the odd;
unsigned int z = 0; // z gets the resulting Morton Number.

for (int i = 0; i < sizeof(x) * CHAR_BIT; i++) // unroll for more speed...
{
  z |= (x & 1U << i) << i | (y & 1U << i) << (i + 1);
}

上述解决方案是显而易见的方法。上面的链接中还有其他版本可能更快。

据我了解,现代CPU还具有交错操作(也称为隔行扫描&#39;),例如使用&#39; shuffling&#39;。可以找到另一种算法和更多信息here

编辑结束

获得二叉树后,可以应用另一个映射来获取数组。还要看一下Binary Heaps,它们通常被实现为数组。