Самый быстрый способ пройти от узла A к узлу B в красно-черном дереве

Скажем, я хочу пройти от узла № 154 к узлу № 254, чтобы выбрать самый быстрый из возможных вариантов:

Лучшее, что я могу получить, это: получить первый узел в диапазоне и пройти по порядку от этого узла:

/*
 * Look for a node matching a key range in tree.
 * Returns a pointer to the node if found, else NULL.
 */
struct rb_node *rb_range(struct rb_tree *tree, void *data, int (*func)(const void *, const void *))
{
    struct rb_node *node = rb_first(tree);
    int res;

    while (node != rb_nil(tree)) {
        if ((res = func(data, node->data)) == 0) {
            return node;
        }
        node = res < 0 ? node->left : node->right;
    }
    return NULL;
}

Функция сравнения выглядит так:

static int range_comp(const void *range, const void *data)
{
    const long ele = *(const long *)data;
    const long *arr = range;

    if ((ele >= arr[0]) && (ele <= arr[1])) {
        return 0;
    }
    if (ele > arr[0]) {
        return -1;
    } else {
        return +1;
    }
}

И обратный вызов, идущий по результату (есть комментарий, чтобы увидеть, сколько узлов было отправлено в обратный вызов):

static int print_data(void *data, void *cookie)
{
    long value = *(long *)data;
    const long *arr = cookie;

    if (cookie != NULL) {
        if (value > arr[1]) {
            return 1;
        }
        //  if (value >= arr[0]) {
            printf("%ld\n", value);
        //  }
    }
    return 0;
}

Обход в порядке:

/*
 * Call func() for each node, passing it the node data and a cookie;
 * If func() returns non-zero for a node, the traversal stops and the
 * error value is returned.  Returns 0 on successful traversal.
 */
int rb_apply_node(struct rb_tree *tree, struct rb_node *node,
    int (*func)(void *, void *), void *cookie, enum rb_traversal order)
{
    int error;

    if (node != rb_nil(tree)) {
        if (order == preorder)
            if ((error = func(node->data, cookie)) != 0)
                return error;
        if ((error = rb_apply_node(tree, node->left, func, cookie, order)) != 0)
            return error;
        if (order == inorder)
            if ((error = func(node->data, cookie)) != 0)
                return error;
        if ((error = rb_apply_node(tree, node->right, func, cookie, order)) != 0)
            return error;
        if (order == postorder)
            if ((error = func(node->data, cookie)) != 0)
                return error;
    }
    return 0;
}

    long x = rand() % n;
    printf("%d) Printing from %ld to %ld\n", i, x, x + 100);
    long arr[] = {x, x + 100};
    node = rb_range(tree, arr, range_comp);
    if (node != NULL) {
        /* Iterate from node A to node B INORDER */
        rb_apply_node(tree, node, print_data, arr, inorder);
    }

Но я получаю слишком много результатов:

0 2 3 4 5 6 8 10 12 13 14 16 17 18 19 20 21 22 23 25 26 27 28 29 31 32 35 36 38 39 40 41 42 43 44 45 47 48 50 51 53 57 59 60 61 62 63 64 65 67 68 72 75 76 77 78 80 81 82 83 85 88 90 91 93 95 96 98 100 101 102 108 109 111 112 113 115 116 117 118 121 122 123 124 125 126 127 128 129 131 132 135 136 137 138 141 143 145 146 148 149 150 153 157 158 161 163 166 168 169 170 172 173 174 175 176 177 178 179 180 182 183 186 188 189 190 192 193 194 195 196 197 198 199 200 201 202 203 205 206 210 212 216 217 218 219 220 221 222 223 225 226 227 228 229 230 231 233 234 235 236 238 239 240 241 244 245 249 252 253 254

Как я могу ограничить результат (насколько это возможно) выбранным диапазоном (от 154 до 254)?


person David Ranieri    schedule 24.01.2017    source источник


Ответы (2)


Во-первых, просто подумайте о том, что вы пытаетесь сделать, а не о том, как вы начали пытаться решить эту проблему (что неверно, поскольку вы ошибочно предполагаете, что следующие узлы всегда находятся в правильном поддереве, что не является true) Чтобы получить какой-то итератор в вашем дереве и найти запись, затем идите дальше и дальше, пока не дойдете до другого конца.

Поскольку двоичное дерево не является такой структурой, как простое дерево, представленное в другом ответе:

    4
   / \
  2   6
 / \ / \
1  3 5  7

предположим, вам нужно перейти от узла 3 к узлу 6. Я попытаюсь предоставить алгоритм, который позволяет мне перемещаться от одного узла к другому, учитывая только характеристики узла, на котором я сейчас нахожусь. Следующим за узлом 3 является узел 4, который требует от нас подняться на левых родителей (левый родитель будет узлом, для которого мы являемся правым потомком), пока мы не дойдем до правого родителя (аналогично, правый родитель - это узел, для которого мы являемся левым потомком). Это приводит нас к следующему узлу, у которого нет правого поддерева.

Попав в узел 4, у нас есть правое поддерево, поэтому следующий узел будет первым из этого правого поддерева, которое можно найти, следуя за левым дочерним элементом правого поддерева, пока мы не получим больше left дочерних элементов. Затем мы переходим к узлу 5.

В узле 5 у нас нет правого поддерева, поэтому мы должны подняться по дереву в поисках первого правого родителя. Это приводит нас к узлу 6. После этого все узлы превышают лимит, поэтому мы заканчиваем поиск.

В структуре, в которую вы включили положение для узла parent (в случае, если у вас его нет, вы должны включить стек узлов из корня дерева, чтобы получить родительский элемент), вы можете иметь следующие функции (у них есть не тестировалось, как написано здесь, это только подсказка, и ее необходимо протестировать, оставив ее в качестве упражнения для читателя)

struct node {
    int key;
    struct node *parent, *left, *right;
    /* ... more stuff */
};

/* this routine returns the parent node if it is a left
 * parent, NULL otherwise (covers the no parent case for
 * the root node */
struct node *left_parent(struct node *n)
{
    if (!n->parent) return NULL; /* no parent */
    if (n->parent->left == n) return NULL; /* this is a right parent */
    return n->parent;
}

/* this routine returns the parent node if it is a right
 * parent, NULL otherwise (covers the no parent case for
 * the root node */
struct node *right_parent(struct node *n)
{
    if (!n->parent) return NULL;
    if (n->parent->right == n) return NULL; /* this is a left parent */
    return n->parent;
}

/* get the first key node of the tree rooted at n */
struct node *first(struct node *n)
{
    while (n->left) n = n->left;
    return n;  /* this is the first node of the subtree rooted at n */
}

/* get the last key node of the tree rooted at n */
struct node *last(struct node *n)
{
    while (n->right) n = n->right;
    return n; /* this is the last node of the subtree rooted at n */
}

/* finds the next node, with the algorithm described in the text */
struct node *next(struct node *n)
{
    if (n->right) /* we have subnodes */
        return first(n->right);
    else { /* on the parents */
        struct node *nxt;
        while ((nxt = left_parent(n)) != NULL) n = nxt;
        return n->parent; /* can be a right parent or NULL only */
    }
}

/* finds the prev node, with the algorithm described in the text */
struct node *prev(struct node *n)
{
    if (n->left) /* we have subnodes */
        return last(n->left);
    else {
        struct node *prv;
        while ((prv = right_parent(n)) != NULL) n = prv;
        return n->parent; /* can be a left parent or NULL only */
    }
}

и наконец;

int first, last;
struct node *iterator, *root;
...
for (iterator = find(root, first); 
     iterator && iterator.key <= last; 
     iterator = next(iterator))
{
    do_something_to_iterator_node(iterator);
}

Что ж, это не так быстро, как следование связному списку, но почти, и у вас есть преимущество сортировки всего дерева.

ПРИМЕЧАНИЕ

На всякий случай, если вы хотите сэкономить место и не включать указатель parent, итератор преобразует один указатель в стек указателей узлов, из того, который мы сейчас посещаем, в корень дерева, и мы проверяем left_parent и right_parent, глядя на верхний узел (или рядом с ним, в зависимости от того, помещаете ли вы посещенный узел в стек или нет) узел.

person Luis Colorado    schedule 25.01.2017
comment
Очень хорошо! Я реализовал нечто подобное на основе реализации красно-черного дерева Бена Пфаффа, поскольку вы ошибочно полагаете, что следующие узлы всегда находятся в правом поддереве да, я знаю, я имел в виду, что посещение левого поддерева в рекурсивном режиме дает мне узлы, которые не находятся в диапазоне, и как тогда остановиться. - person David Ranieri; 25.01.2017
comment
Получил это с помощью рекурсивной функции, теперь она посещает только узлы диапазона. Я оставил ваш ответ принятым, потому что это оптимальное решение. - person David Ranieri; 25.01.2017
comment
У меня есть полная реализация деревьев AVL на C в github.com/mojadita/avl_c.git . Не стесняйтесь загружать, это открытый исходный код. - person Luis Colorado; 26.01.2017
comment
Выглядит отлично! Только один вопрос: почему вы используете циклический контроль избыточности в реализации avl? - person David Ranieri; 26.01.2017
comment
О, понятно, это необязательно #if USE_CRC, очень милый Луис! - person David Ranieri; 26.01.2017
comment
@KeineLust, который должен был проиллюстрировать использование, но может быть безопасно удален с помощью необязательного определения + - person Luis Colorado; 27.01.2017

Ваша функция rb_apply_node() принимает node аргумент, а затем применяет func() к этому узлу и каждому узлу под ним в дереве. Это не то же самое, что начинать с определенного узла и выполнять итерацию в отсортированном порядке. Для иллюстрации возьмем следующее дерево с элементами с 1 по 7:

      4
     / \
  2       6
 / \     / \
1   3   5   7

В вашей реализации, если вы хотите увидеть диапазон от 2 до 6, вы сначала ищите узел, содержащий 2. Пока все хорошо. Но тогда rb_apply_node() просто начнется с этого узла и будет действовать так, как 2 является корнем дерева, и будет спускаться только в этом поддереве, в результате чего:

1 2 3

Если вам нужен диапазон от 4 до 5, он будет начинаться с 4 и спускаться:

1 2 3 4 5

Единственная причина, по которой он остановится на 5, состоит в том, что ваш print_data() имеет проверку на пропуск чего-либо большего, чем конец желаемого диапазона; однако rb_apply_node() все равно продолжит повторение 6 и 7.

Быстрое решение - изменить rb_apply_node(), чтобы он всегда начинался с корня tree, но быстро пропускал первые узлы, пока не достигнет желаемого начала node.

person G. Sliepen    schedule 24.01.2017
comment
Спасибо за ваш ответ @ G.Sliepen, Если в вашей реализации вы хотите увидеть диапазон от 2 до 6, вы сначала ищите узел, содержащий 2. В этом случае (на основе вашего изображения) он начнется с 4, потому что 4 является родительским для 2 и 6 и находится в диапазоне [2 ... 6], см. range_comp: if ((ele >= arr[0]) && (ele <= arr[1])) { - person David Ranieri; 24.01.2017
comment
Быстрое решение - изменить rb_apply_node () так, чтобы он всегда начинался с корня дерева, но быстро пропускал первые узлы, пока не достигнет желаемого начального узла. Но это именно то, что я делаю, Я получаю первый узел в диапазоне, а затем использую его как корневой в rb_apply_node() - person David Ranieri; 24.01.2017
comment
@KeineLust: ну ладно, это то, что ты делаешь. Но представление rb_range() о первом узле - это всего лишь первый узел, прошедший по дереву, находящемуся в пределах диапазона. Обычно это не узел, который ближе всего к arr[0], но больше или равен ему. Таким образом, вам все равно нужно добавить код, чтобы пропустить все узлы в поддереве, которые меньше arr[0]. - person G. Sliepen; 24.01.2017