探索数据结构:红黑树的分析与实现

news2024/9/24 7:25:46

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty’s blog

1. 红黑树的介绍

1.1. 红黑树的引入

我们前面学习了AVL树,知道其本质是通过旋转操作来确保每个节点的左右子树高度差不超过 1,以此解决数据有序或接近有序时二叉搜索树退化为单边树而导致查找效率低下的问题。然而,当插入数据量过大时,频繁的旋转操作同样会使效率降低。为应对这一问题,就有人提出了一种更为特殊的树——红黑树

红黑树是一种自平衡的二叉查找树,查找效率高。它由Rudolf Bayer于 1978 年发明,当时被称为平衡二叉B树。后来,在 1978 年,Leo J. GuibasRobert Sedgewick对其进行了修改,形成了如今的红黑树。

1.2. 红黑树的特点

首先,红黑树属于二叉搜索树,其特点是在每个节点额外增加了一个存储位用于记录节点的颜色,该颜色可以是 RED,也可以是 BLACK。 并且红黑树通过对任意一条从根到叶子的简单路径上颜色的约束,能够保证最长路径不会超过最短路径的二倍,从而实现近似平衡,减少旋转的效果。其具体特点如下:

  1. 节点颜色只有黑色与红色两种颜色。
  2. 根节点一定是黑色。
  3. 空节点null一定为黑色,并且在红黑树中认为null节点才是叶子节点。
  4. 红色节点的子节点和父节点一定为黑色。
  5. 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点。

img

为什么上述规则能保证红黑树最长路径不超过最短路径的两倍?

具体来说,根据上述规则可以得出最短路径即为全是黑节点的路径,最长路径则是一个红节点接着一个黑节点。当从根节点到叶子节点的路径上黑色节点数量相同时,最长路径恰好是最短路径的两倍。

2. 红黑树的功能

以下是红黑树常见的功能:

  1. 红黑树的插入。
  2. 红黑树的查找。
  3. 红黑树的删除。

3. 红黑树的结构

3.1. 红黑树的节点

红黑树的节点本质与二叉搜索树的节点差不多,所以肯定有三个成员变量:左子树_left,右子树_right,键值_kv,并且键值我们采用KV模型的形式。并且还有一个表示节点颜色的变量_col,以及一个父节点_parent。并且为了增加代码的可读性,我们可以定义一个枚举来表示颜色。当然为了适配不同的类型,我们同样需要定义一个模版.。

template<class K,class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K,V>& kv = pair<K,V>(), Color col = RED)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(col)//默认为红节点
	{}
	RBTreeNode<K, V>* _left;//左子树
	RBTreeNode<K, V>* _right;//右子树
	RBTreeNode<K, V>* _parent;//父节点
	pair<K, V> _kv;//键值
	Color _col;//颜色
};

为什么插入新节点的颜色默认是红色而不是黑色呢?

  • 若插入的是黑色节点,会导致插入路径的黑色节点数量增加,破坏原每条路径黑色节点数量相同的平衡特性,需要对所有路径进行调整,非常麻烦。
  • 若插入节点为红色,则需分情况讨论。当插入节点的父节点为黑色时,不会违反红黑树规则,无需处理。而当插入节点的父节点为红色时,由于红黑树不能有连续的红色节点,此时就需要进行调整。但是因为只需调整该路径,所以明显更简单。

综上所述,红黑树插入节点默认设为红色能在一定程度上减少违反规则的情况,同时降低调整树结构的复杂性。

3.2. 红黑树

然后我们就可以通过节点来定义红黑树,并将根节点初始化为空。

template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	//...具体功能
private:
	Node* _root = nullptr;//根节点
};

4. 红黑树的初始化与销毁

4.1. 构造函数/拷贝构造/赋值重载

首先我们直接定义一个无参的构造函数,因为我们在定义拷贝构造之后编译器就不会在生成默认的构造函数了。

RBTree()
{}

之后我们可以利用递归来实现一个拷贝构造函数。

RBTree(const RBTree<K, V>&t)
{
    _root = copy(t._root);
}
Node* copy(Node* root)
{
    // 如果原始节点为空,直接返回空指针
    if (root == nullptr)
    {
        return nullptr;
    }
    // 为新节点分配内存并拷贝原始节点的值
    Node* newnode = new Node(root->_kv);
    // 递归拷贝左子树
    newnode->_left = copy(root->_left);
    // 递归拷贝右子树
    newnode->_right = copy(root->_right);
    // 将新节点的父节点指针置为空
    newnode->_parent = nullptr;
    // 拷贝原始节点的颜色信息
    newnode->_col = root->_col;
    // 如果新节点的左子节点存在,设置其父节点为新节点
    if (newnode->_left)
    {
        newnode->_left->_parent = newnode;
    }
    // 如果新节点的右子节点存在,设置其父节点为新节点
    if (newnode->_right)
    {
        newnode->_right->_parent = newnode;
    }
}

最后我们通过一个简单的方式实现赋值重载——通过形参调用拷贝构造出一个临时变量,然后交换this所指向的变量,这样原本this所指向的对象出了作用域就会销毁,间接实现了实现赋值重载。

RBTree<K, V> operator =(const RBTree<K, V> t)
{
    this->swap(_root, t._root);
    return *this;
}

4.2. 析构函数

析构函数需要借助递归释放所有节点,而为了方便我们传参我们可以定义子函数来帮助我们解决。

~RBTree()
{
    Destroy(_root);
}
void Destroy(Node*& root)
{
	if (root == nullptr)
	{
		return;
	}
	//递归销毁左子树
	Destroy(root->_left);
	//递归销毁右子树
	Destroy(root->_right);
	//销毁根节点
	delete root;
	root = nullptr;
}

5. 红黑树的功能实现

不论是红黑树的插入还是删除操作都需要用到旋转操作,如果你还不知道旋转操作的具体内容可以先看看这篇文章——AVL树。当然具体的旋转操作也看到也有细微的区别,因为红黑树并不存在平衡因子,但是主体思路肯定不会改变。

5.1. 红黑树的插入

向红黑树进行插入,首先是先找到需要插入的位置,这个逻辑与二叉搜索树类似,这里就不在赘述。找到之后插入默认的红节点,此时就可以分为五种情况来讨论:

为了方便叙述,我们首先假设插入节点为cur,其父节点为parent,其父节点的兄弟节点为uncle,其父节点的父节点为grandfather

其中下面图示抽象的情况可能是红黑树整体,也可能是红黑树的某个子树。

5.1.1. 情况一

第一种情况就是第一次插入,也就是插入根节点,此时根据性质1我们只需要将对应颜色从RED改为BLACK即可。

5.1.2. 情况二

第二种情况就是插入节点的父节点是黑节点,符合红黑树的定义,所以此时也不需要做任何调整,直接插入即可。

5.1.3. 情况三

第三种情况就是插入节点的父节点parent是红节点,其父节点的兄弟节点uncle也为红节点,并且祖父节点grandfather是黑节点。如下图:

并且为了方便我们之后的null节点不画出,默认存在。其中a,b,c,d,e五颗子树中黑色节点数量都相同。

img

这种情况的调整方法也很简单,直接将parentuncle变为黑色,grandfather变为红色即可。

img

当然镜像对称后调整方式也是同理,所以这里就不在赘述。

注意:祖父节点grandfather可能不是根节点,当祖父节点parent变为红色节点时可能会引起上面节点的冲突(祖父节点的父节点也为红色),这时就需要循环调整。并且如果祖父节点grandfather是根节点,该调整方法会将根节点变为红节点,所以这时需要将根节点改回黑节点。

img

5.1.4. 情况四

第四种情况严格来说又可以分为两种情况:一种是cur为红色,parent也为红色,grandfather也为黑色,但是uncle不存在。如下图:

其中a,b,c三颗子树中除了null外没有黑色节点。

img

还有一种是cur为红色,parent也为红色,grandfather也为黑色,uncle存在且为黑色·。如下图:

其中a,b,c三颗子树中黑色节点的数量比d,e两棵子树黑色节点的数量多1。

img

当然这种情况肯定不是新插入红色节点造成的,因为一旦cur是新插入节点,那么a,b两棵子树高度为0,没有任何节点,肯定无法满足黑色节点数量比c,d,e多1这个条件。所以这种情况肯定是子树从情况三变换得来的。

针对以上两种情况我们都先右单旋,再将parent变为黑色,grandfather变为红色。当然镜像对称后调整方式也是同理,将旋转改为左单旋转即可,所以这里就不在赘述。

img

img

并且调整完毕后该子树的根节点是黑节点,所以也并不需要往上调整。

5.1.5. 情况五

最后一种情况与第四种情况条件相同:就是cur为红色,parent也为红色,grandfather也为黑色,uncle不存在/存在且为黑色。只不过插入节点位置有所区别。

其中a,b,c三颗子树中除了null外没有黑色节点。

img

其中a,b,c三颗子树中黑色节点的数量比d,e两棵子树黑色节点的数量多1。同样与情况四相同,这种情况不可能是插入造成的,一定是由情况三变化而来。

img

针对以上两种情况我们都先左右双旋,最后将cur变为黑色,grandfather变为红色。当然镜像对称后调整方式也是同理,将旋转改为右左双旋即可,所以这里就不在赘述。

img

img

并且调整完毕后该子树的根节点是黑节点,所以也并不需要往上调整。

默认插入节点为红色,最后我们对插入操作总结如下:

  1. 情况一:新插入的节点为根节点,将其颜色设置为黑色。
  2. 情况二:新插入节点的父节点为黑色,无需调整。
  3. 情况三:父节点和叔父节点均为红色,将父节点和叔父节点改为黑色,祖父节点改为红色,然后以祖父节点为当前节点继续进行调整,如果祖父节点是根节点则停止调整并将其改为黑色。
  4. 情况四:父节点为红色,叔父节点不存在/存在为黑色,当前节点是父节点的左子节点且父节点又是祖父节点的左子节点/当前节点是父节点的右子节点且父节点又是祖父节点的右子节点。先进行左单旋/右单旋,然后将父节点改为黑色,祖父节点改为红色。
  5. 情况五:父节点为红色,叔父节点不存在/存在为黑色,当前节点是父节点的左子节点且父节点又是祖父节点的右子节点/当前节点是父节点的右子节点且父节点又是祖父节点的左子节点。先进行左右双旋/右左双旋,然后将当前节点改为黑色,祖父改为红色。
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 (cur->_kv.first < kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else if (cur->_kv.first > kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else
        {
            return false;
        }
    }
    //找到插入位置
    cur = new Node(kv);
    if (parent->_kv.first < kv.first)
    {
        parent->_right = cur;
    }
    else
    {
        parent->_left = cur;
    }
    cur->_parent = parent;
    while (parent && parent->_col == RED)
    {
        Node* grandfather = parent->_parent;
        if (parent == grandfather->_left)
        {
            Node* uncle = grandfather->_right;
            //情况三:如果叔叔存在且为红
            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                grandfather->_col = RED;
                cur = grandfather;
                parent = cur->_parent;
            }
            else
            {
                //情况四:叔叔不存在/存在且为黑,且cur在parent的左侧
                if (cur == parent->_left)
                {
                    //     g  
                    //   p   u
                    // c 
                    RotateR(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                }
                else//情况五:叔叔不存在 / 存在且为黑,cur在parent的右侧
                {
                    //     g
                    //   p   u
                    //     c
                    RotateLR(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                //这时该子树的根节点变为黑色,不需要继续调整
                break;
            }
        }
        else
        {
            Node* uncle = grandfather->_left;
            //情况三:如果叔叔存在且为红
            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                grandfather->_col = RED;
                //继续调整
                cur = grandfather;
                parent = cur->_parent;
            }
            else
            {
                //情况四:叔叔不存在/存在且为黑,且cur在parent的左侧
                if (cur == parent->_right)
                {
                    //    g
                    //  u   p
                    //        c
                    RotateL(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                }
                else // 情况五:叔叔不存在 / 存在且为黑,cur在parent的右侧
                {
                    //    g
                    //  u   p
                    //    c
                    RotateRL(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                //这时该子树的根节点变为黑色,不需要继续调整
                break;
            }
        }
        
    }
    //防止情况三改到根节点变为红色
    _root->_col = BLACK;
    return true;
}
void RotateR(Node* parent)
{
    Node* cur = 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;
        _root->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = subL;
        }
        else
        {
            ppNode->_right = subL;
        }
        subL->_parent = ppNode;
    }
}
void RotateL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    parent->_right = subRL;
    if (subRL)
        subRL->_parent = parent;

    subR->_left = parent;
    Node* ppNode = parent->_parent;

    parent->_parent = subR;

    if (parent == _root)
    {
        _root = subR;
        _root->_parent = nullptr;
    }
    else
    {
        if (ppNode->_right == parent)
        {
            ppNode->_right = subR;
        }
        else
        {
            ppNode->_left = subR;
        }
        subR->_parent = ppNode;
    }
}
void RotateLR(Node* parent)
{
    RotateL(parent->_left);
    RotateR(parent);
}
void RotateRL(Node* parent)
{
    RotateR(parent->_right);
    RotateL(parent);
}

5.2. 红黑树的查找

红黑树的查找的逻辑本质与二叉搜索树的查找逻辑相同,所以这里就不在赘述。

Node* Find(const K& val)
{
    Node* cur = _root;
    while (cur)
    {
        if (cur->_kv.first > val)
        {
            //左子树中查找
            cur = cur->_left;
        }
        else if (cur->_kv.first < val)
        {
            //右子树中查找
            cur = cur->_right;
        }
        else
        {
            //找到了
            return cur;
        }
    }
    return nullptr;
}

5.3. 红黑树的删除

删除红黑树的逻辑明显比插入逻辑更加复杂,但是总体逻辑又与AVL树与二叉搜索树类似,首先第一步找到待删除节点,如果待删除节点的左右子树都不为空,可以采用伪删除法,即寻找到左子树的最右节点(左子树的最大值),或者是右子树的最左节点(右子树的最小值),然后赋值,转换为在其左子树或者右子树删除节点,保证待删除节点的左右子树至少有一个为空。第二步进行调整,删除该节点可能会破坏红黑树的性质所以这时需要调整。第三步删除节点并重新链接

第一步与第三步都非常简单,我们接下来重点讨论第三步。

为了方便描述我们将待删除节点称为delete,其父节点称为parent,其兄弟节点称为brother,其兄弟节点的左右节点分别称为broLeftbroRight

看图需知

  1. 如果parent为蓝色,表示parent可能为黑色也可能为红色。
  2. 如果broLeftbroRight为蓝色,表示broLeftbroRight可能为黑色也可能为红色,也可能为null节点。
  3. 如果broLeftbroRight为黑色,表示broLeftbroRight可能为黑色也可能为null节点。
  4. 绿色长方形表示高度未知的子树,也可以为null节点。

下面图示我们将按照delete节点在parent节点左侧的形式讨论,根据镜像对称delete节点在parent节点右侧只需对称操作即可,所以这里就不在赘述。

首先如果delete节点为红色,因为至少有一个空节点,那么另一个子节点既不能为红色也不可能为黑色,此时直接删除即可。

其次如果delete节点为黑色,且有一个孩子节点,那么这个孩子节点一定为红色,(parentbrother不都为红色)否则会破坏性质5。这种情况也只需重新链接不需要调整,然后将该孩子节点改为黑色。

imgimg

接下来我们将讨论delete节点为黑色且没有孩子节点的情况。

  1. 情况一:兄弟节点为红色。

因为兄弟节点为红色,其父节点一定为黑色,所以对于这种情况我们只需要对parent节点进行一次左旋转,再将brother变为黑色,parent变为红色。再重新更新brother节点,继续分析原删除节点delete,将情况一转换为情况二,三,四。

img

img

  1. 情况二:兄弟节点为黑色,且其左右孩子为空或者都为黑色。

对于这种情况我们需要将brother变为红色,然后根据parent节点颜色继续分析,如果parent节点为红色,只需要将其变为黑色即可,调整结束;如果parent节点为黑色,那么delete节点被删除后,原来该子树有两个黑色节点变为一个,性质5被破坏,将parent节点当做删除节点往上更新或者遇见根节点直接停止,将情况二转换为情况一,二,三,四。

img

img

  1. 情况三:兄弟节点为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空。

这种情况一定不是第一次调整,因为如果是第一次调整此时根本不平衡。对应这种情况我们需要对brother节点进行一次右单旋,然后再将brother结点变为红色,broLeft变为黑色,最后更新brother节点,再对delete节点进行调整,情况三就转换成了情况四。

img

img

  1. 情况四:兄弟节点为黑色,且其右孩子是红色结点。

对于这种情况我们先将parent节点进行一次左单旋,然后将parent的颜色赋值给brother,再将parent的颜色变为黑色,最后将broRight变为黑色,此时红黑树的调整一定结束。

img

img

最后删除节点如果是根节点直接删除再将左子树或者右子树的根节点变黑,当做新的根节点。如果没有左右子树都为空,那就将根节点_root置为空。

最后我们来总结

  1. 如果删除节点为红色,直接删除。
  2. 如果删除节点为黑色且有一个子节点,直接删除再链接,并将该子节点改为黑色。
  3. 如果删除节点为黑色且没有子节点,可以分为以下四种情况:
  • 情况一:兄弟节点为红色。先对parent左单旋,再将brother变为黑色,将parent变为红色,再对待删除结点delete进行情况分析,情况一就转换成了情况二、三或四。
  • 情况二:兄弟节点为黑色,且其左右孩子为空或者都为黑色。我们直接将brother变成红色,若parent是红色,将parent变为黑色后调整结束;若parent是黑色,则需将parent结点当作下一次调整时的结点进行分析直至根节点,情况二就转换成了情况一,二、三或四。
  • 情况三:兄弟节点为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空。先将brother右单旋,再将brother变为红色,broLeft变为黑色,再对待删除结点delete进行情况分析,将情况三转换成情况四。
  • 情况四:兄弟节点为黑色,且其右孩子是红色结点。先将parent左单旋,然后将parent的颜色赋值给brother,再将parent变为黑色,最后将brotRight变为黑色,此时红黑树的调整一定结束。

思考题:为什么删除节点为黑色且没有子节点的四种情况的调整方法一定能使红黑树调整成功?

我们知道这四种调整方法一共有三个"出口",分别为:(1)情况二调整后parent为红色,(2)情况二parent为黑色调整到根节点,(3)调整完情况四。所以问题就成功转换为经过任意三个"出口"结束之后,为什么红黑树一定调整成功。

  1. 如果从1号"出口"结束:一个子节点被删除后,因为使parent变为黑色,另一个子节点变为红色,所以此时这颗子树的路径上的黑色节点数没减少,调整成功。
  2. 如果从2号"出口"结束:一定是只经过情况二,因为如果是从情况一转换过来的情况二,那parent一定是红色就会从1号"出口"结束。如果只经过情况二那么每次操作都会使上层的一颗子树(非父节点所在子树)黑色节点数减一,最后更新到根节点整颗红黑树根节点到叶子节点的黑色节点数比原来少1,调整成功。
  3. 如果从3号"出口"结束:经过第四种调整方式,被删除一侧的黑色节点数增一,无论是从第一,二,三删除节点后都会使被删除一侧减一,所以经过情况四调整成功。

思考题:为什么删除节点为黑色且没有子节点的四种情况的调整方法不会造成死循环?

因为情况三一定会被转换为情况四,情况四一定会调整成功,所以情况三与情况四一定不会死循环。而情况一与情况二也可能转换为情况三与情况四这时也不会死循环。但会不会出现情况一与情况二一直互相循环的情况呢?其实不会的,因为一旦从情况一转换为情况二,此时父节点parent为红色,一定会结束。

//删除函数
bool Erase(const K& key)
{
    //(一)找到删除节点
    Node* cur = Find(key);
    //未找到返回false
    if (cur == nullptr)
    {
        return false;
    }
    //记录父节点
    Node* parent = cur->_parent;
    //用于标记实际的待删除结点及其父结点
    Node* delParent = nullptr;
    Node* delCur = nullptr;
    if (cur->_left == nullptr) //待删除结点的左子树为空
    {
        if (cur == _root) //待删除结点是根结点
        {
            _root = _root->_right; //让根结点的右子树作为新的根结点
            if (_root)
            {
                _root->_parent = nullptr;
                _root->_col = BLACK; //根结点为黑色
            }
            delete cur; //删除原根结点
            return true;
        }
        else
        {
            delParent = parent; //标记实际删除结点的父结点
            delCur = cur; //标记实际删除的结点
        }
    }
    else if (cur->_right == nullptr) //待删除结点的右子树为空
    {
        if (cur == _root) //待删除结点是根结点
        {
            _root = _root->_left; //让根结点的左子树作为新的根结点
            if (_root)
            {
                _root->_parent = nullptr;
                _root->_col = BLACK; //根结点为黑色
            }
            delete cur; //删除原根结点
            return true;
        }
        else
        {
            delParent = parent; //标记实际删除结点的父结点
            delCur = cur; //标记实际删除的结点
        }
    }
    else //待删除结点的左右子树均不为空
    {
        //替换法删除
        //寻找待删除结点右子树当中key值最小的结点作为实际删除结点
        Node* minParent = cur;
        Node* minRight = cur->_right;
        while (minRight->_left)
        {
            minParent = minRight;
            minRight = minRight->_left;
        }
        cur->_kv = minRight->_kv; //将待删除结点的键值改为minRight的键值
        delParent = minParent; //标记实际删除的父节点
        delCur = minRight; //标记实际删除的结点
    }
    //记录待删除结点及其父结点,便于后面删除
    Node* del = delCur;
    Node* delP = delParent;
    //(二)调整红黑树
    AdjustRBTree(delCur, delParent);
    //(三)进行实际删除
    DeleteNode(del, delP);
    return true;
}
void DeleteNode(Node* del, Node* delP)
{
    if (del->_left == nullptr) //实际删除结点的左子树为空
    {
        if (del == delP->_left) //实际删除结点是其父结点的左孩子
        {
            delP->_left = del->_right;
            //指向父节点
            if (del->_right)
                del->_right->_parent = delP;
        }
        else //实际删除结点是其父结点的右孩子
        {
            delP->_right = del->_right;
            if (del->_right)
                del->_right->_parent = delP;
        }
    }
    else //实际删除结点的右子树为空
    {
        if (del == delP->_left) //实际删除结点是其父结点的左孩子
        {
            delP->_left = del->_left;
            if (del->_left)
                del->_left->_parent = delP;
        }
        else //实际删除结点是其父结点的右孩子
        {
            delP->_right = del->_left;
            if (del->_left)
                del->_left->_parent = delP;
        }
    }
    delete del; //实际删除结点
}
void AdjustRBTree(Node* delCur, Node* delParent)
{
    if (delCur->_col == BLACK) //删除的是黑色结点
    {
        if (delCur->_left) //待删除结点有一个红色的左孩子(不可能是黑色)
        {
            delCur->_left->_col = BLACK; //将这个红色的左孩子变黑即可
        }
        else if (delCur->_right) //待删除结点有一个红色的右孩子(不可能是黑色)
        {
            delCur->_right->_col = BLACK; //将这个红色的右孩子变黑即可
        }
        else //待删除结点的左右均为空
        {
            while (delCur != _root) //可能一直调整到根结点
            {
                if (delCur == delParent->_left) //待删除结点是其父结点的左孩子
                {
                    Node* brother = delParent->_right; //兄弟结点是其父结点的右孩子
                    //情况一:brother为红色
                    if (brother->_col == RED)
                    {
                        delParent->_col = RED;
                        brother->_col = BLACK;
                        RotateL(delParent);
                        //需要继续处理
                        brother = delParent->_right; //更新brother
                    }
                    //情况二:brother为黑色,且其左右孩子都是黑色结点或为空
                    if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
                        && ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
                    {
                        brother->_col = RED;
                        if (delParent->_col == RED)
                        {
                            delParent->_col = BLACK;
                            break;
                        }
                        //需要继续处理
                        delCur = delParent;
                        delParent = delCur->_parent;
                    }
                    else
                    {
                        //情况三:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空
                        if ((brother->_right == nullptr) || (brother->_right->_col == BLACK))
                        {
                            brother->_left->_col = BLACK;
                            brother->_col = RED;
                            RotateR(brother);
                            //需要继续处理
                            brother = delParent->_right; //更新brother
                        }
                        //情况四:brother为黑色,且其右孩子是红色结点
                        brother->_col = delParent->_col;
                        delParent->_col = BLACK;
                        brother->_right->_col = BLACK;
                        RotateL(delParent);
                        break; //情况四执行完毕后调整一定结束
                    }
                }
                else //delCur == delParent->_right //待删除结点是其父结点的右孩子
                {
                    Node* brother = delParent->_left; //兄弟结点是其父结点的左孩子
                    //情况一:brother为红色
                    if (brother->_col == RED) //brother为红色
                    {
                        delParent->_col = RED;
                        brother->_col = BLACK;
                        RotateR(delParent);
                        //需要继续处理
                        brother = delParent->_left; //更新brother
                    }
                    //情况二:brother为黑色,且其左右孩子都是黑色结点或为空
                    if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
                        && ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
                    {
                        brother->_col = RED;
                        if (delParent->_col == RED)
                        {
                            delParent->_col = BLACK;
                            break;
                        }
                        //需要继续处理
                        delCur = delParent;
                        delParent = delCur->_parent;
                    }
                    else
                    {
                        //情况三:brother为黑色,且其右孩子是红色结点,左孩子是黑色结点或为空
                        if ((brother->_left == nullptr) || (brother->_left->_col == BLACK))
                        {
                            brother->_right->_col = BLACK;
                            brother->_col = RED;
                            RotateL(brother);
                            //需要继续处理
                            brother = delParent->_left; //更新brother
                        }
                        //情况四:brother为黑色,且其左孩子是红色结点
                        brother->_col = delParent->_col;
                        delParent->_col = BLACK;
                        brother->_left->_col = BLACK;
                        RotateR(delParent);
                        break; //情况四执行完毕后调整一定结束
                    }
                }
            }
        }
    }
}

6. 判断是否为红黑树

判断是否为红黑树首先得判断是否为二叉搜索树,然后判断是否满足红黑树的性质。

判断是否满足红黑树的性质,首先判断根节点是否为黑色。然后统计一条路径的节点个数,最后通过递归判断是否有连续的红色节点,以及每条路径的黑色节点是否相同。

bool IsRBTree()
{
    if (_root->_col == RED)
    {
        cout << "根节点为红节点" << endl;
        return false;
    }
    //从根节点到叶节点的黑色节点数
    int refNum = 0;
    Node* cur = _root;
    while (cur)
    {
        if (cur->_col == BLACK)
        {
            ++refNum;
        }

        cur = cur->_left;
    }

    return Check(_root, 0, refNum) && isValidBST();
}
//判断是否为二叉搜索树
bool isValidBST()
{
    return _isValidBST(_root, LONG_MIN, LONG_MAX);
}
bool _isValidBST(Node* root, long long lower, long long upper)
{
    if (root == nullptr)
    {
        return true;
    }
    if (root->_kv.first <= lower || root->_kv.first >= upper)
    {
        return false;
    }
    bool left = _isValidBST(root->_left, lower, root->_kv.first);
    bool right = _isValidBST(root->_right, root->_kv.first, upper);
    return left && right;
}
bool Check(Node* root, int blackNum, int refNum)
{
    if (root == nullptr)
    {
        //判断黑色节点数是否相同
        if (refNum != blackNum)
        {
            cout << "存在黑色节点的数量不相等的路径" << endl;
            return false;
        }

        return true;
    }
    //判断是否存在连续的红色节点
    if (root->_parent && root->_col == RED && root->_parent->_col == RED)
    {
        cout << root->_kv.first << "存在连续的红色节点" << endl;
        return false;
    }

    if (root->_col == BLACK)
    {
        blackNum++;
    }

    return Check(root->_left, blackNum, refNum)
        && Check(root->_right, blackNum, refNum);
}

7. 复杂度分析

对于红黑树的插入、查找和删除操作,其时间复杂度和空间复杂度分析如下:
时间复杂度:

  • Insert操作:平均和最坏情况下的时间复杂度均为 O ( log ⁡ n ) O(\log n) O(logn)。插入过程中需要调整红黑树的性质,可能涉及到旋转操作,但这些操作的时间是常数级,而查找插入位置的过程类似于二叉搜索树,时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)
  • Find操作:平均和最坏情况下的时间复杂度均为 O ( log ⁡ n ) O(\log n) O(logn)。与二叉搜索树的查找过程类似,通过比较键值逐渐缩小搜索范围,每次比较都能将范围缩小一半。
  • Erase操作:平均和最坏情况下的时间复杂度均为 O ( log ⁡ n ) O(\log n) O(logn)。删除节点时需要查找节点位置,然后根据红黑树的性质进行调整和可能的旋转操作,这些操作的时间复杂度与插入操作相似。

空间复杂度:

  • Insert操作:空间复杂度为 O ( 1 ) O(1) O(1)。在插入过程中,主要的额外空间消耗在于创建新节点以及存储一些临时变量,如指针等,这些空间消耗是常数级的。
  • Find操作:空间复杂度为 O ( 1 ) O(1) O(1)。寻找过程中仅使用了少量的固定数量的指针和临时变量,没有额外的大规模空间分配。
  • Erase操作:空间复杂度为 O ( 1 ) O(1) O(1)。删除操作中的主要空间消耗在于存储临时变量和指针,不会动态分配大规模的额外空间。

8.源码

#pragma once
#include<utility>
enum _col
{
	RED,//红色
	BLACK//黑色
};
template<class K,class V>
struct RBNode
{
	RBNode(const pair<K,V>& kv = pair<K,V>(), _col col = RED)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(col)//默认为红节点
	{}
	RBNode<K, V>* _left;//左子树
	RBNode<K, V>* _right;//右子树
	RBNode<K, V>* _parent;//父节点
	pair<K, V> _kv;//键值
	_col _col;//颜色
};
template<class K, class V>
class RBTree
{
	typedef RBNode<K, V> Node;
public:
	RBTree()
	{}
	RBTree(const RBTree<K, V>&t)
	{
		_root = copy(t._root);
	}
    RBTree<K, V> operator =(const RBTree<K, V> t)
    {
        this->swap(_root, t._root);
        return *this;
    }
    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 (cur->_kv.first < kv.first)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_kv.first > kv.first)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                return false;
            }
        }
        //找到插入位置
        cur = new Node(kv);
        if (parent->_kv.first < kv.first)
        {
            parent->_right = cur;
        }
        else
        {
            parent->_left = cur;
        }
        cur->_parent = parent;
        while (parent && parent->_col == RED)
        {
            Node* grandfather = parent->_parent;
            if (parent == grandfather->_left)
            {
                Node* uncle = grandfather->_right;
                //情况三:如果叔叔存在且为红
                if (uncle && uncle->_col == RED)
                {
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;
                    cur = grandfather;
                    parent = cur->_parent;
                }
                else
                {
                    //情况四:叔叔不存在/存在且为黑,且cur在parent的左侧
                    if (cur == parent->_left)
                    {
                        //     g  
                        //   p   u
                        // c 
                        RotateR(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    else//情况五:叔叔不存在 / 存在且为黑,cur在parent的右侧
                    {
                        //     g
                        //   p   u
                        //     c
                        RotateLR(grandfather);
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    //这时该子树的根节点变为黑色,不需要继续调整
                    break;
                }
            }
            else
            {
                Node* uncle = grandfather->_left;
                //情况三:如果叔叔存在且为红
                if (uncle && uncle->_col == RED)
                {
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;
                    //继续调整
                    cur = grandfather;
                    parent = cur->_parent;
                }
                else
                {
                    //情况四:叔叔不存在/存在且为黑,且cur在parent的左侧
                    if (cur == parent->_right)
                    {
                        //    g
                        //  u   p
                        //        c
                        RotateL(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    else // 情况五:叔叔不存在 / 存在且为黑,cur在parent的右侧
                    {
                        //    g
                        //  u   p
                        //    c
                        RotateRL(grandfather);
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    //这时该子树的根节点变为黑色,不需要继续调整
                    break;
                }
            }
            
        }
        //防止情况三改到根节点变为红色
        _root->_col = BLACK;
        return true;
    }
    void RotateR(Node* parent)
    {
        Node* cur = 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;
            _root->_parent = nullptr;
        }
        else
        {
            if (ppNode->_left == parent)
            {
                ppNode->_left = subL;
            }
            else
            {
                ppNode->_right = subL;
            }
            subL->_parent = ppNode;
        }
    }
    void RotateL(Node* parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        parent->_right = subRL;
        if (subRL)
            subRL->_parent = parent;

        subR->_left = parent;
        Node* ppNode = parent->_parent;

        parent->_parent = subR;

        if (parent == _root)
        {
            _root = subR;
            _root->_parent = nullptr;
        }
        else
        {
            if (ppNode->_right == parent)
            {
                ppNode->_right = subR;
            }
            else
            {
                ppNode->_left = subR;
            }
            subR->_parent = ppNode;
        }
    }
    void RotateLR(Node* parent)
    {
        RotateL(parent->_left);
        RotateR(parent);
    }
    void RotateRL(Node* parent)
    {
        RotateR(parent->_right);
        RotateL(parent);
    }
    Node* Find(const K& val)
    {
        Node* cur = _root;
        while (cur)
        {
            if (cur->_kv.first > val)
            {
                //左子树中查找
                cur = cur->_left;
            }
            else if (cur->_kv.first < val)
            {
                //右子树中查找
                cur = cur->_right;
            }
            else
            {
                //找到了
                return cur;
            }
        }
        return nullptr;
    }

    //删除函数
    bool Erase(const K& key)
    {
        //(一)找到删除节点
        Node* cur = Find(key);
        //未找到返回false
        if (cur == nullptr)
        {
            return false;
        }
        //记录父节点
        Node* parent = cur->_parent;
        //用于标记实际的待删除结点及其父结点
        Node* delParent = nullptr;
        Node* delCur = nullptr;
        if (cur->_left == nullptr) //待删除结点的左子树为空
        {
            if (cur == _root) //待删除结点是根结点
            {
                _root = _root->_right; //让根结点的右子树作为新的根结点
                if (_root)
                {
                    _root->_parent = nullptr;
                    _root->_col = BLACK; //根结点为黑色
                }
                delete cur; //删除原根结点
                return true;
            }
            else
            {
                delParent = parent; //标记实际删除结点的父结点
                delCur = cur; //标记实际删除的结点
            }
        }
        else if (cur->_right == nullptr) //待删除结点的右子树为空
        {
            if (cur == _root) //待删除结点是根结点
            {
                _root = _root->_left; //让根结点的左子树作为新的根结点
                if (_root)
                {
                    _root->_parent = nullptr;
                    _root->_col = BLACK; //根结点为黑色
                }
                delete cur; //删除原根结点
                return true;
            }
            else
            {
                delParent = parent; //标记实际删除结点的父结点
                delCur = cur; //标记实际删除的结点
            }
        }
        else //待删除结点的左右子树均不为空
        {
            //替换法删除
            //寻找待删除结点右子树当中key值最小的结点作为实际删除结点
            Node* minParent = cur;
            Node* minRight = cur->_right;
            while (minRight->_left)
            {
                minParent = minRight;
                minRight = minRight->_left;
            }
            cur->_kv = minRight->_kv; //将待删除结点的键值改为minRight的键值
            delParent = minParent; //标记实际删除的父节点
            delCur = minRight; //标记实际删除的结点
        }
        //记录待删除结点及其父结点,便于后面删除
        Node* del = delCur;
        Node* delP = delParent;
        //(二)调整红黑树
        AdjustRBTree(delCur, delParent);
        //(三)进行实际删除
        DeleteNode(del, delP);
        return true;
    }
    void DeleteNode(Node* del, Node* delP)
    {
        if (del->_left == nullptr) //实际删除结点的左子树为空
        {
            if (del == delP->_left) //实际删除结点是其父结点的左孩子
            {
                delP->_left = del->_right;
                //指向父节点
                if (del->_right)
                    del->_right->_parent = delP;
            }
            else //实际删除结点是其父结点的右孩子
            {
                delP->_right = del->_right;
                if (del->_right)
                    del->_right->_parent = delP;
            }
        }
        else //实际删除结点的右子树为空
        {
            if (del == delP->_left) //实际删除结点是其父结点的左孩子
            {
                delP->_left = del->_left;
                if (del->_left)
                    del->_left->_parent = delP;
            }
            else //实际删除结点是其父结点的右孩子
            {
                delP->_right = del->_left;
                if (del->_left)
                    del->_left->_parent = delP;
            }
        }
        delete del; //实际删除结点
    }
    void AdjustRBTree(Node* delCur, Node* delParent)
    {
        if (delCur->_col == BLACK) //删除的是黑色结点
        {
            if (delCur->_left) //待删除结点有一个红色的左孩子(不可能是黑色)
            {
                delCur->_left->_col = BLACK; //将这个红色的左孩子变黑即可
            }
            else if (delCur->_right) //待删除结点有一个红色的右孩子(不可能是黑色)
            {
                delCur->_right->_col = BLACK; //将这个红色的右孩子变黑即可
            }
            else //待删除结点的左右均为空
            {
                while (delCur != _root) //可能一直调整到根结点
                {
                    if (delCur == delParent->_left) //待删除结点是其父结点的左孩子
                    {
                        Node* brother = delParent->_right; //兄弟结点是其父结点的右孩子
                        //情况一:brother为红色
                        if (brother->_col == RED)
                        {
                            delParent->_col = RED;
                            brother->_col = BLACK;
                            RotateL(delParent);
                            //需要继续处理
                            brother = delParent->_right; //更新brother
                        }
                        //情况二:brother为黑色,且其左右孩子都是黑色结点或为空
                        if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
                            && ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
                        {
                            brother->_col = RED;
                            if (delParent->_col == RED)
                            {
                                delParent->_col = BLACK;
                                break;
                            }
                            //需要继续处理
                            delCur = delParent;
                            delParent = delCur->_parent;
                        }
                        else
                        {
                            //情况三:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空
                            if ((brother->_right == nullptr) || (brother->_right->_col == BLACK))
                            {
                                brother->_left->_col = BLACK;
                                brother->_col = RED;
                                RotateR(brother);
                                //需要继续处理
                                brother = delParent->_right; //更新brother
                            }
                            //情况四:brother为黑色,且其右孩子是红色结点
                            brother->_col = delParent->_col;
                            delParent->_col = BLACK;
                            brother->_right->_col = BLACK;
                            RotateL(delParent);
                            break; //情况四执行完毕后调整一定结束
                        }
                    }
                    else //delCur == delParent->_right //待删除结点是其父结点的右孩子
                    {
                        Node* brother = delParent->_left; //兄弟结点是其父结点的左孩子
                        //情况一:brother为红色
                        if (brother->_col == RED) //brother为红色
                        {
                            delParent->_col = RED;
                            brother->_col = BLACK;
                            RotateR(delParent);
                            //需要继续处理
                            brother = delParent->_left; //更新brother
                        }
                        //情况二:brother为黑色,且其左右孩子都是黑色结点或为空
                        if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))
                            && ((brother->_right == nullptr) || (brother->_right->_col == BLACK)))
                        {
                            brother->_col = RED;
                            if (delParent->_col == RED)
                            {
                                delParent->_col = BLACK;
                                break;
                            }
                            //需要继续处理
                            delCur = delParent;
                            delParent = delCur->_parent;
                        }
                        else
                        {
                            //情况三:brother为黑色,且其右孩子是红色结点,左孩子是黑色结点或为空
                            if ((brother->_left == nullptr) || (brother->_left->_col == BLACK))
                            {
                                brother->_right->_col = BLACK;
                                brother->_col = RED;
                                RotateL(brother);
                                //需要继续处理
                                brother = delParent->_left; //更新brother
                            }
                            //情况四:brother为黑色,且其左孩子是红色结点
                            brother->_col = delParent->_col;
                            delParent->_col = BLACK;
                            brother->_left->_col = BLACK;
                            RotateR(delParent);
                            break; //情况四执行完毕后调整一定结束
                        }
                    }
                }
            }
        }
    }
    ~RBTree()
    {
        Destroy(_root);
    }
    bool IsRBTree()
    {
        if (_root->_col == RED)
        {
            cout << "根节点为红节点" << endl;
            return false;
        }
        //从根节点到叶节点的黑色节点数
        int refNum = 0;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_col == BLACK)
            {
                ++refNum;
            }

            cur = cur->_left;
        }

        return Check(_root, 0, refNum)&& isValidBST();
    }
    //判断是否为二叉搜索树
    bool isValidBST()
    {
        return _isValidBST(_root, LONG_MIN, LONG_MAX);
    }

private:
    bool _isValidBST(Node* root, long long lower, long long upper)
    {
        if (root == nullptr)
        {
            return true;
        }
        if (root->_kv.first <= lower || root->_kv.first >= upper)
        {
            return false;
        }
        bool left = _isValidBST(root->_left, lower, root->_kv.first);
        bool right = _isValidBST(root->_right, root->_kv.first, upper);
        return left && right;
    }
    bool Check(Node* root, int blackNum, int refNum)
    {
        if (root == nullptr)
        {
            if (refNum != blackNum)
            {
                cout << "存在黑色节点的数量不相等的路径" << endl;
                return false;
            }

            return true;
        }

        if (root->_parent && root->_col == RED && root->_parent->_col == RED)
        {
            cout << root->_kv.first << "存在连续的红色节点" << endl;
            return false;
        }

        if (root->_col == BLACK)
        {
            blackNum++;
        }

        return Check(root->_left, blackNum, refNum)
            && Check(root->_right, blackNum, refNum);
    }
    void Destroy(Node*& root)
    {
        if (root == nullptr)
        {
            return;
        }
        //递归销毁左子树
        Destroy(root->_left);
        //递归销毁右子树
        Destroy(root->_right);
        //销毁根节点
        delete root;
        root = nullptr;
    }


    Node* copy(Node* root)
    {
        // 如果原始节点为空,直接返回空指针
        if (root == nullptr)
        {
            return nullptr;
        }
        // 为新节点分配内存并拷贝原始节点的值
        Node* newnode = new Node(root->_kv);
        // 递归拷贝左子树
        newnode->_left = copy(root->_left);
        // 递归拷贝右子树
        newnode->_right = copy(root->_right);
        // 将新节点的父节点指针置为空
        newnode->_parent = nullptr;
        // 拷贝原始节点的颜色信息
        newnode->_col = root->_col;
        // 如果新节点的左子节点存在,设置其父节点为新节点
        if (newnode->_left)
        {
            newnode->_left->_parent = newnode;
        }
        // 如果新节点的右子节点存在,设置其父节点为新节点
        if (newnode->_right)
        {
            newnode->_right->_parent = newnode;
        }
    }

	Node* _root = nullptr;
};

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

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

相关文章

re正则模块

正则是一个十分重要且基础的模块 学习正则模块就要了解正则的一些基本字符 正则的基本方法有很多 但是大体上分为三种匹配 分割 替换 匹配有match search fullmatch findall finditer 注意finditer得到结果是一个可迭代类型需要遍历才能得到结果 使用group方法就可以查看返回…

【安全】XSS

文章目录 xss1.反射型XSS Payload的一些情况010203040506070809101112131415 HTML文档处理过程0x01 HTML解析0x02 URL解析0x03 JavaScript 解析 2.DOM型Ma Spaghet!JefffUgandan KnucklesRicardo MilosAh Thats HawtLigmaMafia 3.存储型 xss 用户的输入没有进行很好的过滤&…

对比新旧两个数据库表之间的差异

ServerDatabaseVersionUpdateHelper 一个对比不同数据库之间表数据差异的开源软件&#xff0c;欢迎大家到github上点赞 应用下载地址 功能介绍 对比表结构差异和表数据之间的差异 并根据查询生成新的更新sql语句 使用 1. 填写新旧数据库配置 server数据库地址;port数据库端…

报错:xx in xx cannot be applied to ‘()‘ @Data注解的无参构造方法不生效(原因及解决办法)

问题描述 创建User类时&#xff0c;添加了Data注解和User的构造方法 import lombok.Data;Data public class User {private Long id;private String name;private Integer age;private String email;public User(Long id, String name, Integer age, String email) {this.id …

机器学习--常见算法总结

有监督学习算法 1. 线性回归算法 概念&#xff1a;线性回归是一种统计方法&#xff0c;用于预测一个变量&#xff08;因变量&#xff09;与一个或多个自变量&#xff08;特征变量&#xff09;之间的关系。目标是通过线性方程建立自变量和因变量之间的关系模型。 作用&#x…

vertical-align: bottom;

问: 这个弹框中, "张三" 文字在某些ios手机中会上升到顶部, 图片也会移动, 西方二维码也会向下移动, 请问什么原因? 回答: 我们在 "张三" 这个元素dt上, 加上了vertical-align: bottom;这个属性, 让这个在顶部的元素在最下面, 就解决了样式错乱的问题.

SCC-F 23212-0-110310控制器abb面价

SCC-F 23212-0-110310控制器面价 SCC-F 23212-0-110310控制器面价 SCC-F 23212-0-110310控制器面价 SCC-F 23212-0-110310控制模块接线图 SCC-F 23212-0-110310控制模块电路图 SCC-F 23212-0-110310控制模块线路图 SCC-F 23212-0-110310伺服电机控制器是数控系统及其他相…

【C语言】最详细的单链表(两遍包会!)

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C语言数据结构_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:黄灿灿/数据结构 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、前言 二、单链表的概念 1. 单链表的特点 2. 单链表的基本…

Aqua使用记录

Java Kotlin Groovy Python 建议使用Poetry环境 Poetry executable&#xff1a;/Users/wan/Library/Application Support/pypoetry/venv/bin/poetry 安装依赖包 poetry add package 或者在.toml文件添加依赖包信息 Selenium with Python Selenium 生成html测试报告&#x…

Linux驱动——杂项驱动GPIO子系统

一&#xff1a;内核层框架 在介绍linux驱动之前先介绍一下系统。 系统分为两层&#xff1a; 1.系统层 2.内核层 对于内核层就要说一下其中的内核层运行的框架了 代码如下&#xff1a; //头文件 #include "linux/kernel.h" #include "linux/module.h" …

git-版本管理工具基本操作-创建仓库-拉取-推送-暂存库-版本库

1、创建仓库 2、克隆仓库到本地&#xff08;首次拉取需要输入用户名和密码&#xff0c;用户名用邮箱&#xff0c;密码用登录gitee的密码&#xff0c;后面配置密钥后可以直接clone&#xff09; 在命令行输出两行指令配置git才能克隆&#xff1a; username&#xff1a;gitee账号…

2D Inpainting 与NeRF 3D重建的多视角一致性问题

一 问题&#xff1a; NeRF依赖于输入图像的一致性。NeRF&#xff08;Neural Radiance Fields&#xff09;在生成三维场景时&#xff0c;依赖于从多个视角拍摄的输入图像之间的一致性来准确地推断场景的三维结构和颜色信息。 具体来说&#xff1a; 多视角一致性&#xff1a; Ne…

宝塔面板一键部署Inis博客网站结合内网穿透为本地站点配置公网地址

文章目录 前言1. Inis博客网站搭建1.1. Inis博客网站下载和安装1.2 Inis博客网站测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 3. 公网访问测试总…

Day42 | 739. 每日温度 496.下一个更大元素 I 503.下一个更大元素II

语言 Java 739. 每日温度 每日温度 题目 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会升高&#xff0c;请在该…

计算机网络基础详解:从网络概述到安全保障的全面指南

目录 网络基础详细概述 1. 网络概述 1.1数据通信 1.2资源共享 1.3分布式处理 1.4负载均衡 2. 网络分类 2.1按覆盖范围&#xff1a; 2.1.1局域网 (LAN)&#xff1a; 2.1.2城域网 (MAN)&#xff1a; 2.1.3广域网 (WAN)&#xff1a; 2.2按拓扑结构&#xff1a; 2.2.1…

IEEE802网络协议和标准

IEEE802网络协议和标准 802委员会IEEE 802介绍现有标准 IEEE 802.3介绍物理媒介类型MAC子层与LLC子层主要内容通讯标准POE供电标准802.3af、802.3at、802.3btIEEE802.3af的工作过程&#xff1a;IEEE802.3af主要供电参数&#xff1a;IEEE802.3af的分级参数&#xff1a;为什么会有…

C++的序列容器——数组

前言&#xff1a; 这篇文章我们就开始新的章节&#xff0c;我们之前说的C/C的缺陷那部分内容就结束了。在开始新的章之前我希望大家可以先对着题目思考一下&#xff0c;C的容器是什么&#xff1f;有什么作用&#xff1f;下面让我们开始新的内容&#xff1a; 目录 前言&#x…

从数据类型到变量、作用域、执行上下文

从数据类型到变量、作用域、执行上下文 JS数据类型 分类 1》基本类型&#xff1a;字符串String、数字Number、布尔值Boolean、undefined、null、symbol、bigint 2》引用类型&#xff1a;Object (Object、Array、Function、Date、RegExp、Error、Arguments) Symbol是ES6新出…

S7协议转HTTP协议

如下来源成都纵横智控-https://www.iotrouter.com/ 需求概述 本章要实现一个流程&#xff1a;EG8200采集西门子S7-200Smart的数据&#xff0c;并组装成JSON格式通过HTTP上报应用平台。 要采集的PLC点位表如下&#xff1a; PLC S7-200 Smart IP 192.168.0.34/102 点表(DB1…

C++第十一弹 -- STL之List的剖析与使用

文章索引 前言1. list的介绍2 list的使用2.1 list的构造函数2.2 iterator的使用2.3 list capacity2.4 list element access2.5 list modifiers 3. list的迭代器失效4. list与vector的对比总结 前言 本篇我们旨在探讨对于STL中list的使用, 下一篇我们将会对list进行底层剖析以及…