树形结构的节点搜索优化

介绍

树形结构具有在程序中普遍性。以XML为例,是一个树形的结构;其提供的XPath,给予了全局定位每一个节点的能力。本文针对这个操作,讨论性能上的优化。分析过程采用典型的自底向上的方式:基本算法设计、算法性能考虑以及对象接口设计。

基本设计

简化起见,定义Path:<n1, n2, …, nk>。nj可以是常量,代表可以借助索引来加速的情况;也可以是通配符,代表只能遍历的情况。

tree.find(Path xp)

            node <= tree.root

            For i = 1 to k

                        node <= node.find(xp[i]) // node is a set if xp[i] is wildcard

            Return node

以node.find为基本操作,这个算法时间复杂度为O(N),N为Xpath长度。以tree的节点node为基本存储单位,这个算法空间复杂度为O(N),如果xp为常数,N为Xpath长度;如果所有xp[i]为通配符,N为tree节点的总个数。

性能考虑

当tree.find被调用M次的时候,这个算法的时间复杂度为M*O(N)。通过分析,如果考虑调用之间的相关度,可以有效地提高算法效率。

1、   对于常量xp,如果两个相邻的xp之间,相同节点的个数在0到N之间平均分布,那么增加N个基本存储单位作为缓存,时间复杂度会下降为M/2*O(N)。

2、  对于变量xp,事实上搜索结果是节点集合,基本上仍然有上面的结论。值得注意的是此时N为tree节点的总个数。当N比较大的时候,这种缓存技术的空间复杂度并不令人满意。

为了处理好第二种情况,对现实情况进行进一步分析:

1、   一般N比M大很多

2、  如果xp开始几个节点是常量。这是一种在给定节点下的无条件遍历,借助索引的node.find的次数小于xp的长度。这种情况下,多次tree.find调用可以参照常量xp优化。

3、  如果xp开始几个节点是通配符,中间几个节点是常量。最差的情况就是遍历所有的节点(具有规定长度的Path),然后一个一个地判断每个路径是否与中间结点匹配。有些应用里面,可以根据后面几个节点名字得到前面某些节点的范围。此时要在遍历算法中增加这些范围的判断,如果这种判断的代价小于node.find的话。

4、  第3种情况下,多次tree.find调用合并成一次,可以共用遍历,虽不减少时间复杂度,但是减少了节点的内存访问,从而提高性能。

接口设计

最简单的接口就是set_of_node tree.find(Path xp)

注意到返回的节点可能比较多,可以把调用改成多次返回一个节点,即node tree.find(Path xp),返回NULL时一次搜索结束。这样可以削减空间复杂度,但是算法内部需要一个迭代器的设计模式。

考虑到多次变量xp的可合并性,还可以改成两个函数,调用序列如下:

{ { define_path_to_search(xp); } { tree.find(); } }

posted @ 2012-10-12 13:20  yunfeng_net  阅读(1732)  评论(0编辑  收藏  举报