相信对算法设计或者数据结构有一定了解的人对线段树都不会太陌生。它是能够在log(MaxLen)时间内完成线段的添加、删除、查询等操作。但一般的实现都有点复杂而线段树应用中有一种是专门针对点的。(点树?)它的实现却非常简单。
这种数据结构有什么用?我们先来考虑一下下面的需求(全部要求在LogN时间内完成):如何知道一个点在一个点集里的大小"排名"?很简单,开一个点数组,排个序,再二分查找就行了;如何在一个点集内动态增删点?也很简单,弄个平衡树就行了(本来平衡树比线段树复杂得多,但自从世界上有了STL set这么个好东东,就……^_^)那如果我既要动态增删点,也要随时查询到一个点的排名呢?那对不起,可能就要出动到我们的"点树"了。
其实现原理很简单:每当增加(或删除)一个大小为X的点时,就在树上添加(或删除)一条(X,MaxLen)的线段(不含端点),当要查询一个点的排名时,只要看看其上有多少条线段就可以了。针对这一需求,这里有个非常简单的实现(见以下代码,十多行,够短了吧?)其中clear()用于清空点集;add()用于添加一个点;cntLs()返回小于n的点的个数,也就是n的升序排名,类似地cntGt是降序排名。
这个点树有什么用呢?其中一个应用是在O(NlogN)时间内求出一个排列的逆序数,方法是每读到一个数x,就让逆序数+=cntGt(x);然后再add(x)。
这个实现还可以进行一些扩展。比如删除del(int n),只要把add(int n)中的++size换成--size,把a[i/2]++改成a[i/2]--即可。另外还可以通过二分查找功能在O(logN)时间内查到排名第n的点的大小。应该也可以三四行内搞定。
补充:杨弋同学在2008年信息学奥赛冬令营上新发明了一种线段树的省空间堆式存储法,具体方法可以见08年冬令营课件.
实现代码及测试程序
另一种功能上比较类似的数据结构:"树状数组"。它们有不少相似之处:
针对点集的处理(添加、删除、查找);
相似的时空复杂度(logN时间,2N空间);
相似的编程复杂度(都比线段树简短得多);
因此,所有可以用树状数组解决的问题都可以用这个"点树"来解决,另外它还有以下好处:
更直观的转移;
同时支持自下而上和自上而下两种方向的查找和更新,而后者树状数组不支持,所以树状数组不提供某些功能,比如说O(logN)求点集中第k小数。
ZKW线段树由清华大学张昆玮发现,是一种新的用非递归方式实现的线段树,具体请参考张昆玮先生本人的讲稿《统计的力量》 。