Слишком медленные запросы в дереве интервалов

У меня есть список интервалов, и мне нужно вернуть те, которые перекрываются с интервалом, переданным в запросе. Что примечательно, так это то, что в типичном запросе около трети или даже половины интервалов перекрываются с интервалом, указанным в запросе. Также соотношение самого короткого интервала к самому длинному не более 1: 5. Я реализовал свое собственное дерево интервалов (расширенное красно-черное дерево) - я не хотел использовать существующие реализации, потому что мне нужна была поддержка закрытых интервалов и некоторые специальные функции. Я проверил скорость запроса с 6000 запросов в дереве с 6000 интервалами (таким образом, n = 6000 и m = 3000 (приложение)). Оказалось, что брутфорс ничем не хуже использования дерева:

Computation time - loop: 125.220461 s
Tree setup: 0.05064 s
Tree Queries: 123.167337 s

Позвольте мне воспользоваться асимптотическим анализом. n: количество запросов; n: количество интервалов; приложение. n / 2: количество интервалов, возвращаемых в запросе:

временная сложность грубой силы: n * n

дерево временной сложности: n * (log (n) + n / 2) -> 1/2 n n + n log (n) -> n * n

Таким образом, результат говорит о том, что эти два значения должны быть примерно одинаковыми для большого n. Тем не менее можно было бы ожидать, что дерево будет заметно быстрее, учитывая константу 1/2 перед n * n. Итак, я могу представить себе три возможных причины полученных результатов:

а) Моя реализация неверна. (Следует ли мне использовать BFS, как показано ниже?) Б) Моя реализация правильная, но я сделал вещи громоздкими для Python, поэтому ему нужно больше времени для работы с деревом, чем для работы с грубой силой. в) все в порядке - так и должно быть при большом n

Моя функция запроса выглядит так:

from collections import deque

def query(self,low,high):
    result = []
    q = deque([self.root]) # this is the root node in the tree
    append_result = result.append
    append_q = q.append
    pop_left = q.popleft
    while q:
        node = pop_left() # look at the next node
        if node.overlap(low,high): # some overlap?
            append_result(node.interval)
        if node.low != None and low <= node.get_low_max(): # en-q left node
            append_q(node.low)                
        if node.high != None and node.get_high_min() <= high: # en-q right node
            append_q(node.high)

Я строю дерево так:

def build(self, intervals):
    """
    Function which is recursively called to build the tree.
    """
    if intervals is None:
        return None

    if len(intervals) > 2: # intervals is always sorted in increasing order
        mid = len(intervals)//2
        # split intervals into three parts:
        # central element (median)
        center = intervals[mid]
        # left half (<= median)
        new_low = intervals[:mid]
        #right half (>= median)
        new_high = intervals[mid+1:]
        #compute max on the lower side (left):
        max_low = max([n.get_high() for n in new_low])
        #store min on the higher side (right):
        min_high = new_high[0].get_low()

    elif len(intervals) == 2:
        center = intervals[1]
        new_low = [intervals[0]]
        new_high = None
        max_low = intervals[0].get_high()
        min_high = None

    elif len(intervals) == 1:
        center = intervals[0]
        new_low = None
        new_high = None
        max_low = None
        min_high = None

    else:
        raise Exception('The tree is not behaving as it should...')

    return(Node(center, self.build(new_low),self.build(new_high),
                max_low, min_high))

РЕДАКТИРОВАТЬ:

Узел представлен так:

class Node:
    def __init__(self, interval, low, high, max_low, min_high):
        self.interval = interval # pointer to corresponding interval object
        self.low = low # pointer to node containing intervals to the left
        self.high = high # pointer to node containing intervals to the right
        self.max_low = max_low # maxiumum value on the left side
        self.min_high = min_high # minimum value on the right side

Все узлы в поддереве можно получить следующим образом:

def subtree(current):
    node_list = []
    if current.low != None:
        node_list += subtree(current.low)
    node_list += [current]
    if current.high != None:
        node_list += subtree(current.high)
    return node_list

p.s. обратите внимание, что, эксплуатируя то, что существует такое большое перекрытие и что все интервалы имеют сопоставимую длину, мне удалось реализовать простой метод, основанный на сортировке и делении пополам, который завершился за 80 с, но я бы сказал, что это перебор ... Забавно, Используя асимптотический анализ, я обнаружил, что он должен иметь app. та же среда выполнения, что и при использовании дерева ...


person Mitya Stiglitz    schedule 22.10.2014    source источник
comment
Асимптотический анализ почти ничего не говорит о реальном времени работы :) а также пробовали ли вы использовать профилировщик?   -  person BlackBear    schedule 18.01.2017


Ответы (1)


Если я правильно понимаю вашу проблему, вы пытаетесь ускорить свой процесс. Если это так, попробуйте создать настоящее дерево вместо того, чтобы манипулировать списками.

Что-то вроде:

class IntervalTreeNode():
    def __init__(self, parent, min, max):
        self.value      = (min,max)
        self.parent     = parent

        self.leftBranch = None
        self.rightBranch= None

    def insert(self, interval):
        ...

    def asList(self):
        """ return the list that is this node and all the subtree nodes """
        left=[]
        if (self.leftBranch != None):
            left = self.leftBranch.asList()
        right=[]
        if (self.rightBranch != None):
            left = self.rightBranch.asList()
        return [self.value] + left + right

А затем при запуске создайте internalTreeNode и вставьте в него все свои интервалы. Таким образом, если вам действительно нужен список, вы можете создавать список каждый раз, когда вам нужен результат, а не каждый раз, когда вы делаете шаг в своей рекурсивной итерации, используя [x:] или [:x] поскольку манипулирование списком - дорогостоящая операция в Python. Можно также работать, используя непосредственно узлы вместо списка, что значительно ускорит процесс, поскольку вам просто нужно вернуть ссылку на узел вместо добавления некоторого списка.

person Arthur Vaïsse    schedule 22.10.2014
comment
Я добавил еще немного кода о том, как построить дерево (в разделе РЕДАКТИРОВАТЬ). Думаю, я действительно поступил так, как вы предлагаете. Трудно поместить весь код в один пост ... Метод build используется просто для построения дерева с узлами - tree.root находится наверху. Подскажите пожалуйста, если что не так. - person Mitya Stiglitz; 22.10.2014