红黑树

2023/04/29 数据结构与算法 共 4538 字,约 13 分钟

上机实践报告

一、目的

1.熟悉算法设计的基本思想

2.掌握构建红黑树的方法

二、内容与设计思想

  1. 编写红黑树构建算法,中序遍历各节点,输出颜色和值;

  2. 使用红黑树构建算法,并画图描述不同情况下的运行时间差异;

三、使用环境

推荐使用C/C++集成编译环境。

[oj《算法设计与分析》第四次实验课](http://47.100.233.213/contest/87) (密码:40088860)

四、实验过程

1. 写出红黑树构建算法的源代码

#include <bits/stdc++.h>
using namespace std;
#define RED 0
#define BLACK 1

//定义红黑树结点
struct Node
{
    char color;   //颜色
    int key;      //值
    Node *lchild; //左孩子
    Node *rchild; //右孩子
    Node *parent; //父结点
};

class RBtree
{
public:
    //新建一个结点
    Node *creat_root(int key, Node *parent, Node *lchild, Node *rchild)
    {
        Node *p;
        p = new Node();
        p->key = key;
        p->lchild = lchild;
        p->rchild = rchild;
        p->color = BLACK;
        return p;
    }

    //左旋——将该结点变成其右孩子的左孩子
    void rbtree_left_rotate(Node *&root, Node *x)
    {
        Node *y = x->rchild; //设置x的右结点等于y

        //首先,先找到y的左孩子,它最终被x收养为右孩子
        x->rchild = y->lchild;
        if (y->lchild != NULL)
            y->lchild->parent = x;

        //将x的父亲设为y的父亲
        y->parent = x->parent;

        if (x->parent == NULL) //当x为根结点的时候
        {
            root = y; //将y设为根结点
        }
        else //当x不是根节点的时候
        {
            if (x->parent->lchild == x)
            {
                x->parent->lchild = y; // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
            }
            else
            {
                x->parent->rchild = y; // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
            }
        }
        y->lchild = x; //x就位
        x->parent = y;
        //printf("(对关键字%d进行左旋)",x->key);
    }

    //右旋
    void rbtree_right_rotate(Node *&root, Node *y)
    {
        Node *x = y->lchild;

        y->lchild = x->rchild;
        //找到x的右孩子,它最终被y收养为左孩子
        if (x->rchild != NULL)
        {
            x->rchild->parent = y;
        }
        x->parent = y->parent;
        //此时x的右孩子是空的,y来当x的右孩子
        if (y->parent == NULL) //如果y为根结点
        {
            root = x; //将x设为根节点
        }
        else
        {
            if (y->parent->rchild == y) //要确定x是做的左孩子还是右孩子
            {
                y->parent->rchild = x;
            }
            else
            {
                y->parent->lchild = x;
            }
        }
        x->rchild = y; //y就位
        y->parent = x;
    }

    //插入修正 —— 先向该树直接插入一个结点,设置为红色,为了不影响性质4,再进行调整
    void rbtree_insert_fixup(Node *&root, Node *node)
    {
        Node *parent, *gparent;
        // 若父节点存在,并且父节点的颜色是红色
        while ((parent = node->parent) && (parent->color == RED))
        {
            gparent = parent->parent;

            //若“父节点”是“祖父节点的左孩子”
            if (parent == gparent->lchild)
            {
                // Case 1条件:叔叔节点是红色
                {
                    Node *uncle = gparent->rchild;
                    if (uncle && uncle->color == RED)
                    { //父、叔变黑,爷变红,对爷进行判断
                        uncle->color = BLACK;
                        parent->color = BLACK;
                        gparent->color = RED;
                        node = gparent;
                        continue;
                    }
                }

                // Case 2条件:叔叔是黑色,且当前节点是右孩子
                if (parent->rchild == node)
                {
                    Node *tmp;
                    rbtree_left_rotate(root, parent); //父左旋
                    tmp = parent;
                    parent = node;
                    node = tmp;
                }

                // Case 3条件:叔叔是黑色,且当前节点是左孩子。
                parent->color = BLACK;
                gparent->color = RED;
                rbtree_right_rotate(root, gparent);
            }
            else //若“z的父节点”是“z的祖父节点的右孩子”
            {
                // Case 1条件:叔叔节点是红色
                {
                    Node *uncle = gparent->lchild;
                    if (uncle && (uncle->color == RED))
                    {
                        uncle->color = BLACK;
                        parent->color = BLACK;
                        gparent->color = RED;
                        node = gparent;
                        continue;
                    }
                }

                // Case 2条件:叔叔是黑色,且当前节点是左孩子
                if (parent->lchild == node)
                {
                    Node *tmp;
                    rbtree_right_rotate(root, parent);
                    tmp = parent;
                    parent = node;
                    node = tmp;
                }

                // Case 3条件:叔叔是黑色,且当前节点是右孩子。
                parent->color = BLACK;
                gparent->color = RED;
                rbtree_left_rotate(root, gparent);
            }
        }

        // 将根节点设为黑色
        root->color = BLACK;
    }

    //插入
    void rbtree_insert(Node *&root, Node *node)
    {
        Node *y = NULL;
        Node *x = root;

        while (x != NULL) //x为叶子结点跳出循环
        {
            y = x;
            if (x->key > node->key)
            {
                x = x->lchild;
            }
            else
            {
                x = x->rchild;
            }
        }
        node->parent = y;

        if (y != NULL)
        {
            if (node->key < y->key)
            {
                y->lchild = node;
            }
            else
            {
                y->rchild = node;
            }
        }
        else
        {
            root = node; //若y为NULL,说明树为空,则将node设为根节点
        }

        node->color = RED; //将颜色设为红色

        //插入修正
        rbtree_insert_fixup(root, node);
    }

    int insert_rbtree(Node *&root, int key)
    {
        Node *node; //新建一个结点
        node = creat_root(key, NULL, NULL, NULL);
        if (node == NULL)
            return -1;
        else
            rbtree_insert(root, node);
        return 0;
    }

    //中序遍历"红黑树"
    void inorder(Node *root)
    {
        if (root != NULL)
        {
            inorder(root->lchild);
            //cout << root->key<<" ";
            if (root->color == 0)
            {
                cout << "R";
            }
            else
            {
                cout << "B";
            }
            inorder(root->rchild);
        }
    }
};

int main()
{
    int N;
    cin >> N;
    int a[N];
    RBtree mytree;
    //下面开始创建红黑树
    Node *root = new Node();
    cin >> a[0];
    root = mytree.creat_root(a[0], NULL, NULL, NULL);
    //然后插入
    for (int i = 1; i < N; i++)
    {
        cin >> a[i];
        mytree.insert_rbtree(root, a[i]);
    }
    mytree.inorder(root);
    return 0;
}

2. 分别画出各个实验结果的折线图

oom killed pod

五、总结

​ 由实践结果——图表可以看出,随着数据规模即节点数增大,运行时间也随着增加。这次红黑树的实验主要是回归算法本身,该实验主要用到函数就是rbtree_insert(插入)和rbtree_insert_fixup(调整),rbtree_insert主要是为了得到一颗二叉查找树,rbtree_insert_fixup是为了使其满足红黑树的性质,成为红黑树,在其中还用到了左旋和右旋函数。由于一颗有n个结点的红黑树的高度为O(lgn),所以在rbtree_insert函数最后一步之前,要花费O(lgn)时间;在rbtree_insert_fixup函数中,仅当情况1发生后,node指针会沿着树上升两层,while循环才会重复执行。所以while循环可能被执行的总次数为O(lgn)。因此,rbtree_insert总共花费O(lgn)的时间。此外,该程序的旋转从不超过2次,因为只执行了情况2或情况3,while循环就结束了。由算法原理可以看出实验结果是符合的。

​ 自己一开始运行一直不通过出错的原因在于,当遇到情况三也就是在右旋之前,忘记先把当前节点的父结点由红变为黑色,祖父结点由黑变为红色,所以运行结果一直不正确。通过这次实验是自己对红黑树有了更深的理解。

​ 关于算法优化方面,在网上看到了一个优化思路为:

   书上思路为从根到叶子进行遍历,寻找到插入位置,直接将新数据染红并插入,最后进行调整以符合红黑树性质。调整分两大类:1是父和叔均红,解决方案为将祖父由黑变红,父和叔由红变黑,然后将祖父结点作为新的问题,也就是将问题上移两层,最坏情况递归到根才能解决。2是叔为黑,此时可通过最多两次旋转直接解决。 因为在查找插入位置时,要从根到叶子进行遍历,如果此时检查路径上是否存在两个孩子均为红的情况并解决,则在FIXUP的函数中可以避免父和叔均红的情况,使得FIXUP只需要通过O(1)的时间即可完成。

​ 但这样修改后我发现时间运行并没有很大差别,修改后虽然省去了向上递归的消耗,但旋转的次数也增多了。

文档信息

Search

    Table of Contents