我正在尝试使用Barnes-Hut树算法编写用于模拟n体问题的代码。我打算将来使用CUDA,因此我希望我的四叉树数据结构不是由堆对象组成的。
来自论文"基于树的Barnes Hut n-Body算法的高效CUDA实现"作者说:Martin Burtscher和Keshav Pingali(对不起找不到链接):
树等动态数据结构通常是从堆对象构建的,其中每个堆对象包含多个字段,例如子指针和数据字段,并且是动态分配的。因为堆对象的动态分配和访问往往很慢,所以我们使用基于数组的数据结构。如果数组元素是具有多个字段的对象,则无法合并对数组的访问,因此我们使用多个对齐的标量数组,每个字段一个,如图6.6所示。因此,我们的代码使用数组索引而不是指向树节点的指针。
我理解有关对齐标量数组的部分(即并行计算中的SOA与AOS习语),但不幸的是,作者没有解释如何使用数组构造四叉树。
我的问题是如何使用数组实现四叉树数据结构(使用插入空间点的方法)?我知道如何使用节点结构和子节点指针以传统方式实现四叉树等。有人可以提供一个参考,详细说明如何使用数组执行此操作。甚至有关如何使用数组实现二叉树(或任何树)的信息在这里也很有用。
答案 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,它们通常被实现为数组。