【问题标题】:Implementing a Quadtree in Mathematica在 Mathematica 中实现四叉树
【发布时间】:2011-10-05 05:03:32
【问题描述】:

我在 Mathematica 中实现了quadtree。我是使用像 Mathematica 这样的函数式编程语言编码的新手,我想知道是否可以通过更好地使用模式来改进它或使其更紧凑。

(我知道我也许可以通过修剪未使用的节点来优化树,并且可能会有更好的数据结构,如 k-d 树用于空间分解。)

此外,我仍然对每次添加新点时都复制整个树/表达式的想法感到不舒服。但我的理解是,将表达式作为一个整体进行操作而不修改部分是函数式编程方式。我将不胜感激有关这方面的任何澄清。

MV

代码

ClearAll[qtMakeNode, qtInsert, insideBox, qtDraw, splitBox, isLeaf, qtbb, qtpt];

(* create a quadtree node *)
qtMakeNode[{{xmin_,ymin_}, {xmax_, ymax_}}] := 
{{}, {}, {}, {}, qtbb[{xmin, ymin}, {xmax, ymax}], {}}

(* is pt inside box? *)
insideBox[pt_, bb_] := If[(pt[[1]] <= bb[[2, 1]]) && (pt[[1]] >= bb[[1, 1]]) &&
  (pt[[2]] <= bb[[2, 2]]) && (pt[[2]] >= bb[[1, 2]]),
  True, False]

(* split bounding box into 4 children *)
splitBox[{{xmin_,ymin_}, {xmax_, ymax_}}] := {
 {{xmin, (ymin+ymax)/2}, {(xmin+xmax)/2, ymax}},
 {{xmin, ymin},{(xmin+xmax)/2,(ymin+ymax)/2}},
 {{(xmin+xmax)/2, ymin},{xmax, (ymin+ymax)/2}},
 {{(xmin+xmax)/2, (ymin+ymax)/2},{xmax, ymax}}
}

(* is node a leaf? *)
isLeaf[qt_] := If[ And @@((# == {})& /@ Join[qt[[1;;4]], {List @@ qt[[6]]}]),True, False]

(*--- insert methods ---*)

(* qtInsert #1 - return input if pt is out of bounds *)
qtInsert[qtree_, pt_] /; !insideBox[pt, List @@ qtree[[5]]]:= qtree

(* qtInsert #2 - if leaf, just add pt to node *)
qtInsert[qtree_, pt_] /; isLeaf[qtree] :=
{qtree[[1]],qtree[[2]],qtree[[3]],qtree[[4]],qtree[[5]], qtpt @@ pt} 

(* qtInsert #3 - recursively insert pt *)
qtInsert[qtree_, pt_] := 
  Module[{cNodes, currPt},
  cNodes = qtree[[1;;4]];
  (* child nodes not created? *)
  If[And @@ ((# == {})& /@ cNodes), 
    (* compute child node bounds *)
    (* create child nodes with above bounds*)
    cNodes = qtMakeNode[#]& /@ splitBox[List @@ qtree[[5]]];
  ];
  (* move curr node pt (if not empty) into child *)
  currPt = List @@ qtree[[6]];
  If[currPt != {},
    cNodes = qtInsert[#, currPt]& /@ cNodes; 
  ];
 (* insert new pt into child *)
 cNodes = qtInsert[#, pt]& /@ cNodes;
 (* return new quadtree *)
 {cNodes[[1]],cNodes[[2]], cNodes[[3]], cNodes[[4]], qtree[[5]], {}}
 ]

(* draw quadtree *)
qtDraw[qt_] := Module[{pts, bboxes},
  pts = Cases[qt, _qtpt, Infinity] /. qtpt :> List;
  bboxes = Cases[qt, _qtbb, Infinity] /. qtbb :> List;
  Graphics[{
   EdgeForm[Black],Hue[0.2], Map[Disk[#, 0.01]&, pts],
   Hue[0.7],EdgeForm[Red], FaceForm[],(Rectangle @@ #) & /@ bboxes
  },
  Frame->True
 ]
]

用法

Clear[qt];
len = 50;
pts = RandomReal[{0, 2}, {len, 2}];
qt = qtMakeNode[{{0.0, 0.0}, {2.0, 2.0}}];
Do[qt = qtInsert[qt, pts[[i]]], {i, 1, len}]
qtDraw[qt]

输出

【问题讨论】:

  • 我没有时间玩你的代码,但作为回应,'......我仍然不习惯每次有新点时都复制整个树/表达式的想法已添加...'---您是否正在寻找一种方法来为您的函数添加内存?如果是这样,请搜索“记住其值的函数”。如果不是您要找的东西,请忽略,我将在今天晚些时候使用它。看起来不错。 :)
  • 不,我的意思是,当我添加一个点时,不是将节点插入到现有的树中,实际上,我们正在生成一棵全新的树,丢弃旧的。我只是想知道这对内存管理的影响,当这样的表达式真的很大时。很难摆脱 C++ 的思维模式! ;-) 谢谢。
  • 如果我理解正确,您希望通过引用传递,因为您不喜欢 qtInsert 中的 Return 行从(可能)巨大的列表中创建一个新的嵌套列表老树和几个新节点。 Mathematica 中有一种方法可以用 Attributes[qtInsert] = HoldAll; 做这样的事情,但缺点是 qtInsert 的所有参数都必须是变量,而不是文字值。
  • 谢谢@bbtrb - 我需要阅读这个。

标签: wolfram-mathematica quadtree


【解决方案1】:

我认为您的代码并不像您预期​​的那样需要大量内存。它确实会破坏和重组列表,但它往往会保持大多数子列表不变。

正如其他人所说,使用 Hold 包装器和/或 HoldXXX 属性可能会做得更好,以便模拟 call-by-reference。

有关一些相关数据结构实现的核心方法,请参阅

http://library.wolfram.com/infocenter/MathSource/7619/

相关代码在笔记本 Hemmecke-final.nb 中(之所以如此命名,是因为它实现了 R. Hemmecke 和合著者的 toric Groebner 基算法)。

我尝试使用 Hold... 属性重新实现,但我对此并不十分擅长,并在代码向我发起攻击时放弃了它(错过了,但杀死了我的 Mathematica 会话)。因此,我有一个使用未记录的“原始”Mathematica 数据类型的实现,这种数据类型是惰性的,因此可以进行引用调用行为。

有问题的结构称为“expr bag”,因为通用 Mathematica 数据结构是“expr”。它就像一个列表,但是 (1) 它可以在一端增长(尽管不能缩小)并且 (2) 像其他原始表达式类型(例如版本 8 中的图形)一样,它具有可以通过提供的函数访问和/或更改的组件(一个API,可以这么说)。它的底层“元素”是惰性的,因为它们可以引用任何 expr(包括包本身)并且可以按照我将在下面指出的方式进行操作。

上面的第一项提供了实现 Sow/Reap 的底层技术。这是第二个对下面的代码感兴趣的地方。最后我将在解释数据结构的过程中加入一些评论,因为没有正式的文档。

我或多或少地保留了与原始代码相同的样式,特别是它仍然是一个在线版本(也就是说,元素不需要一开始就全部进入,而是可以单独添加)。改了几个名字。使基本结构类似于

节点(边界框、值、零个或四个子节点)

如果有子节点,则 value 字段为空。 box 和 value 字段由通常的 Mathematica List 表达式表示,尽管使用专用头并使其更类似于 C 结构样式可能是有意义的。在命名各种字段访问/设置函数时,我确实做了类似的事情。

需要注意的是,这种原始数据类型消耗的内存开销比例如一个列表。所以我下面的变体将使用比最初发布的代码更多的内存。不是渐近的,只是通过一个常数因子。此外,在访问或设置元素值方面,它需要一个恒定的开销因子,而不是一个可比较的 C 结构。所以它不是灵丹妙药,只是一种行为不应该产生渐近意外的数据类型。


AppendTo[$ContextPath, "Internal`"];

makeQuadTreeNode[bounds_] := Bag[{bounds, {}, {}}]

(*is pt inside box?*)

insideBox[pt_, box_] := 
 And @@ Thread[box[[1]] <= (List @@ pt) <= box[[2]]]

(*split bounding box into 4 children*)

splitBox[{{xmin_, ymin_}, {xmax_, ymax_}}] := 
 Map[makeQuadTreeNode, {{{xmin, (ymin + ymax)/2}, {(xmin + xmax)/2, 
     ymax}}, {{xmin, 
     ymin}, {(xmin + xmax)/2, (ymin + ymax)/2}}, {{(xmin + xmax)/2, 
     ymin}, {xmax, (ymin + ymax)/2}}, {{(xmin + xmax)/
      2, (ymin + ymax)/2}, {xmax, ymax}}}]

bounds[qt_] := BagPart[qt, 1]
value[qt_] := BagPart[qt, 2]
children[qt_] := BagPart[qt, 3]

isLeaf[qt_] := value[qt] =!= {}
isSplit[qt_] := children[qt] =!= {}
emptyNode[qt_] := ! isLeaf[qt] && ! isSplit[qt]

(*qtInsert #1-return input if pt is out of bounds*)

qtInsert[qtree_, pt_] /; ! insideBox[pt, bounds[qtree]] := qtree

(*qtInsert #2-empty node (no value,no children)*)

qtInsert[qtree_, pt_] /; emptyNode[qtree] := value[qtree] = pt

(*qtInsert #2-currently a leaf (has a value and no children)*)

qtInsert[qtree_, pt_] /; isLeaf[qtree] := Module[
  {kids = splitBox[bounds[qtree]], currval = value[qtree]},
  value[qtree] = {};
  children[qtree] = kids;
  Map[(qtInsert[#, currval]; qtInsert[#, pt]) &, kids];
  ]

(*qtInsert #4-not a leaf and has children*)

qtInsert[qtree_, pt_] := Map[qtInsert[#, pt] &, children[qtree]];

getBoxes[ee_Bag] := 
 Join[{bounds[ee]}, Flatten[Map[getBoxes, children[ee]], 1]]
getPoints[ee_Bag] := 
 Join[{value[ee]}, Flatten[Map[getPoints, children[ee]], 1]]

qtDraw[qt_] := Module[
  {pts, bboxes},
  pts = getPoints[qt] /. {} :> Sequence[];
  bboxes = getBoxes[qt];
  Graphics[{EdgeForm[Black], Hue[0.2], Map[Disk[#, 0.01] &, pts], 
    Hue[0.7], EdgeForm[Red], 
    FaceForm[], (Rectangle @@ #) & /@ bboxes}, Frame -> True]]

这是一个例子。我会注意到缩放是合理的。也许 O(n log(n)) 左右。绝对比 O(n^2) 好。

len = 4000;
pts = RandomReal[{0, 2}, {len, 2}];
qt = makeQuadTreeNode[{{0.0, 0.0}, {2.0, 2.0}}];
Timing[Do[qtInsert[qt, pts[[i]]], {i, 1, len}]]

{1.6, Null}

一般 expr 包注释。这些都是旧的,所以我不认为这一切仍然如所示。

这些函数存在于 Internal` 上下文中。

包 创建一个 expr 包,可以选择使用预设元素。

包部分 获取 expr 包的部分,类似于普通的 Part 表达式。也可用于 lhs,例如重置一个值。

东西袋 将元素附加到包的末尾。

我们还有一个 BagLength。对遍历包很有用。

这些函数非常有用,有两个原因。

首先,这是制作可扩展表的好方法 数学。

其次,对袋子的内容进行评估,然后将其放入 一个原始的 expr,因此被屏蔽了。因此,可以将这些用作 “指针”(在 C 意义上)而不是作为对象,而这 不需要 Hold 等。以下是一些示例:

a = {1,2,a} (* gives infinite recursion *)

如果我们改为使用袋子,我们会得到一个自引用结构。

In[1]:= AppendTo[$ContextPath, "Internal`"];

In[2]:= a = Bag[{1,2,a}]
Out[2]= Bag[<3>]

In[3]:= expr1 = BagPart[a, All]
Out[3]= {1, 2, Bag[<3>]}

In[4]:= expr2 = BagPart[BagPart[a, 3], All]
Out[4]= {1, 2, Bag[<3>]}

In[5]:= expr1 === expr2
Out[5]= True

这在 Mathematica 中很难以任何其他方式进行模拟。 在某些情况下需要使用稀疏表(散列) 不太透明的方式。

这是一个相关示例,未完全调试。我们基本上 实现一个可以破坏性修改的链表 尾部,替换子列表等。

tail[ll_] := BagPart[ll,2]
settail[ll_, ll2_] := BagPart[ll,2] = ll2
contents[ll_] := BagPart[ll,1]
setcontents[ll_, elem_] := BagPart[ll,1] = elem

createlinkedlist[elems__] := Module[
    {result, elist={elems}, prev, el},
    result = Bag[{elist[[1]],Bag[]}];
    prev = result;
    Do [el = Bag[{elist[[j]],Bag[]}];
        settail[prev, el];
        prev = el,
        {j,2,Length[elist]}];
    result
    ]

In[18]:= tt = createlinkedlist[vv,ww,xx]
Out[18]= Bag[<2>]

In[20]:= BagPart[tt,All]
Out[20]= {vv, Bag[<2>]}

所以tt是一个链表,第一个元素是vv,接下来是 本身是一个链表等。我避免使用 Lisp 术语(汽车/cdr 等),因为我无法 回想一下 Lisp 的列表操作是否具有破坏性。但 你明白了。

类似地,我使用 expr 包来实现二进制 树木。这很有用,因为我们可以在 恒定时间(假设我们已经有了一个“句柄” 插入/删除),以及 expr 的“原始”性质 bag 意味着我们完全避免了 Mathematica 的无限求值 语义。

也许是另一个应用程序。

Pointer = Internal`Bag
Contents[aa_Pointer, j_Integer] /;0<j<=Internal`BagLength[aa] :=
    Internal`BagPart[aa,j]
SetContents[aa_Pointer, j_Integer, e_] /; 0<j<=Internal`BagLength[aa] :=
    Internal`BagPart[aa,j] = e
SetContents[aa_Pointer, j_Integer, e_] /; j>BagLength[aa] :=
    (Do[Internal`StuffBag[aa,Null], {k,Internal`BagLength[aa]+1,j-1}];
    Internal`StuffBag[aa,e])

试试

a = Bag[{1,2,a,6,t,y,99,Bag[{a,q,3,r,a,5,t}]}]
expr1 = BagPart[a, All]
expr2 = BagPart[BagPart[a, 3], All]

Contents[a, 4]
SetContents[a, 7, Contents[a,7]+5]
SetContents[a,11,33]

丹尼尔·利希特布劳 Wolfram 研究

【讨论】:

  • 很好的信息!似乎是建立列表的一种非常快速的方法
  • +1,我只是第一次阅读这个。很好的信息,谢谢。
【解决方案2】:

这是一个更紧凑的版本。它使用与原始版本相同的数据结构。函数splitBoxinsideBox 也基本相同(只是编写方式略有不同)。

初始框不是逐个添加点,而是包含开头的所有点,因此不需要qtInsert 例程。在每个递归步骤中,包含多个点的框被拆分,并且这些点分布在子框上。这意味着具有多个点的所有节点都是叶子,因此也无需检查。

qtMakeNode[bb_, pts_] := {{}, {}, {}, {}, qtbb @@ bb, pts}

splitBox[bx_] := splitBox[{min_, max_}] := {min + #, max + #}/2 & /@  
  Tuples[Transpose[{min, max}]]


insideBox[pt_, bb_] := bb[[1, 1]] <= pt[[1]] <= bb[[2, 1]] && 
  bb[[1, 2]] <= pt[[2]] <= bb[[2, 2]]

distribute[qtree_] := Which[
  Length[qtree[[6]]] == 1, 
    (* no points in node -> return node unchanged *)
  qtree,

  Length[qtree[[6]]] == 1, 
    (* one point in node -> replace head of point with qtpt and return node *)
  ReplacePart[qtree, 6 -> qtpt @@ qtree[[6, 1]]],

  Length[qtree[[6]]] > 1, 
    (* multiple points in node -> create sub-nodes and distribute points *)
    (* apply distribute to sub-nodes *) 
  Module[{spl = splitBox[qtree[[5]]], div, newtreelist},
   div = Cases[qtree[[6]], a_ /; insideBox[a, #], 1] & /@ spl;
   ReplacePart[qtree, 
    Join[Table[i -> distribute[qtMakeNode[spl[[i]], div[[i]]]], {i, 4}], 
      {6 -> {}}]]]]

示例(使用qtDraw的原始版本):

len = 50;
pts = RandomReal[{0, 2}, {len, 2}];
qt = makeTree[qtMakeNode[{{0.0, 0.0}, {2.0, 2.0}}, pts]];
qtDraw[qt]

结果:

【讨论】:

  • 感谢代码 - 很多有趣的用法。但我确实认为能够一一加分很重要。例如,考虑一个 2D 模拟,其中粒子一个一个地进入。
  • @Heike 要么你的图片是空白的,要么我的机器有问题。
  • @belisarius:奇怪,在我的屏幕上它是可见的。当你点击这个链接时,你能看到它吗? i.stack.imgur.com/kNyrg.jpg
  • @Heike 别介意。当我访问 imgur.com 主页时,我收到了 Emergency maintenance - We're currently expirencing issues with uploading and viewing some images, and are working on correcting them now. Please check back soon for updates. 所以您可能正在查看您机器上的缓存图像。
【解决方案3】:

这可能不是您想要做的,但 Nearest[] 可以创建一个 NearestFunction[],它是一个内置的四叉树结构。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-08
    • 1970-01-01
    • 2020-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-25
    • 1970-01-01
    相关资源
    最近更新 更多