【C++】红黑树讲解及实现

前言:

        AVL树与红黑树相似,都是一种平衡二叉搜索树,但是AVL树的平衡要求太严格,如果要对AVL树做一些结构修改的操作性能会非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此,如果要对一个结构经常修改,AVL树就不太适合,这时就需要运用对平衡要求不太严格的红黑树。


一,红黑树的认识

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

        红黑树确保没有一条路径会比其他路径长出俩倍的原因是由红黑树的性质决定。

红黑树的性质

        1. 每个结点不是红色就是黑色。 

        2. 根节点是黑色的。

        3. 如果一个节点是红色的,则它的两个孩子结点必须是黑色的。即从上层到下层不存在连续的红节点。

        4. 对于每个结点,从该结点到其所有后代叶子结点的简单路径上(包含此节点),均包含相同数目的黑色结点,即每条路径均包含相同数量的黑色节点。

        5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)。这里所到最终节点指的就是空节点。如上图中NIL空节点为黑色。

        红黑树就是满足以上的性质才确保没有一条路径会比其他路径长出两倍。至于原因我们可来设想一下极端情况。由于任意一个节点到其所有后代叶子结点的黑色节点相同,且红色节点不能连续出现,所以极端情况下,我们令一个节点的左边为最短路径——全部黑色,高度h,右边为最长路径——黑色节点之间每隔一个都有一个红色节点,高度最大为2*h。这样我们得出最大路径刚好是最小路径的两倍。由此看来,红黑树下任意节点的路径之差在 [h,2h] 区间。

        红黑树对比AVL树可得,红黑树对平衡的要求不是那么严格,比较宽松,虽说效率逊于AVL树,但是综合性能比AVL树高。而且AVL树的高度很接近 logN,红黑树高度很接近 2*logN,logN 的数值足够小,两者之间效率基本微乎其微,基本可忽略不计。


二,红黑树的插入操作

2-1,红黑树节点的定义

        红黑树的结点定义大致跟AVL树一样,唯一要不同的是红黑树中没有平衡因子,只包含颜色。

//节点的颜色——这里用枚举表示

enum Color
{
    RED,       //红色
    BLACK   //黑色
};

//红黑树节点的结构

template <class K, class V>
struct RBTreeNode
{
    RBTreeNode<K, V>* _parent;   //双亲节点
    RBTreeNode<K, V>* _left;        //左孩子节点
    RBTreeNode<K, V>* _right;     //右孩子节点
    Color _color;                             //节点颜色
    pair<K, V> _kv;                         //节点数据

    RBTreeNode(const pair<K, V>& kv)   //为了后面插入方便,这里构造为红色节点
        : _parent(nullptr)
        , _left(nullptr)
        , _right(nullptr)
        , _color(RED)    
        , _kv(kv)
    {    }
};

2-2,红黑树的插入操作

        红黑树的插入跟AVL树一样,都是在二叉搜索树的基础上加上了平衡限制,因此可分为两步:1,按照二叉搜索树的规则进行插入。2,检测插入后的红黑树是否造到平衡破坏,若平衡被破坏,这里要进行旋转或变色(由于这里的平衡是根据颜色来控制的,插入后若平衡别破坏颜色管理一定失调,但插入后颜色失调不一定会导致平衡破坏,这里可想象一下当平衡满足AVL平衡时插入的场景)。

        红黑树由于要满足左右所有路径上的黑色节点相同,若插入黑色节点会引发上面一系列结构问题和不平衡现象,因此这里最好插入红色节点。

       首先我们先来考虑什么情况下插入新节点后不会发生旋转和变色。这里很显然,若我们在黑色节点后面插入新节点(红色节点)是不会影响颜色管理,至于是否影响平衡还要看极端情况。在极端情况下插入发生平衡失调,这绝对不会插入在黑色节点后面。因此若插入新节点在黑色节点后面我们可放任不管。

        若我们插入的新节点cur在红色节点后面,此时的红色结点必然无孩子且一定要进行变色(恢复红黑树颜色规则),还可能要进行旋转(恢复红黑树平衡规则),因为若此时红色结点存在一个孩子,那么这个孩子必然是黑色的,这样一来就违背了红黑树的左右路径黑色结点数量相同的性质。此情况下,因为cur的双亲结点是红色的,所以必然会存在黑色的爷爷结点,所以,这里的情况可分为三类谈论:1,存在cur的叔叔节点(双亲结点的兄弟结点)且为红。2,不存在cur的叔叔结点。3,存在cur的叔叔结点且为黑。其中情况二和情况三在代码实现中可分为一种情况,具体原因下面会说明。

情况一:(存在cur的叔叔节点u且为红)

        我们来看新插入结点cur的叔叔结点为红色时的情况。根据已有条件可知,cur的双亲结点parent(简写:p)为红色,那么cur的爷爷结点grandparent(简写:g)必为黑色,叔叔结点uncle(简写:u)为红色。如下图:

       我们先来观察a结构。这里先只看以p为根节点的子树,由于p为红色,且插入cur之前p的左边路径为1,所以a要么为空,要么只有一个黑色结点。当为黑色结点时,左右路径的黑色结点数量不相等,所以a只能为空。b和c同理,只看以g为根节点的子树结构,b和c不能为黑色,但b和c为红色,这里也不能为红色,所以只能为空。如下:

        这种情况下是否需进行旋转暂不明确,但显然违反了颜色规则——红色结点之间相连,需进行颜色变换。颜色变色这块需明白,当一个结点变色后是不会影响此节点的下层颜色规则,只会影响上面结构。因此,这里变色规则是p和u变黑色,g变红色,控制原本黑色结点的数量不变(保证上层结构)。如果g是根节点再将其变黑色,然后结束操作;如果g不是根节点,需要继续往上查看,因为这里虽说满足黑色结点数量的规则,但是g变成了红色,g的双亲结点可能为红色或黑色。当g的双亲结点为黑色时颜色变色后满足规则,可直接结束;当g的双亲结点为红色时,这里就要将重复操作,即将cur = g,继续排查,直到遇到p为黑色或cur为根节点为止。注意:这里往上排查过程中也可能遇到情况2(u结点不存在)或情况3(u结点存在且为黑色),这里先提前说一下,其实排查过程中不可能遇到情况2,至于原因和遇到情况3下面会详细说明。

        这里有两个问题。1,p结点和g结点是否存在,即是否为空。2,平衡是否被破坏,即是否需要进行旋转。

问题一:

        p结点可能为空,此时cur结点为根节点,对照上面cur为根节点直接处理情况,若在刚开始p为空,此时新插入结点cur为根节点,插入后直接返回即可。

        g结点这里一定不为空,因为当g结点为空时,p结点就是根节点,p结点一定为黑色。我们使用g结点的前提条件是p结点为红色,若p是黑色直接退出,根本就没有g结点这一环节。

问题二:

        这种条件下平衡一定不会被破坏。插入新节点后平衡被破坏的前提是插入前结构必为极端情况,而在极端情况下cur必有叔叔结点u且为黑或无叔叔结点u。因此,此种情况只需变色,无需旋转。从这可说明,发生旋转和变色的只有cur存在叔叔结点u且为黑色或无叔叔结点u。

        注意,在情况一下又可划分为四种情况,如下:

                1,p是g的左孩子,u是g的右孩子,新插入结点cur是p的左孩子。(如上图)

                2,p是g的左孩子,u是g的右孩子,新插入结点cur是p的右孩子。

                3,p是g的右孩子,u是g的左孩子,新插入结点cur是p的左孩子。

                4,p是g的右孩子,u是g的左孩子,新插入结点cur是p的右孩子。

        这里划分的四种情况都可看成一种情况,因为这里只需变色无需旋转,针对的目标是p、u、g三结点的颜色,至于谁是左孩子谁是右孩子根本不理会。      

情况二:(不存在cur的叔叔结点u

        当不存在叔叔结点u时插入新节点cur时显然违反了平衡规则——左右最大高度与最小高度不满足二倍关系,至于怎么旋转我们从AVL树中可知需看cur、p的具体位置(前文中已有AVL树旋转的详细讲解以及原理,若这里不明白的请看前文AVL树讲解),然而这里存在四种小情景:        

        1,p是g的左孩子,新插入结点cur是p的左孩子。p一定没有右孩子,因为p为红色且插入前无左孩子。此种情景跟AVL树中插入在g的左子树的左侧相似,进行右单旋,旋转后还要变色。为了保证这里黑色结点数量不变(保证上层结构)且在此子树下的左右路径黑色结点数量相同(保证本层子树结构,即最底层结构),需将g变为红色,p变为黑色,如下图:

        2,p是g的左孩子,新插入结点cur是p的右孩子。p一定没有左孩子,因为p为红色且插入前无右孩子。此种情景跟AVL树中插入在g的左子树的右侧相似,进行左右旋,旋转后也要变色。这里的变色规则跟上面一样,即将g变为红色,cur变为黑色,如下图:

        3,p是g的右孩子,新插入结点cur是p的左孩子。与上同理,这里p一定无右孩子。此种情景跟AVL树中插入在g的右子树的左侧相似,进行右左旋,旋转后也要变色。这里的变色规则跟上面一样,即将g变为红色,cur变为黑色。

        4,p是g的右孩子,新插入结点cur是p的右孩子。与上同理,p一定无左孩子。此种情景跟AVL树中插入在g的右子树的右侧相似,进行左单旋,旋转后还要变色,需将g变为红色,p变为黑色。

        当出现此种情况时,cur绝不可能是往上排查中的情况,必定为新插入结点,因为在这种情况下,以g为根节点的子树左右路径之差根本不满足红黑树的性质,例如以上面情景四为例,当cur不是新插入的结点时,g的左边高度固定为1,右边高度大于3。

        这里当发生这种情况时,旋转变色后可直接退出,因为无论出现以上哪四种场景,旋转后都将恢复原本平衡,变色后在g位置的结点都为黑色,根本不影响上层结点情况或g为根节点情况。

情况三:(存在cur的叔叔结点且为黑)

         首先,我们先来观察cur为新插入结点时遇到此情况的时候。

        我们来分析以上情况,这里叔叔结点u为黑色,根据红黑树性质,这里以g为根节点的子树中a结构必须有一个黑色结点,但在插入前图中p没有左孩子,这里以p为根节点的子树中情景跟上面一样,a结构必须为空,与上形成矛盾,因此cur为新插入结点的情况根本不可能出现,也就是说cur的叔叔结点为黑色的情况下只能是cur不为新插入的结点,即情况一中往上排查中遇到的情况(遇到情况二一步操作后直接退回)。    

        从情况一中的问题二可知,发生旋转和变色的情况只有在cur必有叔叔结点u且为黑色或无叔叔结点u的条件下,其中无叔叔结点只能是在刚插入时的情况,而此情况只能在上层中遇到,但注意,当遇到此种情况时还不能说明平衡一定被破坏,即平衡可能被破坏也可能没有被破坏。为了方便起见,这里处理的方式是无论平衡是否被破坏都进行旋转处理,因为旋转调节后我们严格遵守红黑树的颜色管理,也就间接不会破坏平衡;而且旋转的本质是将高度大的一方减一,高度小的一方加一,此种情况又是在上层结构中遇到的,当平衡时旋转调节后一定不会破坏这里的平衡。当平衡被破坏时,路径最长的是2*h+1(新插入结点的一方),路径最短的是h,旋转之后路径最长的变为2*h,路径最短的变为h+1,满足条件。

        旋转之前我们来分析已确定的条件。首先,这里叔叔既然存在且为黑,那么必然存在g结点。其次,这里的cur结点是情况一中往上排查时上一次的g结点——原本是黑色的现在变成红色的,相当于又一次检验,因此cur是红色的,那么p结点也是红色的,如果p是黑色的,在情况一中就直接退出了,根本不会进行到下一轮,且就算叔叔结点u是黑色的,但若双亲结点p是黑色的根本不可能出现不平衡现象,即颜色失调现象。最后,由于p是红色的,那么g是黑色的。总的来说,当发生这种情况时,就已确定g结点和u结点是黑色的,p结点和cur结点是红色的。

        此种情况跟情况二相似,对应的也有四种不同位置的场景,由于需要进行旋转,所以这里都要进行讨论。

        1,cur是p的左孩子,p是g的左孩子,u是g的右孩子。

        这里我们要明白是左右中的哪里高度出现了问题,需在哪里进行旋转。上面说过,存在叔叔节点u且为黑时进行旋转必然是插入前已处于极端情况下,此处高度大的一方就是cur的一方,因此此场景的旋转类似于AVL树中插入在g的左子树的左侧,进行右单旋。旋转后这里违背了颜色分配规则,具体的变化规则必须要依据插入前的原结构。插入前a、b、c、d、e结构尚未可知,但除去a、b、c、d、e结构外,g结点位置的左边无黑色结点,右边有一个黑色结点,g位置上是黑色结点(防止上面的双亲结点),除了这里已知结点外,还要注意a、b、c、d、e结构中头节点的颜色(为了周全,这里最好全部都看成不为空时的情况),即a、b、c的头节点必定为黑,d、e未知,因此这里需注意d、e为红的情况——若为红色,它们的双亲必须是黑色;若为黑色,它们的双亲可随意。旋转后必须也要满足此条件和以上注意情况,否则颜色分配这里可能就会出问题,因此,这里颜色的变化是将p变为黑色,g变为红色,如下图。

        2,cur是p的右孩子,p是g的左孩子,u是g的右孩子。

        这里高度大的一方还是cur的一方,原因与上一样。此时场景如同AVL树中插入在g的左子树的右侧,旋转的方式是左右旋——先左旋后右旋。颜色变化更上面场景原理一样,除去a、b、c、d、e结构,g位置的右边有一个黑色结点,g位置的左边没有黑色结点,g位置上是一个黑色结点(为了防止g位置的双亲结点)。这里还是将a、b、c、d、e子树结构看成都不为空时的情况,其中a、b、c的根节点必是黑色的,d、e的根节点可能是红色的,因此,这里旋转后连接d、e的结点必须也是黑色的。具体的颜色变化是将cur变成黑色,g变成红色,如下图:

        3,cur是p的左孩子,p是g的右孩子,u是g的左孩子。

        这里cur高度超出范围,与上一样。此时的旋转跟AVL树中插入在g的右子树的左侧时进行的旋转一样——进行左右旋(先左单旋后右单旋)。旋转后颜色的变化逻辑思想与上相同,g位置上是黑色结点,g位置左边有一个黑色结点,g位置右边没有黑色结点,且d、e子树结构的根节点可能是红色的。变色后应满足以上规则且连接d、e子树结构的结点必须是黑色的。因此,这里的颜色变色规则是g变成红色,cur变成黑色,如下图:

        4,cur是p的右孩子,p是g的右孩子,u是g的左孩子。

        这里cur高度超出范围,与上一样。此时的旋转跟AVL树中插入在g的右子树的右侧时进行的旋转一样——进行左单旋。旋转后颜色的变化逻辑思想与上相同,g位置上是黑色结点,g位置左边有一个黑色结点,g位置右边没有黑色结点,且d、e子树结构的根节点可能是红色的。变色后应满足以上规则且连接d、e子树结构的结点必须是黑色的。因此,这里的颜色变色规则是g变成红色,p变成黑色,如下图:

        这里不难发现,处于这种情况下的无论哪种场景,叔叔结点u以及叔叔结点u下层的结构不做任何改变,至于p和cur下层的结构若不为空,根节点必为黑色,这里完全不影响上面结点的颜色。仔细思考可发现,这里发生的旋转和不同场景下颜色的改变跟情况二中无叔叔结点时一摸一样—,即g结点的位置是黑色的且满足颜色分配,旋转变色后可直接退出,可归为一类。

代码实现   

        总上所述,这里红黑树的插入操作逻辑大致是这样:当插入结点的双亲结点是黑色的不做任何处理;当插入结点的双亲结点是红色的,这时分两种情况来判断:1,存在叔叔结点且为红。2,存在叔叔结点且为黑或不存在叔叔结点。当存在叔叔结点且为红,这种情况只用于变色并向上做出排查,直到排查到根节点;当不存在叔叔结点或存在叔叔结点且为黑,直接进行旋转并变色后退出。由于情况一需要对叔叔结点进行变色,而情况二需要确定叔叔结点的位置以便确定哪种旋转,所以这里可再分两种小情况进行讨论——叔叔结点u是g的左孩子或叔叔结点u是g的右孩子。于情况一而言,这里只用变色,无需注意其它结点位置;于情况二而言,由于需要旋转,只确定p和u的位置还不够,还需确定cur的具体位置。代码如下:

bool Insert(const pair<K, V>& kv)
{
    //1,插入节点
    if (_root == nullptr)
    {
        _root = new Node(kv);
        _root->_color = BLACK;
        return true;
    }
    Node* cur = _root;
    Node* parent = _root->_parent;
    while (cur)
    {
        if (cur->_kv.first > kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else if (cur->_kv.first < kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else
        {
            return false;
        }
    }
    if (parent->_kv.first > kv.first)
    {
        parent->_left = new Node(kv);
        cur = parent->_left;
    }
    else
    {
        parent->_right = new Node(kv);
        cur = parent->_right;
    }
    cur->_parent = parent;

    //2,旋转或变色
    //只用处理插入结点cur的双亲结点是红色时的情况,要注意cur是根节点的情况
    while (parent && parent->_color == RED) 
    {
        //存在parent且parent是红色时,由于根节点是黑,所以一定存在它的双亲结点
        Node* grandparent = parent->_parent;
        //cur双亲结点parent在左,叔叔结点uncle在右
        if (parent == grandparent->_left)
        {
            Node* uncle = grandparent->_right;
            //情况一: 存在叔叔结点且为红
            //这种情况平衡不会被破坏,不用关心结点的具体位置,只需变色

            if (uncle && uncle->_color == RED)
            {
                //变色
                grandparent->_color = RED;
                parent->_color = uncle->_color = BLACK;
                //往上排查
                cur = grandparent;
                parent = cur->_parent;
            }
            //情况二: 存在叔叔结点且为黑或不存在叔叔结点
            else 
            {
                //情景一: p是g的左孩子,u是g的右孩子,cur是p的左孩子
                if (cur == parent->_left)
                {
                    //旋转
                    RotateR(grandparent);
                    //变色
                    grandparent->_color = RED;
                    parent->_color = BLACK;
                }
                //情景二: p是g的左孩子,u是g的右孩子,cur是p的右孩子
                else
                {
                    //旋转
                    RotateLR(grandparent);
                    //变色
                    grandparent->_color = RED;
                    cur->_color = BLACK;
                }
                break;//旋转变色后直接退出
            }
        }
        //cur双亲结点parent在右,叔叔结点uncle在左
        else
        {
            Node* uncle = grandparent->_left;
            //情况一: 存在叔叔结点且为红
            //情况以上一样,这里只需变色无需旋转
            //这里之所以不能单独列出来是因为叔叔结点不确定是否存在

            if (uncle && uncle->_color == RED)
            {
                //变色
                grandparent->_color = RED;
                parent->_color = uncle->_color = BLACK;
                //继续往上排查
                cur = grandparent;
                parent = cur->_parent;
            }
            //情况二: 存在叔叔结点且为黑或不存在叔叔结点
            else 
            {
                //情景三: p是g的右孩子,u是g的左孩子,cur是p的左孩子
                if (cur == parent->_left)
                {
                    //旋转
                    RotateRL(grandparent);
                    //变色
                    grandparent->_color = RED;
                    cur->_color = BLACK;
                }
                //情景四: p是g的右孩子,u是g的左孩子,cur是p的右孩子
                else
                {
                    //旋转
                    RotateL(grandparent);
                    //变色
                    grandparent->_color = RED;
                    parent->_color = BLACK;
                }
                break;//旋转变色后直接退出
            }
        }
    }
    //直接暴力解决cur为根节点情况
    _root->_color = BLACK;
    return true;
}

//旋转算法
void RotateL(Node* parent) //左单旋
{
    Node* cur = parent->_right;
    Node* curL = cur->_left;
    Node* pparent = parent->_parent;

    //旋转
    parent->_right = curL;
    if (curL)
        curL->_parent = parent;

    //更新节点
    if (parent == _root)
    {
        _root = cur;
        _root->_parent = nullptr;
    }
    else
    {
        if (pparent->_left == parent)
        {
            pparent->_left = cur;
            cur->_parent = pparent;
        }
        else
        {
            pparent->_right = cur;
            cur->_parent = pparent;
        }
    }

    //旋转
    cur->_left = parent;
    parent->_parent = cur;
}

void RotateR(Node* parent) //右单旋
{
    Node* cur = parent->_left;
    Node* curR = cur->_right;
    Node* pparent = parent->_parent;

    //旋转
    parent->_left = curR;
    if (curR)
        curR->_parent = parent;

    //更新节点
    if (parent == _root)
    {
        _root = cur;
        _root->_parent = nullptr;
    }
    else
    {
        if (pparent->_left == parent)
        {
            pparent->_left = cur;
            cur->_parent = pparent;
        }
        else
        {
            pparent->_right = cur;
            cur->_parent = pparent;
        }
    }

    //旋转
    cur->_right = parent;
    parent->_parent = cur;
}

void RotateLR(Node* parent) //左右旋
{
    //先左再右两次旋转
    RotateL(parent->_left);
    RotateR(parent);
}

void RotateRL(Node* parent) //右左旋
{
    //先右再左两次旋转
    RotateR(parent->_right);
    RotateL(parent);
}

2-3,红黑树的验证

        红黑树的验证不能根据红黑树的特点判断,即不能根据最长路径不超过最短路径的二倍来判断。红黑树之所以路径会满足此特点是由红黑树的性质决定的,但反过来,满足路径关系却不一定满足红黑树的性质,所以这里要根据红黑树的性质进行验证。

        红黑树的检测分为两步:1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列) 2. 检测其是否满足红黑树的性质。

        这里重点说明第二步。红黑树的性质验证的核心检查有三点:1,根节点是黑色的。2,没有连续的红色结点。3,每条路径的黑色结点的数量相等。

        第一点代码很好实现,不做过多说明。第二点直接使用前序遍历整个树,当遍历到此时结点为红色时查看它的双亲结点,若双亲结点也是红色的验证失败。第三点方法有很多,这里可先从树的根节点选任意一个路径,记录此路径黑色结点的数量作为标杆,然后不断前序遍历所有路径,若有任意一个路径的黑色结点数量与之不同,那么遍历失败。原因很简单,树的所有路径都可看成从根节点开始的路径的子路径,只要我们在每个结点到叶子结点路径上遇到黑色结点的数量加上上面从根节点开始到此路径的黑色结点数,就如同从根节点开始的一条路径黑色结点数,即任意一个结点(这里是根节点)到后代所有路径的黑色结点数比较。

//核心检查三步:1,根节点是黑色的。2,没有连续的红色结点。3,每条路径的黑色结点的数量相等。
bool IsBalance()
{
    //核心检查1—根节点是黑色的
    if (_root && _root->_color == RED)
        return false;
    //核心检查3—每条路径的黑色结点的数量相等
    Node* cur = _root;
    //检查3中,先检查红黑树的左子树黑色结点总共的个数,即标杆
    size_t sign = 0;
    while (cur)
    {
        if (cur->_color == BLACK)
            sign++;
        cur = cur->_left;
    }
    return Check(_root, 0, sign);
}
bool Check(const Node* root, size_t blacknum, size_t sign)
{
    //一条路径结束,开始判断黑色结点数量是否相同
    if (!root)
    {
        if (blacknum != sign)
            return false;
        return true;
    }
    //核心检查2—没有连续的红色结点
    Node* parent = root->_parent;
    if (root->_color == RED && parent->_color == RED) //root结点是红色时一定不是根节点
        return false;
    //检查3中,然后以标杆做依据,遍历每个路径的黑色结点也之是否相同
    if (root->_color == BLACK)
        blacknum++;
    return Check(root->_left, blacknum, sign) && Check(root->_right, blacknum, sign);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

降低笔记本电脑噪音的七种方法,看下有没有适合你的

序言 无论是玩游戏、浏览网络还是做严肃的工作,差不多都有这么一台笔记本电脑,它有足够的处理能力来处理几乎任何事情。不幸的是,它可能会变得非常大声,但有办法来遏制这种噪音。 清洁通风口和风扇,并使用硬表面 如果你的笔记本电脑现在比过去运行同样的软件时声音更大…

【MySQL基础】MySQL基本操作详解

系列文章目录 第1篇&#xff1a;【MySQL基础】MySQL介绍及安装 第2篇&#xff1a;【MySQL基础】MySQL基本操作详解 文章目录 ✍1&#xff0c;数据库操作     &#x1f50d;1.1,查看数据库     &#x1f50d;1.2,创建数据库     &#x1f50d;1.3,选择数据库    …

腾讯云添加域名后不生效

问题原因 添加域名后不生效可能是因为没有加CDN域名解析 解决步骤

MacOS Docker 可视化面板 Portainer

一、简介 Portainer 是一个可视化的容器镜像图形管理工具&#xff0c;使用 Portainer 可以轻松构建、管理和维护Docker 环境。 而且完全免费&#xff08;portainer-ce 是免费的&#xff0c;portainer-ee 是需要授权的&#xff0c;今天安装的是 portainer-ce 版本&#xff09;&…

Java多态练习2

设计金融产品类Financial&#xff0c;属性包括产品名称、产品介绍、起投金额、产品期限&#xff08;int&#xff09;、年化收益&#xff08;百分数&#xff09;&#xff1b;方法包括发布、截止、投资。 设计金融产品类子类&#xff1a; 基金产品Fund&#xff0c;继承金融产品类…

Jenkins (三) - 拉取编译

Jenkins (三) - 拉取编译 通过Jenkins平台 git 拉取github上项目&#xff0c;通过maven编译并打包。 Jenkins 安装 git 插件 Manager Jenkins -> Plugins -> Available plugins -> Git 打包编译检验 FressStyle 风格编译 New Item输入 item name Spring-Cloud-1…

LeetCode---127双周赛

题目列表 3095. 或值至少 K 的最短子数组 I 3096. 得到更多分数的最少关卡数目 3097. 或值至少为 K 的最短子数组 II 3098. 求出所有子序列的能量和 一、或值至少k的最短子数组I&II 暴力的做法大家都会&#xff0c;这里就不说了&#xff0c;下面我们来看看如何进行优化…

1、快速上手Docker:入门指南

文章目录 Linux中安装docker防火墙端口配置web项目需要的环境安装yarn安装nodejs安装脚手架并准备项目 构建镜像启动镜像查看日志管理镜像推送镜像 发布项目准备服务器环境部署项目&#xff1a; PS&#xff1a;扩展一点小知识 这篇文章只是docker入门的第一个Docker项目&#x…

STM32使用HAL库获取GPS模块HT1818Z3G5L信息(方法1)

1、写在最前 先了解一下GPRMC的格式 格 式&#xff1a; GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,A*50 说 明&#xff1a; 字段 0&#xff1a;$GPRMC&#xff0c;语句ID&#xff0c;表明该语句为Recommended Minimum Specific GPS/TRANSIT Data&…

数据结构|排序总结(1)|直接插入排序

排序分类 插入排序&#xff1a;直接插入排序&#xff0c;希尔排序 选择排序&#xff1a;选择排序&#xff0c;堆排序 交换排序&#xff1a;冒泡排序&#xff0c;快速排序 归并排序 插入排序 直接插入排序 相当于摸牌&#xff0c;例如我们现在手上有{2&#xff0c;4&#xff0…

碘浊度法与红外相机联用测定食品中维生素C

&#x1f31e;欢迎来到看论文的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f4c6;首发时间&#xff1a;&#x1f339;2024年4月6日&…

数据库的简单查询

一、检索一列或多列1.检索单独一列 select 列名 from 表名; select order_num from orders; 2.检索多列数据 select 列 1&#xff0c;列 2... from 表名; select order_num,order_date from orders; select order_date,order_num from orders; 3.查询所有字段 select * from…

正排索引 vs 倒排索引 - 搜索引擎具体原理

阅读导航 一、正排索引1. 概念2. 实例 二、倒排索引1. 概念2. 实例 三、正排 VS 倒排1. 正排索引优缺点2. 倒排索引优缺点3. 应用场景 三、搜索引擎原理1. 宏观原理2. 具体原理 一、正排索引 1. 概念 正排索引是一种索引机制&#xff0c;它将文档或数据记录按照某种特定的顺序…

016——DHT11驱动开发(基于I.MX6uLL)

目录 一、 模块介绍 1.1 简介 1.2 电路描述 1.3 通信协议 二、 驱动程序 三、 应用程序 四、 上机实验 一、 模块介绍 1.1 简介 DHT11 是一款可测量温度和湿度的传感器。比如市面上一些空气加湿器&#xff0c;会测量空气中湿度&#xff0c;再根据测量结果决定是否继续加…

Cortex-M7 内存映射模型

1 前言 如图1所示&#xff0c; Cortex-M7最大支持4GB的内存寻址&#xff0c;并对内存映射(memory map)做了初步的规定&#xff0c;将整个内存空间划分为了多个内存区域(region)。每个内存区域有着既定的内存类型(memory type)和内存属性(memory attribute)&#xff0c;这两者决…

物理层习题及其相关知识(谁看谁不迷糊呢)

1. 对于带宽为50k Hz的信道&#xff0c;若有4种不同的物理状态来表示数据&#xff0c;信噪比为20dB 。&#xff08;1&#xff09; 按奈奎斯特定理&#xff0c;信道的最大传输数据速率是多少&#xff1f;&#xff08;2&#xff09; 按香农定理&#xff0c;信道的最大传输数据速度…

基于Springboot+Vue实现前后端分离酒店管理系统

一、&#x1f680;选题背景介绍 &#x1f4da;推荐理由&#xff1a; 近几年来&#xff0c;随着各行各业计算机智能化管理的转型&#xff0c;以及人们经济实力的提升&#xff0c;人们对于酒店住宿的需求不断的提升&#xff0c;用户的增多导致酒店管理信息的不断增多&#xff0c;…

ICLR 2024 | 联邦学习后门攻击的模型关键层

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了免费的人工智能中文站https://ai.weoknow.com 新建了收费的人工智能中文站https://ai.hzytsoft.cn/ 更多资源欢迎关注 联邦学习使多个参与方可以在数据隐私得到保护的情况下训练机器学习模型。但是由于服务器无法…

华为分红出炉,人均超50w!

华为分红 770 亿 4 月 2 日&#xff0c;北京金融资产交易所官网发布了《华为投资控股有限公司关于分配股利的公告》。 公告指出&#xff1a;经公司内部有权机构决议&#xff0c;拟向股东分配股利约 770.945 亿元。 众所周知&#xff0c;华为并不是一家上市公司&#xff0c;这里…

C++从入门到精通——初步认识面向对象及类的引入

初步认识面向对象及类的引入 前言一、面向过程和面向对象初步认识C语言C 二、类的引入C的类名代表什么示例 C与C语言的struct的比较成员函数访问权限继承默认构造函数默认成员初始化结构体大小 总结 前言 面向过程注重任务的流程和控制&#xff0c;适合简单任务和流程固定的场…