动态订单统计:在恒定时间内得到第k个元素?

时间:2018-01-06 16:30:34

标签: java algorithm data-structures binary-search-tree avl-tree

所以,我正在尝试实现数据结构来处理动态订单统计。数据结构具有以下操作:

  • add(x):插入值为x
  • 的新元素
  • get(k):返回第k个最小元素:k = ceiling(n / a),其中n =数据结构中元素的数量,a =常数因子。
  • reset:重置整个数据结构,即数据结构“
  • 后为空”

我使用平衡的AVL树实现了我的数据结构。使用此操作具有以下时间复杂度:

  • add(x):O(log(n))
  • get(k):O(log(n))

这是我使用O(log(n))时间的get(k)的实现:

public static int get(Node current, int k) {
    int l = tree.sizeLeft(current) + 1;
    if(k == l) {
        return current.value;
    } else if(k < l) {
        if(current.left == null) {
            return current.value;
        }
        return get(current.left, k);
    } else {
        if(current.right == null) {
            return current.value;
        }
        return get(current.right, k);
    }
}

这是我对节点类的实现:

class Node {
int height, value, bal, size;   // bal = balanceFactor, size = amount of nodes in tree 
                                   rooted at current node
Node leftChild = null;
Node rightChild = null;

public Node(int val) {
    value = val;
    height = 1;
    size = 1; 
}

}

但是,我的任务是实现一个可以处理上述操作的数据结构,只执行操作get(k)的O(1)(常量)时间。 (并添加(x)仍然采用O(log(n))时间)。另外,我不允许使用hashmap。

是否可以修改我的实现以获得恒定时间? 或者,什么样的数据结构可以在恒定时间内处理get(k)操作?

2 个答案:

答案 0 :(得分:1)

据我所知,k参数基本上随着元素的大小而增长,这意味着对于每个n,你知道k的确切值。

如果是这种情况,那么我的建议是使用max-heap和min-heap。 max-heap在堆结构中组织元素(小于n / a th元素),允许在恒定时间内访问最大元素(root)。 因此,最小堆在堆结构中组织元素(大于第n个元素),允许在恒定时间内访问最小元素(根)。

当新元素到达(添加)时,将它们放在O(log n)中的相应堆中。如果max-heap变得大于或小于(n / a),则在O(log n)中的两个堆之间重新平衡

你的get()函数现在只需要在O(1)中返回max-heap的根元素。

在Java中,您可以为max-heap(和min-heap)使用优先级队列

PriorityQueue<Integer> heap = new PriorityQueue<>(10, Collections.reverseOrder());

这个课程看起来像这样

import java.util.Collections;
import java.util.PriorityQueue;

public class DOS
{

    double a;
    PriorityQueue<Integer> heap;
    PriorityQueue<Integer> heap_large_elements;

    public DOS(double a) {
        this.a = a;
        this.heap = new PriorityQueue<>(10, Collections.reverseOrder());
        this.heap_large_elements = new PriorityQueue<>();
    }

    public void add(int x){
        if(heap.size() == 0 || x < heap.peek())
            heap.add(x); // O(log n/a)
        else
            heap_large_elements.add(x); // O(log n)

        //possible rebalance operations
        int n = heap.size() + heap_large_elements.size();
        if(heap.size() > Math.ceil(n/a)){
            heap_large_elements.add(heap.poll()); //O(log n)
        }else if(heap.size() < Math.ceil(n/a)) {
            heap.add(heap_large_elements.poll()); //O(log n)
        }
    }

    public int get(){
        return heap.peek(); //O(1)
    }

    public static void main(String[] args)
    {
        DOS d = new DOS(3);
        d.add(5);d.add(6);d.add(2);d.add(3);d.add(8);d.add(12);d.add(9);
        System.out.println(d.get());
    }

}

编辑(作者:Cheaty McCheatFace):

另一个让你使用你的代码但有点欺骗的想法如下。每当你向AVL树添加一个元素时,你就会计算k(= n / a)最大元素(在代码中完成)并存储它。这样,add() - 函数仍然具有O(log n)运行时。 get() - 函数只检索存储的值,并且在O(1)中。

答案 1 :(得分:0)

如果您想使用树木,请保持1到最多a平衡树的顺序。对于每个树,保留指向最小和最大元素以及树大小的指针。每当添加元素时,将其插入适当的树中。如果树长度超过ceiling(n / a)元素,则通过将相应的最低或最高值移动到相邻树来重新组织树,以使它们在大小为floor(n / a)ceiling(n / a)元素之间保持全部。 k元素将始终是其中一棵树中最小或最高的。

Add需要O(log a + log(n/a) * a) = O(log n)时间 Get需要O(log a) = O(1)时间。