在红黑树中从节点A走到节点B的最快方法

时间:2017-01-24 11:13:19

标签: c algorithm red-black-tree

假设我想从节点#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;
}

inorder遍历:

/*
 * 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)?

2 个答案:

答案 0 :(得分:2)

首先,只考虑你要做的事情而不是你是如何开始尝试解决它的(这是错误的,因为你错误地认为下一个节点总是在正确的子树中,这不是真的)要在你的树上找到某种迭代器并找到一条记录,然后去下一步,接下来,直到你到达另一端。

由于二叉树不是一种结构,所以在其他答案中暴露出简单的树:

BeanDeserializerModifier

假设您必须从节点 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); } 指针,迭代器会从单个指针转换为一堆节点指针,从我们现在访问的那个指针到树的根,我们检查parentleft_parent,查看顶部节点(或者在顶部旁边,具体取决于是否在堆栈上推送受访节点)节点。

答案 1 :(得分:1)

您的函数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