手撕红黑树(kv模型模拟)

目录

前言

一、相关概念

二、性质介绍

红黑树平衡说明

三、红黑树模拟(kv结构)

1、红黑树节点

2、红黑树插入

2、特殊处理情况

声明:

情况一:cur为红,p为红,g为黑,u存在,且uncle为红色

情况二:cur为红,p为红,g为黑,u不存在

情况三:cur为红,p为红,g为黑,u存在且为黑

总结

四、红黑树的验证

1、检查中序

2、检测红黑树性质

3、调试

五、红黑树的性能

1、测试函数

2、随机值测试

3、AVL,红黑树对比测试

管理随机数据

管理有序数据

总结

结束语

附录1(红黑树模拟代码)

附录2(红黑树测试代码)


前言

        本篇博客介绍红黑树结构,通过模拟红黑树的插入,来深入理解红黑树的结构以及调整规则。

一、相关概念

        红黑树,也是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的,特别注意不是和AVL一样的绝对平衡


二、性质介绍

(1). 每个结点不是红色就是黑色,只有两种颜色的节点

(2). 根节点是黑色的

(3). 如果一个节点是红色的,则它的两个孩子结点是黑色的(无连续红色节点

(4). 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点(核心

(5). 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

        满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍。(路径是指从根节点走到空节点的路程)

红黑树平衡说明

        我们可以通过分析极端情况(不一定存在)下的现象来得到上述结论,即最短路径为全是黑色黑色节点,最长路径为一黑一红节点排列,此时我们可以发现最长路径是最短路径的两倍长度,但最长路径与最短路径的极端情况不一定会出现,极端情况都满足则说明所有情况都是满足其最长路径中节点个数不会超过最短路径节点个数的两倍。


三、红黑树模拟(kv结构)

1、红黑树节点

        包含节点的父节点指针(方便对于红黑树进行符合其性质的调整),左孩子指针右孩子指针颜色属性节点数据

        注意对于新初始化的节点默认颜色为红色。本来这个树的每条路径黑色节点数是相同的,假设你选定黑色节点的话,无论黑色节点添加在哪里,新增节点的路径上的黑色节点会加一,红黑树要求每个路径黑色节点数相同,树中所有路径都会受到影响,大面积的影响处理起来非常困难。假设你选择的是一个红色节点,如果添加在黑色节点下面,则整棵树仍然满足性质,不需要改变,添加在红色节点下面才需要处理(不能连续出现红色节点),直解决当前路径下出现的问题,大大化简了对红黑树的调整过程。

2、红黑树插入

        由于红黑树仍然是一颗搜索二叉树,所以按照二叉搜索的树规则插入新节点,具体的插入规则之前文章有详解,本篇文章主要讲述红黑树结构及其节点调整原理,对于之前的只是就不在赘述。

        假定插入节点前,树满足红黑树性质,插入节点完成后,要保证其最长路径中节点个数不会超过最短路径节点个数的两倍我们就要使这棵树仍然保持有红黑树的性质,要对红黑树性质遭到波坏的插入情况进行相应的调整。

2、特殊处理情况

        插入位置的父亲节点是黑色节点,父亲节点为黑色,插入红色节点后,红黑树的性质仍然存在,不需要处理。

        插入位置的父亲节点是红色节点,有连续红色节点,需要特殊处理,维护整棵树以及每一颗子树的红黑树性质。

        由于插入节点前树是满足红黑树性质的,那么根节点不可能是红色的,此时一定存在爷爷节点,且原来没有连续红色节点,所以爷爷节点一定为黑色。由于处理可能会一直向上更新到根节点,所以采用循环处理(先知道,后面会解释)。而对于这种问题的处理,有着不同的情况与处理方法,关键在uncle节点

声明:

        在此之前,我们先统一几个变量,以及相关声明

(1)、cur为当前节点,

(2)、父节点,parent,简称p

(3)、祖父节点,grandfather,简称g

(4)、叔叔节点,uncle,简称u

        声明:我们无法列举出所有的树形结构,以为树的结构具有多样性,对于子树衍生情况非常多,无法针对每一种树形结构定制处理办法。但是我们可以总结某一大类(树形结构)的解决办法,在这种大类下不破坏红黑树性质而衍生出来的各种树形结构,都可以使用这种方法处理。所以以下情况均是树形结构的大类,在其基础上可以有树的衍生,但必须是不破坏红黑树性质的合理衍生。

情况一:cur为红,p为红,g为黑,u存在,且uncle为红色

        我们不能将cur变黑,这样的话就相当于是插入一个黑色节点,其弊端在上文中已阐述。

        解决办法:将parent,uncle变黑色,grandfather变红色,如下图

当前大类所代表的树当作一颗完整的树,或是一颗子树(g可能是根节点,也可能不是)。

        如果g节点是根节点,那么调整完成后,将g改成黑色节点,如果是子树,g仍然有父节点,那么调整完成后,将g改成黑色节点。并且继续向上处理,把grandfather节点当作cur节点继续向上更新。因为g原来是黑色的,根据红黑树性质推得,如果它有父节点,那么他的父节点一定是红色的,当我们最终将g节点变为红色,那么又会出现连续得红色的节点,与性质相冲突。需要我们继续把g节点当作cur节点来继续更新颜色,解决这颗树出现的问题。

        在极端情况下可能出现,一直更新到根节点的情况,这也是我们为什么我们把由采用循环条件处理的原因(后面还会详细解释)。

        连续向上更新举例:

补充:

循环条件的解释:

        1、只有cur的p节点为红色时,才需要特殊处理,维护红黑树性质。在不断向上更新的时候,如果cur的节点为黑色,就相当于在当前节点下,添加了一个黑色节点,这正是我们不需要特殊处理的情况,此时便退出循环。

        2、当更新到根节点时候,parent节点不存在也要退出循环。

特别注意: 这种情况下,不关心cur,parent的相对位置无论parent的左孩子还是右孩子,cur是左孩子还是右孩子,处理方法都一样

情况二:cur为红,p为红,g为黑,u不存在

当u不存在时,cur一定是新插入的节点

        如果cur不是新插入的节点,而是经过一次处理更新上来的红色节点,根据情况一的更新方法判断cur为节点的子树一定有黑色节点,这样的话无uncle的那条路径上的黑色节点数就会小于cur所在的路径上的黑色节点数,不满足性质4,所以可以确定cur就是新增节点

在这种情况下个根据p节点与cur节点所处相对位置的不同,又划分为四种子情况

(1)、p为g的左孩子,cur为p的左孩子

右旋g,g变红,p变黑

(2)、p为g的左孩子,cur为p的右孩子

左旋p,右旋g,cur变黑,g变红

(3)、p为g的右孩子,cur为p的左孩子

右旋p,左旋g,cur变黑,g变红

(4)、p为g的右孩子,cur为p的右孩子

左旋g,g变红,p变黑

(对于左右旋的具体方法参考本人博客《AVL树模拟》,有很详细的介绍)

经过上述处理,以上情况对应变化为下图:

情况三:cur为红,p为红,g为黑,u存在且为黑

        如果cur是新插节点,那么在插入之前这棵树就已经违反了性质4,如上图增加cur之前,p节点所在的路径与u节点所在的路径黑色节点数并不相同,所以这种猜想是错误的。我们可以断定这种情况不是插入节点得到的树的结构,p节点所在路径的黑色节点等于u节点所在路径的黑色节点数,那么可以知道cur的子树中的路径应该是有1个黑色节点即分别在a,b的衍生中有且只有一个黑色节点),推测为是其他结构下插入新节点后,向上更新产生的一种情况,简单来说就是cur节点在插入之前是黑色的,被更新成了红色产生的新情况。如下是一种该情况产生的举例,当然还有其他情况(左图的u,p节点的子节点位置插入新节点)

当cur,p所处的相对位置不同,对应的处理方式又会不同,这里分化为四种子情况:

(1)、p为g的左孩子,cur为p的左孩子

右旋g,g变红,p变黑

(2)、p为g的左孩子,cur为p的右孩子

左旋p,右旋g,cur变黑,g变红

(3)、p为g的右孩子,cur为p的左孩子

右旋p,左旋g,cur变黑,g变红

(4)、p为g的右孩子,cur为p的右孩子

左旋g,g变红,p变黑

        在这里我们模拟第一种和第二种子情况的变换,第三种第四种变换和第一种第二种变换呈对称变换,可以尝试自己画一下。

第一种子类型:

第二种子类型:

总结

        结合情况二与情况三,我们可以发现连续红色节点出现时,p,cur节点相对位置相同(即子情况相同),无论是大条件是uncle不存在,还是uncle为黑色,他们对应相同子情况的解决办法都是相同的,对于节点的变色方法也相同,于是我们将二者合二为一。即cur为红,p为红,g为黑,u存在且为黑或者uncle不存在为一种情况。当cur,p所处的相对位置不同,产生的不同的子情况方法也对应合并:

(1)、p为g的左孩子,cur为p的左孩子

右旋g,g变红,p变黑

(2)、p为g的左孩子,cur为p的右孩子

左旋p,右旋g,cur变黑,g变红

(3)、p为g的右孩子,cur为p的左孩子

右旋p,左旋g,cur变黑,g变红

(4)、p为g的右孩子,cur为p的右孩子

左旋g,g变红,p变黑

注意:

        在情况二和情况三合并的情况处理之后,我们可以发现以子树的根节点开始的路径,插入前后路径上的黑色节点保持不变,而且处理之后这颗子树的根是黑色的,并不破环整棵树的红黑树性质,于是到这里我们就不需要继续向上更新了,在这里就直接退出这颗树的特殊处理(退出循环)


四、红黑树的验证

1、检查中序

        判断是否为有序排列,红黑树本身就是一颗搜索二叉树,所以必须要满足中序为有序排列。但仅此并不能说明它是一颗红黑树,我们必须要验证其红黑树的性质

2、检测红黑树性质

1、检查根节点是不是黑色

2、检查是否存在连红,遍历检查每个红色节点的parent节点,如果存在parent为红,那么就存在连续红色节点

3、检查每条路径的黑色节点数:

(1)求最左路径黑色节点数作为基准值

(2)递归记录当前节点下路径的黑色节点数

(3)走到空节点的时候判断,与基准值比较,看黑色节点数是否相同

细节:

        1、如果根为红色,提前返回错误

        2、以根节点开始向下不断递归展开,展开得到树中的每一条路径,并且记录着每条路径的黑色节点数;在展开的过程中会遍历到每一个节点,所以对连续红色节点的检测也结合在递归过程中,不需要额外遍历一次去检查连续红色节点的问题。

        3、递归子函数中,增加对黑色节点记录的参数(这是从根节点到当前节点的黑色节点数),如果节点为黑色则要对其+1带到更下一层的递归。特别注意这里不能使用引用参数,否则下一层黑色节点的更新会影响到上一层的计数,那么上一层再向其他子树递归展开时计数就会错误。在这里我们希望的是下一层的黑色节点数增加不会影响到上一层

3、调试

        和AVL树调试相同,插入一个值后就判断一次平衡,找到最先不平衡的插入点。之后再设置对应断点,逐步调试去查找问题。


五、红黑树的性能

1、测试函数

        为了对红黑树的性能有直观体现,我们又增加了一些函数来反映出他的效率,如树的高度,节点个数,查找效率,旋转调整次数等等。

树的高度

节点个数

查找函数

旋转次数

2、随机值测试

        对于千万级数据,插入效率与比较效率还不错

3、AVL,红黑树对比测试

        向AVL分别插入一千万个数据,对比效率(随机值,有序值)

管理随机数据

1、红黑树高度略高与AVL

2、AVL插入效率略高于红黑树,但两者效率在一个层次(都是log_2 N)

3、查找,红黑树肯定吃亏,因为AVL对于平衡的要求更加严格

4、红黑树的优势主要在,大大减少了旋转次数

管理有序数据

如果插入的是有序值,AVL更好一点,但实际中很少插入有序数列,参考意义不大

总结

        红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log_2 N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多,更多应用的是红黑树!!!


结束语

        本篇文章的内容就到此结束了,希望通过这篇博客大家能有所收获,有什么内容不明白的,大家可以在评论去向我提问,我会一一回答,当然有什么错误或者有什么不足的地方,希望大家可以包容并指出,后续还会更新对于红黑树的具体应用,红黑树迭代器模拟,封装map与set,在博客《红黑树大能量——map,set模拟》中,希望大家可以持续关注,向每一位读者送上真诚的小花。

        

附录1(红黑树模拟代码)

#pragma once
enum Color { RED, BLACK };

template <class K, class V>
struct RBTreeNode {
    typedef RBTreeNode<K, V> Node;

    Node* _parent;
    Node* _left;
    Node* _right;
    Color _col;
    pair<K, V> _kv;

    RBTreeNode(const pair<K, V>& kv)
        :_parent(nullptr),
        _left(nullptr),
        _right(nullptr),
        _col(RED),//新插入节点为红色,初始化节点时,直接给红色
        //如果给黑色,会影响到所有路径
        _kv(kv)
    {}
};

template<class K, class V>
class RBTree {
    typedef RBTreeNode<K, V> Node;
public:
    bool Insert(const pair<K, V>& kv) {
        if (_root == nullptr) 
        {
            _root = new Node(kv);
            _root->_col = BLACK;
            return true;
        }
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            if (kv.first > cur->_kv.first)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (kv.first < cur->_kv.first)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
                return false;
        }
        //插入新节点,并且与parent进行链接
        cur = new Node(kv);
        if (kv.first < parent->_kv.first)
        {
            parent->_left = cur;
        }
        else
        {
            parent->_right = cur;
        }
        cur->_parent = parent;

        //父亲节点为黑色,插入成功,不需要处理(不进入循环)
        //if (parent->_col == BLACK)
        //    return true;
 

        //父亲节点为红色,有连续红色,需要特殊处理,此时一定存在爷爷节点

        Node* grandfather = nullptr; 
        Node* uncle = nullptr;
        while (parent && parent->_col == RED)
        {
            //父亲节点为红色,有连续红色,需要特殊处理,此时一定存在爷爷节点
            grandfather = parent->_parent;
            //一、parent在grandfather的左边,说明uncle在右边
            if (parent == grandfather->_left)
            {
                uncle = grandfather->_right;

                //1、uncle存在,且uncle为红色
                //parent,uncle变黑色,grandfather变红色,并且继续向上处理
                //*********   不关心uncle的方向   *******
                if (uncle && uncle->_col == RED)
                {
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;

                    //需要继续向上处理,直到退出循环;
                    cur = grandfather;
                    parent = cur->_parent;
                }
                //2、uncle不存在,或者uncle存在且为黑色,        利用旋转的方式处理,
                //**********   uncle左右位置不同,处理方式不同
                else
                {
                    //(1)cur是parent的左边,右旋grandfather,
                    // 变色:p变黑,g变红
                    //          g
                    //       p     u
                    //     c
                    if (cur == parent->_left)
                    {
                        RotateR(grandfather);
                        grandfather->_col = RED;
                        parent->_col = BLACK;
                    }

                    //(2)cur是parent的有右边,左旋parent,右旋grandfather
                    //变色: c变黑,g变红
                    //          g
                    //       p     u
                    //         c
                    else
                    {
                        RotateL(parent);
                        RotateR(grandfather);
                        grandfather->_col = RED;
                        cur->_col = BLACK;
                    }

                    //旋转后以grandfather为根的子树每条路径的黑色节点数与没插入之前相同,不影响其他路径,不继续向上处理,退出循环
                    //处理完根是黑的,直接退出;
                    break;
                }
            }
            //二、parent在grandfather的右边,说明uncle在左边
            else
            {
                uncle = grandfather->_left;

                //1、uncle存在,且uncle为红色
                //parent,uncle变黑色,grandfather变红色,并且继续向上处理
                //*********   不关心uncle的方向   *******
                if (uncle && uncle->_col == RED)
                {
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;

                    //需要继续向上处理,直到退出循环;
                    cur = grandfather;
                    parent = cur->_parent;
                }
                //2、uncle不存在,或者uncle存在且为黑色              利用旋转的方式处理
                //**********   uncle左右位置不同,处理方式不同
                else
                {
                    //(1)cur是parent的右边,左旋grandfather,
                    // 变色:p变黑,g变红
                    //          g
                    //       u     p
                    //               c
                    if (cur == parent->_right)
                    {
                        RotateL(grandfather);
                        grandfather->_col = RED;
                        parent->_col = BLACK;
                    }

                    //(2)cur是parent的左边,右旋parent,左旋grandfather
                    //变色: c变黑,g变红
                    //          g
                    //       u     p
                    //           c
                    else
                    {
                        RotateR(parent);
                        RotateL(grandfather);
                        grandfather->_col = RED;
                        cur->_col = BLACK;
                    }
                    //旋转后以grandfather为根的子树每条路径的黑色节点数与没插入之前相同,不影响其他路径,不继续向上处理,退出循环;
                    //处理完根是黑的,直接退出;
                    break;
                }
            }
        }
        //最后让根节点的颜色一定为黑色,不在循环里面单独判断是否为根节点
        _root->_col = BLACK;
        return true;
    }
    void RotateL(Node * parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;

        //处理parent与subRL新关系的链接
        parent->_right = subRL;
        //subRL不为空,其父节点指向parent
        if (subRL)
            subRL->_parent = parent;

        //处理parent与subR新关系的链接
        subR->_left = parent;
        Node* ppnode = parent->_parent;//保存旧parent的父亲节点与subR节点链接
        parent->_parent = subR;

        //处理parent的父亲节点与subR节点链接
        //parent可能是根节点
        if (_root == parent)
        {
            _root = subR;
            subR->_parent = nullptr;
        }
        else
        {
            if (parent == ppnode->_left)
                ppnode->_left = subR;
            else
                ppnode->_right = subR;
            subR->_parent = ppnode;
        }
        ++Rotatesize;
    }
    //右旋
    void RotateR(Node * parent)
    {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;

        parent->_left = subLR;
        if (subLR)
            subLR->_parent = parent;

        subL->_right = parent;
        Node* ppnode = parent->_parent;
        parent->_parent = subL;

        if (parent == _root)
        {
            _root = subL;
            subL->_parent = nullptr;
        }
        else
        {
            if (ppnode->_left == parent)
            {
                ppnode->_left = subL;
            }
            else
            {
                ppnode->_right = subL;
            }
            subL->_parent = ppnode;
        }
        ++Rotatesize;
    }

    //中序遍历
    void Inorder()
    {
        _inorder(_root);
        cout << "end" << endl;
    }
    void _inorder(Node* root)
    {
        if (root == nullptr)
            return;
        _inorder(root->_left);
        cout << root->_kv.first << ":" << root->_kv.second << endl;
        _inorder(root->_right);
    }

    //红黑树规则测试
    //1、检查根节点是不是黑色
    //2、检查是否存在连红,遍历检查每个红色节点的parent节点,如果存在parent为红,那么就存在连红
    //3、检查每条路径的黑色节点数
    //(1)求最左路径黑色节点数作为基准值
    //(2)递归记录当前节点路径,黑色节点数
    //(3)走到空节点的时候判断,与基准值比较
    bool isBalance()
    {
        if (_root&&_root->_col == RED)
            return false;
        Node* cur = _root;
        int refblacknum = 0;
        while (cur)
        {
            if (cur->_col == BLACK)
            {
                ++refblacknum;
            }
            cur = cur->_left;
        }
            return check(_root, refblacknum, 0);
    }
    bool check(Node* root,int& refblacknum,int blacknum) //采用按值传参,下一层不影响上一层
    {
        if (root == nullptr)
        {
            if (blacknum == refblacknum)
            {
                return true;
            }
            cout << "黑色节点数不相等" << endl;
            return false;
        }
        if (root->_col == RED && root->_parent->_col == RED)
        {
            cout << root->_kv.first<<"有连续的红色节点" << endl;
            return false;
        }
        if (root->_col == BLACK)
            ++blacknum;
        return check(root->_left, refblacknum,blacknum) && check(root->_right, refblacknum, blacknum);
    }

    //求当前树的高度
    int Height()
    {
        return height(_root);
    }
    //求高度子函数
    int height(Node* root)
    {
        if (root == nullptr)
            return 0;
        int lefth = height(root->_left);
        int righth = height(root->_right);
        return lefth > righth ? lefth + 1 : righth + 1;
    }
    //树的节点个数
    size_t Size()
    {
        return _Size(_root);
    }
    size_t _Size(Node* root)
    {
        if (root == NULL)
            return 0;

        return _Size(root->_left)
            + _Size(root->_right) + 1;
    }


    //查找节点
    Node* Find(const K& key)
    {
        Node* cur = _root;
        while (cur)
        {
            if (cur->_kv.first < key)
            {
                cur = cur->_right;
            }
            else if (cur->_kv.first > key)
            {
                cur = cur->_left;
            }
            else
            {
                return cur;
            }
        }

        return NULL;
    }
    int GetRotatesize()
    {
        return Rotatesize;
    }
  
private:
    Node* _root = nullptr;
    int Rotatesize = 0;
    };

附录2(红黑树测试代码)

#include<iostream>
using namespace std;

#include<vector>
#include"RBTree.h"
#include"AVLtree.h"

void test1()
{
		//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
		int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,16, 3, 7, 11, 9, 26, 18, 14, 15 };
		RBTree<int, int> t;
		for (auto e : a)
		{
			if (e == 14)
			{
				int x = 0;
			}
			t.Insert(make_pair(e, e));
			//cout <<e<<"->" << t.isBalance() << endl;
		}

		t.Inorder();
		cout << t.isBalance() << endl;

}

void test2()
{
	const int N = 10000000;
	vector<int> v;
	v.reserve(N);
	srand((size_t)time(0));
	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);

	}

	size_t begin2 = clock();
	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}
	size_t end2 = clock();
	cout << "Insert:" << end2 - begin2 << endl;
	cout << t.isBalance() << endl;
	cout << "Height:" << t.Height() << endl;
	cout << "Size:" << t.Size() << endl;

	size_t begin1 = clock();
	// 确定在的值
	for (auto e : v)
	{
		t.Find(e);
	}

	// 随机值
	for (size_t i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}

	size_t end1 = clock();

	cout << "Find:" << end1 - begin1 << endl;
}

void test3()
{
	//调试出来的错误为,退出循环写错了位置,导致uncle为红色结束后直接退出了
	const int N = 20;
	vector<int> v;
	v.reserve(N);
	//srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand()%100);
	}

	RBTree<int, int> t;
	for (auto e : v)
	{
		if (e == 64)
		{
			int x = 0;
		}
		t.Insert(make_pair(e, e));
		cout <<e<<"->" <<t.isBalance() << endl;
	}
	cout << t.isBalance() << endl;
}


//1、红黑树高度略高与AVL
//2、AVL插入效率略高于红黑树,但两者效率再一个层次,
//3、如果插入的是有序值,AVL更好一点
//4、红黑树的优势主要在,大大减少了旋转次数
//5、查找,红黑树肯定吃亏
//*************************更多应用是红黑树
void RBTree_AVL_Compare()
{

	const int N = 10000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; i++)
	{
		//v.push_back(rand() + i);//随机值
		v.push_back(i);         //有序值
	}
	RBTree<int, int> t1;
	AVLTree<int, int> t2;

	size_t begin1 = clock();
	for (auto e : v)
	{
		t1.Insert(make_pair(e, e));
	}
	size_t end1 = clock();

	size_t begin2 = clock();
	for (auto e : v)
	{
		t2.Insert(make_pair(e, e));
	}
	size_t end2 = clock();

	size_t begin3 = clock();
	// 确定在的值
	for (auto e : v)
	{
		t1.Find(e);
	}

	// 随机值
	/*for (size_t i = 0; i < N; i++)
	{
		t1.Find((rand() + i));
	}*/

	size_t end3 = clock();

	size_t begin4 = clock();
	// 确定在的值
	for (auto e : v)
	{
		t2.Find(e);
	}

	// 随机值
	/*for (size_t i = 0; i < N; i++)
	{
		t2.Find((rand() + i));
	}*/

	size_t end4 = clock();


	cout << "RBTree RoateSize:" << t1.GetRotatesize() << endl;
	cout << "AVLTree RoateSize:" << t2.GetRotatesize() << endl << endl;


	cout << "RBTree Insert:" << end1 - begin1 << endl;
	cout << "AVLTree Insert:" << end2 - begin2 << endl << endl;

	cout << "RBTree IsBalance:" << t1.isBalance() << endl;
	cout << "AVLTree IsBalance:" << t2.isBalance() << endl << endl;

	cout << "RBTree Height:" << t1.Height() << endl;
	cout << "AVLTree Height:" << t2.Height() << endl<< endl;

	cout << "AVLTree Size:" << t2.Size() << endl ;
	cout << "RBTree Size:" << t1.Size() << endl << endl;

	cout << "RBTree Find:" << end3 - begin3 << endl;
	cout << "AVLTree Find:" << end4 - begin4 << endl<<endl;

}
int main()
{
	//test1();
	


	//test2();


	//test3();//调试出来了很隐蔽的错误

	RBTree_AVL_Compare();

	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/580939.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis 核心配置讲解(下)

大家好&#xff0c;我是王有志&#xff0c;一个分享硬核 Java 技术的互金摸鱼侠。 我们书接上回&#xff0c;继续聊 MyBatis 的核心配置&#xff0c;我们今天分享剩下的 5 项核心配置。 不过正式开始前&#xff0c;我会先纠正上一篇文章 MyBatis 核心配置讲解&#xff08;上&…

QAnything知识库问答系统离线部署(LLM+RAG)

一、QAnything介绍 &#xff08;一&#xff09;简介 QAnything 是网易有道开源的一个问答系统框架&#xff0c;支持私有化部署和SaaS服务两种调用形式。它能够支持多种格式的文件或数据库&#xff0c;提供准确、快速和可靠的问答体验。目前已支持的文件格式包括PDF、Word、PP…

防火墙对要保护的服务器做端口映射的好处是几个

防火墙对要保护的服务器进行端口映射具有多重好处&#xff0c;这些好处主要围绕网络安全性、灵活性和可管理性展开。以下是对这些好处的专业分析&#xff1a; 1. 增强网络安全性&#xff1a;端口映射允许防火墙对进入服务器的流量进行精确控制。通过映射特定端口&#xff0c;防…

FPGA秋招-笔记整理(3)无符号数、有符号数

参考&#xff1a;Verilog学习笔记——有符号数的乘法和加法 一、无符号数、有符号数 将输入输出全部定义为有符号数 &#xff08;1&#xff09;无符号数的读取按照原码进行&#xff0c;有符号数的读取应该按照补码读取&#xff0c;计算规则为去掉符号位后取反、加1在计算数值…

Flink学习(九)-jar 包提交给 flink 集群执行

一、界面执行 1&#xff0c;点击左侧的 submit new job&#xff0c;然后点击add New 2&#xff0c;粘贴程序入口&#xff0c;设置并行度 3&#xff0c;执行后&#xff0c;就可以在 taskManager 中找到相关任务了 二、控制台执行 在命令行中&#xff0c;在flink 的安装目录下&…

【Java】关于异常你需要知道的事情

文章目录 异常体系异常声明捕获多个异常Java中的哪些异常&#xff0c;程序不用捕获处理&#xff1f;【重要】try with resource 异常处理流程foreach中遇到异常面试题try和finally中都由return 异常体系 异常声明 如果声明的是Exception&#xff0c;那么必须要处理如果声明的是…

基于SpringBoot的合家云社区物业管理平台 - 项目介绍

合家云社区物业管理平台 2.合家云需求&设计 2.1 项目概述 2.1.1 项目介绍 合家云社区物业管理平台是一个全新的 ”智慧物业解决方案“&#xff0c;是一款互联网的专业社区物业管理系统。平台通过社区资产管理、小区管理、访客管理、在线报修、意见投诉等多种功能模块&a…

CSS详解(一)

1、css工作中使用场景 美化网页&#xff08;文字样式、背景样式、边框样式、盒子模型、定位、动画、&#xff09;&#xff0c;布局页面&#xff08;flex布局、响应式布局、媒体查询&#xff09; 2、CSS 规则 通常由两个主要部分组成选择器和样式声明 2.1选择器 选择器指定了…

Opencv | 边缘提取

目录 一. 边缘检测1. 边缘的定义2. Sobel算子 边缘提取3. Scharr算子 边缘提取4. Laplacian算子 边缘提取5. Canny 边缘检测算法5.1 计算梯度的强度及方向5.2 非极大值抑制5.3 双阈值检测5.4 抑制孤立弱边缘 二. 轮廓信息1. 获取轮廓信息2. 画轮廓 一. 边缘检测 1. 边缘的定义…

号卡流量卡分销推广系统源码

这是一个多功能的流量卡推广分销系统PHP源码&#xff0c;它是一套完善的、功能丰富的号卡分销系统&#xff0c;拥有多个接口&#xff0c;包括运营商接口&#xff0c;以及无限三级代理。这是目前市面上最优雅的号卡系统&#xff0c;没有之一。 软件架构说明&#xff1a; 环境要求…

网络原理(qq消息发送原理)

1.网络初识 IP地址 概念&#xff1a; IP地址主要⽤于标识⽹络主机、其他⽹络设备&#xff08;如路由器&#xff09;的⽹络地址。简单说&#xff0c;IP地址⽤于定位主机的⽹络地址。 就像我们发送快递⼀样&#xff0c;需要知道对⽅的收货地址&#xff0c;快递员才能将包裹送到…

多模态视觉大模型(2): 常用模型介绍(CLIP和LLAVA)

文章目录 1.CLIP 讲解1.1 clip 预训练过程1.2 利用clip进行图像分类1.3 CLIP代码详解1.3.1 Image Encoder 和 Text Encoder的实现1.3.2 搭建CLIP模型1.3.3 准备数据1.3.4 Loss的定义1.4 完整代码2.GLIP 讲解2.1 GLIP 介绍2.2 GLIP 网络结构3.Flamingo3.1 模型介绍3.2 Loss 定义…

远程控制软件优化(1)

远程控制软件优化&#xff08;1&#xff09; 第一版存在以下缺点&#xff1a; 1、四大部分中 Robot States 部分过于简陋&#xff0c;不适合放到论文中 2、Lidar BEV 图像显示效果非常差&#xff0c;显示不全且很稀疏 3、视频流传输延时过高&#xff0c;无法实现远程控制 以…

基于OpenMV 双轴机械臂 机器学习

文章目录 一、项目简要二、目标追踪1. 色块识别与最大色块筛选2. PID位置闭环 三、机器学习1. Device12. Device2 四、效果演示 一、项目简要 两套二维云台设备&#xff0c;Device1通过摄像头捕捉目标物块点位进行实时追踪&#xff0c;再将自身点位传到Device2&#xff0c;Dev…

【力扣周赛】第394场周赛

文章目录 1.统计特殊字母的数量2.使矩阵满足条件的最少操作次数 1.统计特殊字母的数量 题目链接 &#x1f34e;该题涉及的小技巧&#xff1a;&#x1f425; &#x1f427;①大写字母和对应的小写字母低5位都是相等的&#xff1b; &#x1f427;②大写字母ASCII二进制第 6 位…

node.js + @elastic/elasticsearch 操作elasticsearch数据库

我这边node.js 使用的是 koa2&#xff0c;elasticsearch是8.11.1版本 官网&#xff1a;https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/getting-started-js.html 一、elastic/elasticsearch 连接 elasticsearch数据库 如果elasticsearch没有设…

win c++使用lua环境配置 5.3.5版本

编译lua 下载lua源码&#xff0c;github仓库 使用vs编译源码&#xff0c;新建一个静态库项目(只会生成lib文件)&#xff0c;想要dll的话就新建dll项目&#xff08;有一个lib文件和dll文件&#xff09; 把lua源码下面的文件夹都是&#xff0c;复制到vs项目中 lib目录是我手动…

ResNeXt网络结构

一、简介 在ResNet的基础上&#xff0c;对残差结构的block进行了更新。 ResNeXt网络是一种深度神经网络架构&#xff0c;可以视为对ResNet&#xff08;残差网络&#xff09;的改进和升级。ResNeXt结合了VGG网络的堆叠相同基础模块的策略以及Inception系列网络中的split-trans…

杰发科技AC7840——CAN通信简介(6)_监听模式

参考&#xff1a;http://t.csdnimg.cn/AFFPC 0. 简介 7840支持4种扩展模式&#xff0c;其中监听模式。 监听模式概念 作用: 这里写的用于诊断&#xff0c;实际上我还没有用到&#xff0c;不太理解为啥可以用作诊断。 我的理解是&#xff0c;在多个总线下&#xff0c;使用监听…

装饰器模式【结构型模式C++】

1.概述 装饰器模式是一种结构型设计模式&#xff0c; 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 2.结构 抽象构件&#xff08;Component&#xff09;角色&#xff1a;定义一个抽象接口以规范准备接收附加责任的对象。具体构件&#xff08;Concre…