【问题标题】:Red-Black Tree rebalancing crashes on tree rotation红黑树重新平衡在树旋转时崩溃
【发布时间】:2019-09-07 02:13:03
【问题描述】:

我正在实现一棵红黑树。目前卡在树的旋转上。当我旋转并分配新的左/右孩子时,我崩溃并燃烧。我学会在二叉树上进行左右旋转的方式是这样的(在 C++ 中):

void right_rotation(node *&root)
{
    auto *temp = root->left;
    root->left = temp->right;
    temp->right = root;
    root = temp;
}

这适用于 AVL 树。

这是 RB 树。我将尝试发布重现此内容所需的最低限度

#include <stdio.h>

struct foo
{
    int key;
    foo *parent;
    foo *left;
    foo *right;
    int rb; // 0 black, 1 red

    foo(int k, foo *p, foo *l, foo *r, int _rb) : key(k), parent(p), left(l), right(r), rb(_rb) {}
};

class rbtree
{
public:
    foo *root{};
    void insert(int key)
    {
        if (root != nullptr)
            insert(root, root, key);
        else
            root = new foo(key, nullptr, nullptr, nullptr, 0);
    }

    void insert(foo *&node, foo *&parent, int key)
    {
        if (!node) {
            node = new foo(key, parent, nullptr, nullptr, 1);
            rebalance(node);
        } else if (key <= node->key) {
            insert(node->left, node, key);
        } else {
            insert(node->right, node, key);
        }
    }

    void rebalance(foo *&node)
    {
        if (!node)
            return;

        if (root == node) {
            root->rb = 0;
            return;
        }

        if (node->rb == 1 && node->parent->rb == 1) {
            auto *grand_p = node->parent->parent;
            foo *aunt;

            if (grand_p->left != node->parent)
                aunt = grand_p->left;
            else
                aunt = grand_p->right;

            if (!aunt || aunt->rb == 0)
                rotate(node, grand_p);
            else
                color_flip(node);
        }

        // if there is no parent to the root
        if (!node->parent)
            root = node;

        rebalance(node->parent);
    }

    void rotate(foo *&node, foo *&grand_parent)
    {
        if (grand_parent->right->left == node) {
            right_left_rot(node);
        } // else the rest is not critical
    }

    void right_rot(foo *&root)
    {
        auto *grand_p = root->parent;
        auto *tmp = root->left;
        if (!tmp->left)
            printf("\nI am about to crash");
        root->left = tmp->right; // segfault here
        // the rest of the rotation logic
        tmp->right = root;
        root->parent = tmp;
        if (root->left)
            root->left->parent = root;
        if (grand_p) {
            if (root == grand_p->left)
                grand_p->left = tmp;
            else if (root == grand_p->right)
                grand_p->right = tmp;
        }
        tmp->parent = grand_p;
    }

    void right_left_rot(foo *&node)
    {
        right_rot(node->parent);
        // rest not important
    }

    void color_flip(foo *&node)
    {
        node->parent->parent->rb = 1;
        node->parent->parent->left->rb = 0;
        node->parent->parent->right->rb = 0;
        if (root->rb != 0)
            root->rb = 0;
    }
};

int main()
{
    rbtree rbt;
    rbt.insert(3);
    printf("\n%s%d", "Added successfully ", 3);
    rbt.insert(1);
    printf("\n%s%d", "Added successfully ", 1);
    rbt.insert(5);
    printf("\n%s%d", "Added successfully ", 5);
    rbt.insert(7);
    printf("\n%s%d", "Added successfully ", 7);
    rbt.insert(6);
    printf("\n%s%d", "Added successfully ", 6);
    return 0;
}

据我所知,tmp-&gt;leftnullptr,因此当我将其分配给root-&gt;left 时,出现段错误是正常的。我该如何克服这个问题并执行此步骤而不终止?

我搜索过 SO 和其他互联网角落,发现人们使用这种方法,他们没有抱怨这个段错误。

如果我检查if (tmp-&gt;right) root-&gt;left = tmp-&gt;right;,那么代码不会被执行,我会跳过一个关键的算法步骤。通过这个if 语句,我得到了一棵树,其中一些节点指向自己,它变得非常混乱。

示例案例

为了得到这种情况,我插入 3(root)->1(go left of 3)->5(go right of 3)->7(go right of 5)->6(go left of 7) .必须在 5->7->6 处进行平衡。逻辑是进行左右旋转。在正确的轮换中,这种情况正在发生

【问题讨论】:

  • 对于这类事情,我发现纸上调试很有帮助。
  • 在失败步骤之前打印你的树,检查你的假设是否正确
  • 如果没有一个完整的例子让我们自己看到崩溃,我认为我们无法为您提供太多帮助(另请参阅Minimal, Complete, and Verifiable example
  • 如果您在指定位置遇到段错误,则 tmp 为空)并且 tmp->right 无意义)。尝试将空节点旋转到任何其他节点的父位置表明您在确定您将哪个节点视为根节点以调用旋转函数的逻辑中存在错误。除此之外,如上所述,我们需要一个 MCV 示例。
  • 很抱歉回复晚了,也很抱歉没有提供足够的信息。我希望编辑更清楚。

标签: c++ red-black-tree red-black-tree-insertion


【解决方案1】:

rebalance 唯一应该重申的情况是阿姨是红色的,在这种情况下,下一个要处理的节点将是祖父节点,而不是父节点。如果阿姨是黑色的,那么在单轮或双轮之后你就完成了。

记住,插入逻辑是:

insert as normal for any BST
set new node's color to red
LOOP:
if node is root then set node's color black and done
if node's parent's color is black then done
if node's aunt's color is red then set node's parent's and node's aunt's color to black, set node's grandparent's color to red, set node to node's grandparent and go to LOOP
if node is left child of right child or right child of left child then rotate node's parent so it is child to node otherwise set node to node's parent
set node's color to black
set node's parent's color to red
rotate so that node's parent is child to node
done

您似乎根本没有“如果节点是右孩子的左孩子或左孩子的右孩子,则旋转节点的父节点,因此它是子节点,否则将节点设置为节点的父节点”步骤。

即使是最后一步,您也不会交换节点及其父节点的颜色(注意在此阶段“节点”是红色违规的父节点,而不是在可选旋转之前开始的子节点)。

另外,你有:

if (!aunt || aunt->rb == 0)

然后马上旋转,阿姨是黑色的情况是你应该颜色翻转,而不是旋转。

【讨论】:

  • 没错,我没有那个逻辑。我试图为这个问题提供一个最小的运行示例。当你陈述规则时你是正确的,但我遇到的问题不是旋转逻辑,而是指针及其引用。在root-&gt;left = tmp-&gt;right; 上它会因为tmp-&gt;right 为空而崩溃,这是可以理解的,但它不应该是这样的,在其他实现中它与该指针逻辑一起工作。
  • 您在该位置获得一个空节点这一事实意味着您在此之前的逻辑中某处有错误(甚至可能在之前的插入中,这就是为什么您被告知打印出您的树并检查 RB 树条件是否已实际满足)。它出现在轮换中只是一个症状。您当前的问题在其他地方。旋转功能可能有问题,也可能没有问题,我没有特别仔细检查,因为您没有在我评论的代码中要求在正确的条件下进行旋转。
【解决方案2】:

好的,经过大量测试后,我发现了一个有趣的案例,上面的代码可以正常工作。最值得注意的是,auto *grand_p = root-&gt;parent; 行导致了问题。如果我创建一个类似的方法

foo *rbtree::parent(foo *root)
{
    return *&root->parent;
}

然后通过这个方法调用访问父级

auto *grand_p = parent(root);

那么就不会有段错误,树的整个旋转将完全按照它应该的那样。

由于我对编译器优化和底层如何处理引用的知识有限,我会假设以下情况正在发生。

在这两种情况下,我都获得了指向祖父变量的父指针的副本,但是当这是通过函数完成时,编译器不会进行任何优化和取消引用,这会导致段错误。

这是另一个有similar topic的问题

【讨论】:

    猜你喜欢
    • 2015-08-10
    • 2015-04-16
    • 1970-01-01
    • 2018-06-30
    • 2013-10-13
    • 2013-11-11
    • 2018-11-07
    • 2020-07-25
    • 2021-12-13
    相关资源
    最近更新 更多