Статистика динамического порядка: получить k-й элемент в постоянное время?

Итак, я пытаюсь реализовать структуру данных для обработки статистики динамического порядка. Структура данных имеет следующие операции:

  • add( x): вставляет новый элемент со значением x
  • get(k): возвращает K-й наименьший элемент: k = потолок (n/a), где n = количество элементов в структуре данных и A = постоянный коэффициент.
  • reset: сбрасывает весь datastructuer, т. е. структура данных " пуста после него

я реализовал моя структура данных с использованием сбалансированного дерева AVL. Используя это, операции имеют следующую временную сложность:

  • добавить(x): O(log(n))
  • get (k): O(log(n))

вот моя имплементация для get (k), которая использует время O(log(n)):

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);
    }
}

и вот моя реализация для класса node:

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; 
}

}

однако, моя задача-реализовать структуру данных, которая может обрабатывать вышеуказанные операции и только принимая O (1) (постоянное) время для операции get (k). (И добавить(x) все еще принимая O (log (n)) время). Кроме того, мне не разрешено использовать hashmap.

можно ли изменить мою реализацию, чтобы получить постоянное время? Или какая структура данных может обрабатывать операцию get (k) в постоянное время?

2 ответов


насколько я понимаю, параметр k в основном растет с размером элементов, а это означает, что для каждого n вы знаете точное значение k.

Если это так, то мое предложение-использовать max-heap и min-heap. Max-heap организует элементы (smallerequals, чем n / a-й элемент) в структуре кучи, позволяя получить доступ к самому большому элементу (корню) в постоянное время. Соответственно, мин-куча организует элементы (больше, чем n/a-й элемент) в куче структура, позволяющая получить доступ к наименьшему элементу (корню) в постоянное время.

когда появляются новые элементы (добавить), вы помещаете их в соответствующую кучу в O(log n). Если max-куча становится больше или меньше, чем (n / a), вы перебалансируете между двумя кучами в O(log n)

ваша функция get () теперь просто должна вернуть корневой элемент max-heap в O (1).

в 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()-функция просто retrives сохраненное значение и в O (1).


если вы хотите использовать деревья, поддерживайте порядок от 1 до максимума a сбалансированные деревья. Для каждого дерева держите указатель на самый маленький и самый большой элемент и размер дерева. Всякий раз, когда элемент добавляется, вставьте его в соответствующее дерево. Если дерево растет дальше ceiling(n / a) элементы, реорганизуйте деревья, перемещая соответствующий самый низкий или самый высокий в соседнее дерево, чтобы сохранить их все между floor(n / a) и ceiling(n / a) элементы в размер. The kth элемент всегда будет самое маленькое или самое высокое из деревьев.

Add примет O(log a + log(n/a) * a) = O(log n) времени.
Get примет O(log a) = O(1) времени.