算法随笔(一):动态维护分位数(第N大数,The N-th element)
查找分位数的典型算法是分治法$T(n)=O(n)$。但是对于动态数组来说,依然有优化空间。
问题描述:交易员在策略中要维护一个定长价格序列(N=3000),交易所每推过来一个新的价格,需要从头部插入它,同时从尾部移走最早的价格。之后,获取它的10%分位数和90%分位数。根据分位数来获得交易信号。
最原始的做法是:每次更新价格后,排序,然后取10%,90%分位数。在实盘运行时自然没有问题,但是回测速度特别慢。因为它的时间复杂度是$T(n)=O(n\log n)$
使用分治法代替排序过程后,时间复杂度为$T(n)=O(n)$。依然不够快:
最终做法:
1、建立一个Queue(内部实现:循环数组),来按时间顺序存放价格。
2、建立三个SortedSet(内部实现:排序二叉树): LeftSet,MiddleSet和RightSet。分别保存序列中最小的10%,中间的80%和最大的10%和价格。
每来一个价格p:
1、从Queue头部插入p,从尾部弹出最老的数据q。时间复杂度:$T(n)=O(1)$
2、从LeftSet, MiddleSet,RightSet三个集合中找到q,删除。若q在LeftSet中,删除后LeftSet的元素将少于10%。这时将MiddleSet中最小值移动到LeftSet中,再将RightSet的最小值移入MiddleSet。其他情况以此类推。时间复杂度:$T(n)=O(\log n)$
3、p插入LeftSet,这时候LeftSet中元素个数超过10%。所以将LeftSet最大值移入MiddleSet,在把MiddleSet最大值移入RightSet。最后删除RightSet中的最大值。时间复杂度:$T(n)=O(\log n)$
4、获取10%分位数=LeftSet.Max,90%分位数=RightSet.Min。时间复杂度:$T(n)=O(\log n)$
这样,整个操作时间复杂度降为$T(n)=O(\log n)$
当然,排序二叉树也可以用堆来代替。实现略有不同,这里省略。