深入解析红黑树(RB-Tree):原理、操作及应用

文章目录

  • 一、红黑树的特点与性质
  • 二、红黑树的实现
    • 1、实现红黑树的插入操作
    • 2、红黑树的验证方法
      • a. Check 函数
      • b. IsBalance 函数

红黑树作为一种自平衡的二叉搜索树,在计算机科学领域中占据着重要的地位。它的设计旨在在维持树的平衡性的同时,保证各种操作的时间复杂度始终稳定在较低水平。红黑树的灵活性和高效性使得它成为众多编程语言和系统实现中不可或缺的数据结构之一。本文将带领读者深入探究红黑树的结构与原理。(c++实现)

一、红黑树的特点与性质

  • 红黑树的定义与性质

红黑树是一种特殊的自平衡二叉查找树,它通过颜色和一系列性质来确保树的平衡,从而实现高效的查找、插入和删除操作。在红黑树中,每个节点都被赋予红色或黑色的颜色,并且这些颜色与树的五个基本性质一起工作,共同维持树的平衡。

红黑树是具有着色性质的二叉搜索树:

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点是黑色。
  3. 每个叶子节点(通常称为NIL或空节点)是黑色。在大多数实现中,叶子节点不实际存储,而是用NIL或空指针表示。
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的(即,不能有两个相邻的红色节点)。
  5. 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点。

这些性质共同确保了红黑树的平衡性。特别是性质5,它树确保没有一条路 径会比其他路径长出两倍。这种平衡性保证了红黑树在查找、插入和删除操作中的时间复杂度都是O(log n),其中n是树中节点的数量。着色法则的一个推论是,红黑树的高度最多是 2log(n+1) 。因此,查找保证是一种对数级别的操作。

在这里插入图片描述

有 n 个节点的红黑树的高度最多是 2log(n+1),这是为什么呢?

首先我们来我们考虑一个问题,根节点为黑高为 h 的红黑树,内部节点个数至少有多少个?(黑高:从某节点出发(不含该节点)到达任意一叶节点的路径上黑节点的总数。)

内部节点上最少的情况就是总共 h 层黑节点的满树形态。若根节点黑高为 h,内部节点数最少有 2h-1 个。因此,若红黑树总高度为 h ,则根节点的黑高肯定是大于等于 h/2 的,因此内部节点数 n >= 2h/2-1,由此推出 h <= 2log(n+1) 。

  • 红黑树与AVL树的对比

红黑树同AVL树类似,也是一种自平衡的二叉搜索树,它通过颜色和一系列性质来维护树的平衡性。与AVL树不同的是,红黑树并不追求完全的平衡,而是允许局部的不平衡,这使得红黑树在插入或删除节点时的旋转操作次数较少,降低了操作的复杂性。同时,红黑树的查找、插入和删除操作的时间复杂度也能保持在O(log n)。由于红黑树的这种特性,它在需要频繁进行插入和删除操作的应用场景中表现得更好。

总的来说,红黑树在平衡性、操作复杂性和性能之间找到了一个较好的平衡点,使得它在许多实际应用中成为了一个优秀的选择。而AVL树虽然具有严格的平衡性,但由于其操作的复杂性以及在实际应用中的效率问题,使得它的使用范围相对较小。普通二叉搜索树虽然简单,但在数据动态变化的情况下,其性能可能会受到影响。因此,在选择使用哪种树形结构时,需要根据具体的应用场景和需求进行权衡。 关于AVL树的内容,大家可以点击这里。


二、红黑树的实现

  • 定义红黑树节点的结构体

在此我们定义一个模板结构体RBTreeNode,它是红黑树中的一个节点。红黑树是一种自平衡的二叉搜索树,其中每个节点都带有颜色属性(红色或黑色)。Colour的枚举类型,它有两个值:REDBLACK。这个枚举类型用于表示红黑树中节点的颜色:

enum Colour { RED, BLACK };// 节点的颜色

template<class K, class V>
struct RBTreeNode{
	RBTreeNode<K, V>* _left;		// 节点的左孩子
	RBTreeNode<K, V>* _right;		// 节点的右孩子
	RBTreeNode<K, V>* _parent;		// 节点的父节点
	pair<K, V> _kv;					// 节点的键值对
	Colour _col;					// 节点的颜色

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};

_left_right_parent都初始化为nullptr,表示新节点最初没有子节点或父节点。 _kv初始化为传入的键值对kv_col初始化为RED,表示新创建的节点默认是红色的。

为什么要默认是红色呢?

红黑树的困难在于将一个新的值插入到树中,通常把新的值作为树叶放到树中。如果插入时把该节点作为黑色,那么一定违反性质5,因为会建立一条更长的黑节点的路径。因此,该节点必须涂成红色。因此我们在构造函数里将其颜色初始化为红色。

template<class K,class V>
class RBTree {
	typedef RBTreeNode<K, V> Node;
public:
    //...
private:
	Node* _root = nullptr;
};

1、实现红黑树的插入操作

在找到插入位置时,如果父节点是黑的,插入完成,因为插入红色节点不会违反性质5。如果它的父节点已经是红色的,那么我们得到连续红色节点,这就违反性质4。在这种情况下,我们必须调整该树以确保性质4满足(且不引起性质5被破坏)。因此我们在插入时需要的基本操作是节点颜色的改变以及树的旋转:

bool Insert(const pair<K, V>& kv) ;

  1. 找到插入位置:我们遍历红黑树,找到新节点应该插入的位置。这通常是通过比较新节点的键与当前节点的键来完成的,直到找到一个空位置(即叶子节点的子节点位置)为止。在这个过程中,我们还记录了新节点的父节点,以便后续操作。

  2. 插入新节点:一旦找到插入位置,我们将新节点插入到树中。这涉及到将新节点的父节点指针指向之前记录的父节点,并根据新节点的键值更新父节点的左子节点或右子节点指针。

    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;
    
  3. 修复红黑树性质:插入新节点后,红黑树可能不再满足其性质。因此,我们需要进行一系列操作来修复这些性质。这主要包括检查新节点的父节点和叔叔节点的颜色,并根据需要执行旋转和重新着色操作。关于旋转操作我们在AVL树中已进行描述,若仍存疑,可以点击此处,或给我留言。

    • 检查父节点颜色:如果新节点的父节点是黑色,那么插入操作不会破坏红黑树的任何性质,因此无需进行任何修复操作。如下图插入10或20,不会破坏红黑树性质:

      在这里插入图片描述

    • 父节点和叔叔节点均为红色:如果新节点的父节点是红色,并且其叔叔节点(父节点的兄弟节点)也是红色,那么我们需要将父节点和叔叔节点都重新着色为黑色,并将祖父节点(父节点的父节点)着色为红色。然后,我们将祖父节点作为新的当前节点,继续向上检查,直到根节点或遇到黑色父节点为止。如图所示:

      在这里插入图片描述

      分为两种对称情况,图示为插入30。若插入3,同理即可。代码实现如下:

      while (parent && parent->_col == RED) { 
          if (parent == grandparent->_left) {
              Node* uncle = grandparent->_right;
              if (uncle && uncle->_col == RED) {
                  parent->_col = uncle->_col = BLACK;
                  grandparent->_col = RED;
      
                  cur = grandparent;
                  parent = cur->_parent;
              }
              else {
                  //....
              }
          }
          else {
              Node* uncle = grandparent->_left;
              if (uncle && uncle->_col == RED) {
                  parent->_col = uncle->_col = BLACK;
                  grandparent->_col = RED;
      
                  cur = grandparent;
                  parent = cur->_parent;
              }
              else {
                  //...
              }
          }
      } 
      

      此段代码用于处理父节点和叔叔节点都为红色的情况:

      parent 是新插入节点的父节点,grandparentparent 的父节点,_col 是节点的颜色(RED 或 BLACK)的属性,_left_right 是指向左右子节点的指针。

      这段代码的逻辑是这样的:

      1. while (parent && parent->_col == RED):这个循环会持续进行,直到 parent 为空(即已经到达根节点)或者 parent 的颜色为黑色。
      2. if (parent == grandparent->_left):判断 parent 是否是 grandparent 的左子节点。
      3. Node* uncle = grandparent->_right;:如果是左子节点,则叔叔节点(uncle)是 grandparent 的右子节点。
      4. if (uncle && uncle->_col == RED):如果叔叔节点存在且为红色,那么执行以下操作:
        • parent->_col = uncle->_col = BLACK;:将父节点和叔叔节点的颜色都改为黑色。
        • grandparent->_col = RED;:将祖父节点的颜色改为红色。
        • cur = grandparent;parent = cur->_parent;:更新 cur 为祖父节点,并将 parent 更新为 cur 的父节点,以便在下一次循环中继续向上检查。
      5. 如果叔叔节点不存在或者不是红色,代码进入 else 块,这里通常包含一些旋转和颜色调整的逻辑,我们将在下面继续讨论。

      我们给出抽象图,注意对称情况,我们不在赘述:

      在这里插入图片描述

      如果这次更新完后,祖父节点作为了新节点。它的父节点为红,则继续向上更新。

    • 父节点为红色,叔叔节点不存在或为黑色:在这种情况下,我的当前的节点称为新节点(下文统一称其为新节点),因此该节点可能是由其子树变化得来,也有可能是新插入的节点。我们需要进行旋转操作。具体的旋转方式取决于新节点是父节点的左孩子还是右孩子。如果新节点是父节点的左孩子,并且父节点是其祖父节点的左孩子,则对祖父节点进行右旋;如果新节点是父节点的右孩子,并且父节点是其祖父节点的右孩子,则对祖父节点进行左旋。旋转后,我们将原父节点的颜色更改为黑色,并将祖父节点的颜色更改为红色。如图所示:
      在这里插入图片描述

      分为两种对称情况,图示为插入2。若插入55,同理即可。

      如果新节点是父节点的右孩子,并且父节点是其祖父节点的左孩子,则先对父亲节点进行右旋,再最祖父节点进行左旋。如果新节点是父节点的左孩子,并且父节点是其祖父节点的右孩子,则先对父亲节点进行左旋,再最祖父节点进行右旋。旋转后,我们将原祖父节点的颜色更改为红色,并将新节点的颜色更改为黑色。如图所示:

      在这里插入图片描述

      同样分为两种对称情况,图示为插入4。若插45,先右单旋父节点,再左单旋祖父节点。

      ⚠️需要注意的是:

      如果叔叔节点如果不存在,那么新节点一定是新插入的节点

      因为如果新节点不是新插入节点,那么父亲节点和新节点一定由一个节点的颜色是黑色的,不满足性质5:每条路径黑色节点个数相同。

      如果叔叔节点存在,根据判断条件,叔叔是黑色的那么新节点原来的颜色肯定是黑色。变为红色的原因是因为新节点的子树在调整过程中改变了新节点的颜色。

      代码实现如下:

     while (parent && parent->_col == RED) {
    	Node* grandparent = parent->_parent;
    	if (parent == grandparent->_left) {
            Node* uncle = grandparent->_right;
            if (uncle && uncle->_col == RED) {
               //... 叔叔存在且为红
            }
        	else {
                //情况二:叔叔不存在 或者 存在但为黑色
                //旋转+变色
                if (cur == parent->_left) {
                    //       g
                    //    p    u
                    // c
                    RotateR(grandparent);
                    parent->_col = BLACK;
                    grandparent->_col = RED;
                }
                else {
                    //       g
                    //    p    u
                    //		c
                    RotateL(parent);
                    RotateR(grandparent);
                    cur->_col = BLACK;
                    grandparent->_col = RED;
                }
                break;
            }
    	}
    	else {
        Node* uncle = grandparent->_left;
        if (uncle && uncle->_col == RED) {
            //... 情况一:叔叔存在且为红
        }
        else {
            // 情况二:叔叔不存在或者存在且为黑
            // 旋转+变色
            //      g
            //   u     p
            //            c
            if (cur == parent->_right) {
                RotateL(grandparent);
                parent->_col = BLACK;
                grandparent->_col = RED;
            }
            else {
                //		g
                //   u     p
                //      c
                RotateR(parent);
                RotateL(grandparent);
                cur->_col = BLACK;
                grandparent->_col = RED;
            }
            break;
        }
    }
    
     
    
    
    	 
      
    
    
  4. 如果当前节点cur是其父节点的左孩子,并且父节点是祖父节点的左孩子,则执行右旋操作RotateR(grandparent)。这会将父节点移动到祖父节点的右子树,并相应地更新子节点的父节点指针。旋转后,将父节点变为黑色,祖父节点变为红色。

  5. 如果当前节点cur是其父节点的右孩子,并且父节点是祖父节点的左孩子,则首先执行左旋操作RotateL(parent),将当前节点cur移动到父节点的左子树。接着执行右旋操作RotateR(grandparent),将父节点移动到祖父节点的右子树。旋转后,将当前节点cur变为黑色,祖父节点变为红色。

类似地,当父节点是祖父节点的右孩子时,执行相反的操作:

  1. 如果当前节点cur是其父节点的右孩子,并且父节点是祖父节点的右孩子,则执行左旋操作RotateL(grandparent)
  2. 如果当前节点cur是其父节点的左孩子,并且父节点是祖父节点的右孩子,则首先执行右旋操作RotateR(parent),接着执行左旋操作RotateL(grandparent)

我们分别给出新节点与父亲在不同方向的抽象图和相同方向的抽象图,注意对称情况:

在这里插入图片描述

如果新节点是父节点的右孩子,并且父节点是其祖父节点的左孩子,则先对父亲节点进行右旋,再最祖父节点进行左旋。如果新节点是父节点的左孩子,并且父节点是其祖父节点的右孩子,则先对父亲节点进行左旋,再最祖父节点进行右旋。

在这里插入图片描述

如果新节点是父节点的左孩子,并且父节点是其祖父节点的左孩子,则对祖父节点进行右旋;如果新节点是父节点的右孩子,并且父节点是其祖父节点的右孩子,则对祖父节点进行左旋。

此次旋转完后,我们就结束循环,此时可以确保树仍然满足红黑树的定义。

  1. 确保根节点为黑色:在所有修复操作完成后,我们还需要确保根节点是黑色的。如果根节点在修复过程中被着色为红色,我们需要将其重新着色为黑色。在找到插入位置时,如果父节点是黑的,插入完成,因为插入红色节点不会违反性质5。如果它的父节点已经是红色的,那么我们得到连续红色节点,这就违反性质4。在这种情况下,我们必须调整该树以确保性质4满足(且不引起性质5被破坏)。因此我们在插入时需要的基本操作是节点颜色的改变以及树的旋转:

    _root->_col = BLACK;
    

通过执行上述步骤,我们可以确保在插入新节点后,红黑树仍然保持其平衡性质,并且能够有效地支持查找、删除等后续操作。红黑树的插入操作在平均情况下的时间复杂度为O(log n),其中n为树中节点的数量。

插入的完整代码如下:

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* grandparent = parent->_parent;
        if (parent == grandparent->_left) {
            Node* uncle = grandparent->_right;
            //情况一  :叔叔存在且为红
            if (uncle && uncle->_col == RED) {
                //先变色 后向上处理
                parent->_col = uncle->_col = BLACK;
                grandparent->_col = RED;

                cur = grandparent;
                parent = cur->_parent;
            }
            else {
                //情况二:叔叔不存在 或者 存在但为黑色
                //旋转+变色
                if (cur == parent->_left) {
                    //       g
                    //    p    u
                    // c
                    RotateR(grandparent);
                    parent->_col = BLACK;
                    grandparent->_col = RED;
                }
                else {
                    //       g
                    //    p    u
                    //		c
                    RotateL(parent);
                    RotateR(grandparent);
                    cur->_col = BLACK;
                    grandparent->_col = RED;
                }
                break;
            }
        }
        else {
            Node* uncle = grandparent->_left;
            // 情况一:叔叔存在且为红
            if (uncle && uncle->_col == RED) {
                // 变色
                parent->_col = uncle->_col = BLACK;
                grandparent->_col = RED;

                // 继续往上处理
                cur = grandparent;
                parent = cur->_parent;
            }
            else 
            {
                // 情况二:叔叔不存在或者存在且为黑
                // 旋转+变色
                //      g
                //   u     p
                //            c
                if (cur == parent->_right) {
                    RotateL(grandparent);
                    parent->_col = BLACK;
                    grandparent->_col = RED;
                }
                else {
                    //		g
                    //   u     p
                    //      c
                    RotateR(parent);
                    RotateL(grandparent);
                    cur->_col = BLACK;
                    grandparent->_col = RED;
                }

                break;
            }
        }
    }
    _root->_col = BLACK;
    return true;
}

2、红黑树的验证方法

红黑树也是一种特殊的二叉搜索树,因此我们可以先获取二叉树的中序遍历序列,来判断该二叉树是否满足二叉搜索树的性质:

void _InOrder(Node* root){
    if (root == nullptr)
        return;

    _InOrder(root->_left);
    cout << root->_kv.first << endl;
    _InOrder(root->_right);
}
void InOrder() { _InOrder(_root); } 

我们也要检测其是否满足红黑树的性质,我们需要两个函数:

a. Check 函数

Check 函数用于递归地检查红黑树的性质是否得到满足。它接受三个参数:当前节点 cur、从根节点到当前节点路径上的黑色节点数 blackNum 和从根节点到最左侧路径上的黑色节点数 refBlackNum

  1. 空节点检查:如果当前节点为空(cur == nullptr),则检查从根节点到该路径的黑色节点数 blackNum 是否与最左侧路径的黑色节点数 refBlackNum 相等。如果不相等,则输出错误信息并返回 false
  2. 连续红色节点检查:如果当前节点为红色且其父节点也为红色,则违反了红黑树的性质(性质 4),输出错误信息并返回 false
  3. 黑色节点计数:如果当前节点为黑色,则增加 blackNum 的计数。
  4. 递归检查子节点:递归调用 Check 函数检查当前节点的左子树和右子树,只有当左右子树都返回 true 时,当前节点才返回 true
bool Check(Node* cur, int blackNum, int refBlackNum) {
    if (cur == nullptr) {
        if (refBlackNum != blackNum) {
            cout << "黑色节点的数量不相等" << endl;
            return false;
        }

        cout << "黑色节点的数量:" << blackNum << endl;
        return true;
    }

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

    if (cur->_col == BLACK)
        ++blackNum;

    return Check(cur->_left, blackNum, refBlackNum)
        && Check(cur->_right, blackNum, refBlackNum);
}

b. IsBalance 函数

IsBalance 函数用于检查整棵红黑树是否平衡。

  1. 根节点颜色检查:如果根节点存在且为红色,则直接返回 false,因为根节点必须是黑色(性质 2)。
  2. 计算最左侧路径黑色节点数:从根节点开始,沿着最左侧路径遍历树,计算遇到的黑色节点数,存储在 refBlackNum 中。
  3. 调用 Check 函数:从根节点开始,调用 Check 函数递归地检查整棵树是否满足红黑树的性质。传递的初始黑色节点数 blackNum 为 0,参考黑色节点数 refBlackNum 为之前计算得到的值。
bool IsBalance(){
    if (_root && _root->_col == RED)
        return false;
    int refBlackNum = 0;
    Node* cur = _root;
    while (cur){
        if (cur->_col == BLACK)
            refBlackNum++;
        cur = cur->_left;
    }

    return Check(_root, 0, refBlackNum);
}

如果 Check 函数返回 true,则说明红黑树平衡,IsBalance 函数也返回 true;否则,返回 false

本文代码置于:RBtree · 比奇堡的Zyb/每日学习 - 码云 - 开源中国 (gitee.com)

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

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

相关文章

【JavaScript】JavaScript 运算符 ⑤ ( 赋值运算符 | 基础赋值运算符 与 复合赋值运算符 )

文章目录 一、JavaScript 赋值运算符1、赋值运算符 概念2、基础赋值运算符 与 复合赋值运算符3、复合赋值运算符4、完整代码示例 一、JavaScript 赋值运算符 JavaScript 赋值运算符种类 : 基础赋值运算符 : 等于 : ; 复合赋值运算符 : 加等 : 减等 : -乘等 : *除等 : /取模等…

基于springboot+vue的房屋交易平台

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

App拉新必备!Xinstall渠道追踪,让每一分钱都花在刀刃上

在移动互联网时代&#xff0c;App已经成为人们日常生活中不可或缺的一部分。然而&#xff0c;对于App开发者来说&#xff0c;如何有效地进行拉新&#xff0c;提高用户留存率&#xff0c;一直是一个难题。而渠道追踪&#xff0c;作为App推广过程中的重要环节&#xff0c;往往被忽…

029—pandas 遍历行非向量化修改数据

前言 在 pandas 中&#xff0c;向量化计算是指利用 pandas 对象的内置方法和函数&#xff0c;将操作应用到整个数据结构的每个元素&#xff0c;从而在单个操作中完成大量的计算。 但在一些需求中&#xff0c;我们无法使用向量化计算&#xff0c;就需要迭代操作&#xff0c;本例…

前端三件套 | 综合练习:模拟抽奖活动,实现一个简单的随机抽取并显示三名获胜者

随机运行结果如下&#xff1a; 参考代码如下&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><tit…

通俗易懂的Python循环讲解

循环用于重复执行一些程序块。从上一讲的选择结构&#xff0c;我们已经看到了如何用缩进来表示程序块的隶属关系。循环也会用到类似的写法。 for循环 for循环需要预先设定好循环的次数(n)&#xff0c;然后执行隶属于for的语句n次。 基本构造是 for 元素 in 序列: statemen…

常用芯片学习——BME280芯片

BME280 温湿度气压传感器 芯片介绍 BME280是基于成熟传感原理的组合数字湿度、压力和温度传感器。该传感器块采用极为紧凑的金属盖LGA封装&#xff0c;占地面积仅为2.5x2.5mm2&#xff0c;高度为0.93mm。该传感器提供I2C以及SPI接口。它的小尺寸和低功耗允许在电池驱动的设备…

如何写好Stable Diffusion的prompt

Stable Diffusion是一种强大的文本到图像生成模型&#xff0c;其效果在很大程度上取决于输入的提示词&#xff08;Prompt&#xff09;。以下是一些关于如何编写有效的Stable Diffusion Prompt的秘诀&#xff1a; 明确描述&#xff1a;尽量清晰地描述你想要的图像内容。使用具体…

算法第二十九天-最长公共子序列

最长公共子序列 题目要求 解题思路 求这两个数组或者字符串的最长公共子序列问题&#xff0c;肯定要用到动态规划。 首先区分两个概念&#xff1a;子序列可以是不连续的&#xff1b;子数组&#xff08;子字符串&#xff09;是需要连续的&#xff1b;另外&#xff0c;动态规划…

2024年腾讯云免费服务器4核8G配置申请

腾讯云免费服务器4核8G配置申请入口 https://curl.qcloud.com/FJhqoVDP 免费服务器可选轻量应用服务器和云服务器CVM&#xff0c;轻量配置可选2核2G3M、2核8G7M和4核8G12M&#xff0c;CVM云服务器可选2核2G3M和2核4G3M配置&#xff0c;腾讯云服务器网txyfwq.com分享2024年最新腾…

OpenAI Q-Star:AGI距离自我意识越来越近

最近硅谷曝出一份54页的内部文件&#xff0c;揭露了去年OpenAI宫斗&#xff0c;导致Altman&#xff08;奥特曼&#xff09;差点离职的神秘项目——Q-Star&#xff08;神秘代号Q*&#xff09;。 根据该文件显示&#xff0c;Q-Star多模态大模型拥有125万亿个参数&#xff0c;比现…

【GPT-SOVITS-01】源码梳理

说明&#xff1a;该系列文章从本人知乎账号迁入&#xff0c;主要原因是知乎图片附件过于模糊。 知乎专栏地址&#xff1a; 语音生成专栏 系列文章地址&#xff1a; 【GPT-SOVITS-01】源码梳理 【GPT-SOVITS-02】GPT模块解析 【GPT-SOVITS-03】SOVITS 模块-生成模型解析 【G…

RuiYi-Vue开源项目1-下载并实现运行RuiYi-Vue项目

下载并实现运行RuoYi项目 RuiYi-Vue介绍环境需要下载项目项目配置后端项目配置前端项目配置 启动后前端登录页面截图 RuiYi-Vue介绍 RuoYi-Vue 是一个 Java EE 企业级快速开发平台&#xff0c;基于经典技术组合&#xff08;Spring Boot、Spring Security、MyBatis、Jwt、Vue&a…

clickhouse学习笔记01(小滴课堂)

老王经历-数据库架构演变历史 你是否能分清OLTP和OLAP系统 急速掌握-数据库里面行存储和列式存储 新一代列式存储ClickHouse介绍和应用场景说明 Linux服务器容器化部署ClickHouse实战 记得要在安全组里配置开放端口号。 到这我们就安装完了。 简单使用&#xff1a; 创建你的第…

P1093 [NOIP2007 普及组] 奖学金

[NOIP2007 普及组] 奖学金 题目背景 NOIP2007 普及组 T1 题目描述 某小学最近得到了一笔赞助&#xff0c;打算拿出其中一部分为学习成绩优秀的前 5 5 5 名学生发奖学金。期末&#xff0c;每个学生都有 3 3 3 门课的成绩&#xff1a;语文、数学、英语。先按总分从高到低排…

关于volatile与指令重排序的探讨

写在开头 在之前的学习我们了解到&#xff0c;为了充分利用缓存&#xff0c;提高程序的执行速度&#xff0c;编译器在底层执行的时候&#xff0c;会进行指令重排序的优化操作&#xff0c;但这种优化&#xff0c;在有些时候会带来 有序性 的问题。 那何为有序性呢&#xff1f;…

Gin 框架中前端向后端传值的几种方式介绍

我将为您详细讲解 Gin 框架中前端向后端传值的几种方式&#xff0c;并给出相应的简单例子。Gin 是一个高性能的 Web 框架&#xff0c;用于构建后端服务。在 Web 应用程序中&#xff0c;前端通常需要向后端发送数据&#xff0c;以便后端能够进行处理。以下是几种常见的前端向后端…

数据可视化学习:Matplotlib概述

一、图表的常用设置 1.基本绘图主要函数 (1).matplotlib.pyplot.plot(x,y,format_string,**kwargs) 2.参数说明 (1).x:x轴数据 (2).y:y轴数据 (3).format_string:控制曲线格式的字符串&#xff0c;包括颜色、线条样式和标记样式 (4)**kwargs:键值参数&#xff0c;相当于…

go rabbitmq 操作

go rabbitmq 操作 go 依赖包github.com/streadway/amqp docker快速部署 docker pull rabbitmq:management docker run -d rabbitmq:management # 先跑一个看看监听了哪些端口 docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq #5672 go 程序连接&#x…

TPU浅谈

前言 大家好&#xff0c;我是jiantaoyab&#xff0c;上篇文章讲了FPGA和ASIC&#xff0c;讲解了 FPGA 如何实现通过“软件”来控制“硬件”&#xff0c;以及我们可以进一步把 FPGA 设计出来的电路变成一块 ASIC 芯片。今天我们来看看TPU。大家可以点击这篇文章TPU深入了解TPU。…