我正在思考以下问题的解决方案:我有一个大整数数组(在更难的情况下是整数流),我想将该数组转换为中位数数组,即它的位置对应于数组的中位数 [ 0..i]。
现在,我的蛮力方法是针对每个子数组,对其进行排序,然后选择中间元素。那是 O(n^2 log n),因为每个 n 子数组必须在 N log N 时间排序。我可能可以使用一些计数机制将时间降低到 N^2,但是 N^2 是可以做到的最好的吗?
将单个项目插入到已经排序的树状结构中,例如红黑树,将花费 O(log n )。如果您维护每个节点的后代数量,那么找到中位数也可以在 O(log n ) 中完成。对流的所有n 个元素执行此操作,并且您有一个 O( n log n ) 算法。
数据结构和中值计算可能如下所示:
struct TreeNode {
enum { RED, BLACK } color;
size_t numElements;
int value;
TreeNode* left, right;
} *root;
TreeNode* treeSelect(TreeNode *top, size_t count) {
if (top->left != NULL) {
if (count < top->left->numElements)
return treeSelect(top->left, count)
count -= top->left->numElements;
}
if (count == 0)
return top;
return treeSelect(top->right, count - 1);
}
TreeNode* treeMedian() {
return treeSelect(root, root->numElements / 2);
}
其他操作通常用于红黑树,尽管您可以跳过那些删除节点。您可以调整它们以使用重复元素。这里的一般规则是,当要插入的元素等于当前节点的元素时,您可以插入到任何子树中。并且在平衡树时,应保持重复键的顺序,以便您也保持附加子树的顺序。但是除非我错过了什么,否则平衡将在任何情况下都没有值比较,所以一旦你插入了重复项,你就完成了。如果您期望确实有很多重复值,则可以使用类似于 multimap 的方法,在每个节点中都有一个计数器。
可以通过O(nlogn)
以下方法完成:
使用跳过列表(或 B+ 树)来维护数据流。
还要维护一个指向当前中位数的指针。
在每次迭代中,让节点数为n
(在插入刚到达的元素之前),中位数(值)为m
,您看到的新值为x
。
x
跳过列表n%2 ==0
(中位数的索引应该增加):
x < m.value
: 不需要改变, 的索引m
也增加
m <- m.next
x < m.value
: set m <- m.previous
//因为 m 的索引增加了
m.value
是下一个元素的中位数找到上一个/下一个是O(1)
,插入x
是O(logn)
- 总复杂度是O(nlogn)
额外的O(n)
空间。
注意:如果流可以包含重复元素,则应特别注意,并且跳过列表应具有确定性行为 - 即始终插入最后一个可能的索引
使用一些快速的 BST,如 AVL 或 Splay。您可以简单地修改结构,以便每个节点都能及时获得其子树的中值log N
。特别是您可以获得树中所有项目的中位数。
现在你正在做这样的事情:
Create empty BST
foreach n in stream:
Insert n to BST
Get median from BST.root