DS进阶:AVL树和红黑树

一、AVL树

1.1 AVL树的概念

        二叉搜索树(BST)虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
(1)它的左右子树都是AVL树
(2)左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
 

AVL树有多种实现版本,但是我们采用平衡因子的版本来模拟实现AVL树。

1.2 AVL树的节点定义

        AVL树每插入或者删除一个节点,都有可能会影响高度,所以大多数情况下都需要向上调整平衡因子,所以我们的实现采用三叉链的形式(left、right、parent),方便我们找父节点,并且引入平衡因子,为的就是通过对平衡因子的调整和判断该树是否需要进行旋转。

template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf;//平衡因子 balance factorAVLTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};

      平衡因子的大小=右子树的高度-左子树的高度。 

1.3 AVL树的插入

        首先AVL树本质上也是BST的逻辑,只不过增加了平衡因子来控制高度。所以在按照BST的逻辑插入节点之后,我们要去向上调整平衡因子。逻辑:如果我(cur)是你(parent)的左子树,那么你的bf就-- ,如果我是你的右子树,那么你的bf就++(因为平衡因子的大小=右子树的高度-左子树的高度。

       而现在需要思考的问题就是,什么情况下需要去进行调整。因此我们需要分情况讨论

1、如果调整过后,平衡因子的绝对值为1,说明调整之前的平衡因子为0,即左右高度是相等的,此时变成1说明树的高度变了,因此需要继续向上调整。

2、如果调整过后,平衡因子的绝对值为2,说明调整之前的平衡因子绝对值为1,说明子树已经严重不平衡并且破坏了AVL树的规则,此时我们就要进行旋转。旋转过后就可以结束循环了

3、如果调整过后,平衡因子的绝对值是0,说明调整之前的平衡因子的绝对值是1,这说明之前的高度是不平衡的,插入之后反而变得更平衡了,此时就可以结束循环了。

       根据上面的这些规则,我们现将整个架子先搭建起来,然后再去研究当bf的绝对值为2的时候应该怎么去进行旋转。

	bool insert(const pair<K, V>& kv){//如果为空树,新节点就是根if (_root == nullptr){_root = new Node(kv);return true;}//如果不为空树Node* parent = nullptr;Node* cur = _root;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;//相等 }//此时肯定是对应地接在parent的后面cur = new Node(kv);if (kv.first < parent->_kv.first)   parent->_left =cur;                //比父亲小连左边else  parent->_right = cur; //比父亲大连右边//别忘了父亲指针cur->_parent = parent;//调整平衡因子while (parent){//首先调整因子,如果我是你的左孩子,这时你的左边变高了,所以要--//如果我是你的右孩子,此时你的右边变高了,所以要++if (cur == parent->_left) --parent->_bf;else ++parent->_bf;//更新之后,我们要根据当前多的情况,决定是向上更新,还是旋转,还是退出if (abs(parent->_bf) == 1)  //更新之后变成1,说明之前是左右相等,现在高度变了{parent = parent->_parent;//继续向上更新cur = cur->_parent;}// 更新之后变成0,说明之前是1或者 - 1,插入后更加平衡了,此时就不需要进行下去了else if (abs(parent->_bf) == 0)  break;else if (abs(parent->_bf) == 2) //说明子树不平衡,需要对子树进行旋转处理{//……………………… (旋转)break;}

1.4 AVL树的旋转(重点)

每一个模块都分别画了抽象图和具象图

1.4.1 新节点插入较高左子树的左侧 (左左->右单旋)

1.4.2 新节点插入较高右子树的右侧 (右右->左单旋)

1.4.3 新节点插入较高左子树的右侧 (左右->左右双旋)

1.4.4 新节点插入较高右子树的左侧 (右左->右左双旋)

 1.4.5 旋转的代码完善

        分析完上面的四种情况,我们通过抽象图去判断具体应该怎么旋转,分别封装对应的旋转函数,然后再根据图去看看哪一些需要去调整平衡因子。统一放在我们的代码逻辑里面。 

       平时我们记忆该选择哪一种旋转方式,可以先画出折线图再一步一步去分析。

	void RotateR(Node *parent){//旋转前,先记录对应的节点Node* subL = parent->_left;Node* subLR = subL->_right;Node* ppnode = parent->_parent;//子树的前驱节点//先让b变成60的左边parent->_left = subLR;if (subLR) subLR->_parent = parent;//让60变成30的右边subL->_right = parent;parent->_parent = subL;//此时与前驱节点连接起来 如果前驱节点为空,直接改变根if (ppnode==nullptr){_root = subL;_root->_parent = nullptr;}//如果前驱节点不为空,此时要根据之前paernt的情况决定插在哪边else{if (ppnode->_left == parent) ppnode->_left = subL;else ppnode->_right = subL;//向上连接subL->_parent = ppnode;}//旋转完后,别忘记调节平衡因子,按图,都为0subL->_bf = parent->_bf = 0;}void RotateL(Node* parent){//旋转前,先记录对应的节点Node* subR = parent->_right;Node* subRL = subR->_left;Node* ppnode = parent->_parent;//子树的前驱节点//先让b变成30的边parent->_right = subRL;if (subRL) subRL->_parent = parent;//让30变成60的左边subR->_left = parent;parent->_parent = subR;//此时与前驱节点连接起来 如果前驱节点为空,直接改变根if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}//如果前驱节点不为空,此时要根据之前paernt的情况决定插在哪边else{if (ppnode->_left == parent) ppnode->_left = subR;else ppnode->_right = subR;//向上连接subR->_parent = ppnode;}//旋转完后,别忘记调节平衡因子,按图,都为0subR->_bf = parent->_bf = 0;}void RotateLR (Node* parent){//旋转前,先记录相应的节点Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;//先对30左旋,再对90右旋RotateL(subL);RotateR(parent);//此时要调整平衡因子,但是有三种情况if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 0){parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else assert(false);}void RotateRL(Node* parent){//旋转前,先记录相应的节点Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;//先对90右旋,再对30左旋RotateR(subR);RotateL(parent);//此时要调整平衡因子,但是有三种情况if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (bf == 0){parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else if (bf == 1){parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else assert(false);}};

然后我们接着去根据4种旋转方式,判断insert需要调用哪一个类型的函数,完善我们的insert代码

bool insert(const pair<K, V>& kv)
{//如果为空树,新节点就是根if (_root == nullptr){_root = new Node(kv);return true;}//如果不为空树Node* parent = nullptr;Node* cur = _root;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;//相等 }//此时肯定是对应地接在parent的后面cur = new Node(kv);if (kv.first < parent->_kv.first)   parent->_left =cur;                //比父亲小连左边else  parent->_right = cur; //比父亲大连右边//别忘了父亲指针cur->_parent = parent;//调整平衡因子while (parent){//首先调整因子,如果我是你的左孩子,这时你的左边变高了,所以要--//如果我是你的右孩子,此时你的右边变高了,所以要++if (cur == parent->_left) --parent->_bf;else ++parent->_bf;//更新之后,我们要根据当前多的情况,决定是向上更新,还是旋转,还是退出if (abs(parent->_bf) == 1)  //更新之后变成1,说明之前是左右相等,现在高度变了{parent = parent->_parent;//继续向上更新cur = cur->_parent;}// 更新之后变成0,说明之前是1或者 - 1,插入后更加平衡了,此时就不需要进行下去了else if (abs(parent->_bf) == 0)  break;else if (abs(parent->_bf) == 2) //说明子树不平衡,需要对子树进行旋转处理{//旋转处理 1、让子树平衡(降低高度) 2、旋转同时保证是搜索树的逻辑。//此时会出现四种情况,左单旋 右单旋 左右双旋转  右左双旋//情况1 右单旋   较高左子树的左侧if (parent->_bf == -2 && cur->_bf == -1)  RotateR(parent);//情况2 左单旋   较高右子树的右侧else if (parent->_bf == 2 && cur->_bf == 1)  RotateL(parent);//情况3 左右双旋  较高左子树的右侧else if (parent->_bf == -2 && cur->_bf == 1) RotateLR(parent);//情况4  右左双旋  较高右子树的左侧else if (parent->_bf == 2 && cur->_bf == -1) RotateRL(parent);else assert(false);//加个断言防止旋转出问题//旋转处理完之后,可以直接breakbreak;}else  assert(false);//加个断言,防止平衡因子出问题}return true;
}

1.5 AVL树的验证

1.5.1 验证其为二叉搜索树

        这个比较容易,我们可以直接通过一个中序遍历,如果打印出来之后得到的是一个有序序列,说明这就是一个二叉搜索树。但是接下来我们还得判断他是否是平衡树。

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

1.5.2 验证节点的平衡因子是否等于右子树的高度-左子树的高度

     首先我们需要封装一个计算树高度的函数。需要用到后序遍历。

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;
}

       然后看看我们遍历到当前节点的时候,我们用该函数算出左子树的高度和右子树的高度,看看右子树-左子树是否等于平衡因子。 

1.5.3 以每个节点为根的树他的左右子树是否严格遵守高度差

      但是平衡因子是我们自己去调整的,所以我们最关键的还是去判断我们的左右子树的高度差绝对值是否<2 

     需要注意的是,我们必须确保每一个子树都满足AVL树的性质,所以调用完一次isbalance之后,还得去继续判断他的左子树和右子树!!!  这样才能说明他是一颗平衡树!!   

bool IsBalance()
{return _IsBalance(_root);
}bool _IsBalance(Node* root)
{if (root == nullptr) return true;int leftH = _Height(root->_left);int rightH = _Height(root->_right);if (rightH - leftH != root->_bf) //检查平衡因子是否符合要求{cout<< root->_kv.first << "节点平衡因子异常" << endl;return false;}return abs(leftH - rightH) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);
}

 1.6 AVL树的性能

      AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log2N。但是如果要对AVL树做一些结构修改的操
作,性能非常低下
,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,
有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数
据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

     为了解决这个问题,红黑树出现了!!

二、红黑树

2.1 红黑树的概念

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

    总的来说,BST可能会退化成单支树,而AVL旋转过于严格,RB就是一种退而求其次的解决方法。 比如下面这颗树,AVL树一定旋转,但是RB不一定旋转。

 2.2 红黑树的性质

    接下来我们就来探究,大佬是如何通过几条规则来让红黑树确保没有一条路径会比其他路径长出俩倍的。

1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
 

 思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点
个数的两倍?

 2.3 红黑树节点的定义

和将AVL树的平衡因此换成颜色

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){}};

思考:为什么我们默认要把颜色设置成红色??

      要分析究竟设置成红色还是黑色好一点,取决于以上的两条规则(1)如果一个节点是红色的,则它的两个孩子结点是黑色的 。(2)对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点

      首先我们要知道,插入这个节点之前该树一定是保持红黑树的性质,即满足上面的两个规则,如果我们新插入的节点默认选择黑色的话,那么凭空多出来的黑色节点必然会导致规则2被破坏,也就是说我们每插入一次就要去调整。如果我们选择的是红色的话,我们红色的父亲节点是黑色,那么就不需要进行调整,如果父亲节点是红色,我们才要进行调整!!!总的来说就是黑色是必然会破坏规则的,但是红色不一定,所以我们默认选择设置成红色,这样需要考虑的情况会更少一些。

2.4 红黑树的插入和旋转(重点)

情况1: 首先因为我们把默认节点设置为红色,所以如果被插入位置的父亲节点是黑色的话,就不需要进行调整了。

情况2:如果待插入前是空树, 那么新插入元素自动成为根,并且将其设置为黑色

      前两个情况比较简单,而后三个情况就是红黑树的非常关键的逻辑!!首先我们要知道我们调整的前提是当前待插入节点的父节点也是红色。

      如上图我们可以知道,因为待插入节点的父节点必然是红色,所以其祖父节点g必然是黑色,而我们下面情况的分析就是取决于u节点 

情况3:u存在且为红

 情况4:u存在且为黑,由情况3变化而来,插入在较高子树的同一侧(单旋)

 情况5:u存在且为黑,由情况3变化而来,插入在较高子树的另一侧(双旋) 

 总结:

 2.5 红黑树旋转和插入代码实现

//旋转代码和AVL树是一样的,只不过不需要搞平衡因子void RotateL(Node* parent){//旋转前,先记录对应的节点Node* subR = parent->_right;Node* subRL = subR->_left;Node* ppnode = parent->_parent;//子树的前驱节点//先让b变成30的边parent->_right = subRL;if (subRL) subRL->_parent = parent;//让30变成60的左边subR->_left = parent;parent->_parent = subR;//此时与前驱节点连接起来 如果前驱节点为空,直接改变根if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}//如果前驱节点不为空,此时要根据之前paernt的情况决定插在哪边else{if (ppnode->_left == parent) ppnode->_left = subR;else ppnode->_right = subR;//向上连接subR->_parent = ppnode;}}void RotateR(Node* parent){//旋转前,先记录对应的节点Node* subL = parent->_left;Node* subLR = subL->_right;Node* ppnode = parent->_parent;//子树的前驱节点//先让b变成60的左边parent->_left = subLR;if (subLR) subLR->_parent = parent;//让60变成30的右边subL->_right = parent;parent->_parent = subL;//此时与前驱节点连接起来 如果前驱节点为空,直接改变根if (ppnode == nullptr){_root = subL;_root->_parent = nullptr;}//如果前驱节点不为空,此时要根据之前paernt的情况决定插在哪边else{if (ppnode->_left == parent) ppnode->_left = subL;else ppnode->_right = subL;//向上连接subL->_parent = ppnode;}}//先用搜索树的逻辑插入节点,然后再去更新平衡因子。
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->_left;}else if (cur->_kv.first < kv.first) //比你小,你去右子树{parent = cur;cur = cur->_right;}else return false;//相等 }//此时肯定是对应地接在parent的后面cur = new Node(kv);if (kv.first < parent->_kv.first)   parent->_left = cur;                //比父亲小连左边else  parent->_right = cur; //比父亲大连右边//别忘了父亲指针cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//情况1,如果u为存在且为红if (grandfather->_left == parent)//如果p是g的左边,u就在右边{Node* uncle = grandfather->_right;//情况1,如果u为存在且为红 p u变黑,g变红 向上调整if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}else //情况2或者情况3, u为黑或者不存在   旋转+变色{ if (cur == parent->_left) //情况2 右单旋+p变黑 g变红{//      g//   p    u// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else  //情况3 右左双旋  c变黑 g变红{//          g//   p     u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//情况2和情况3都要跳出循环}}else//if (grandfather->_right == parent)//如果p是g的右边,u就在左边    几乎一样,就是旋转的逻辑不同{Node* uncle = grandfather->_left;//情况1,如果u为存在且为红 p u变黑,g变红 向上调整if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}else//情况2或者情况3, u为黑或者不存在   旋转+变色{if (cur == parent->_right) //情况2 左单旋+p变黑 g变红{//      g//   p    u//          cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else  //情况3 左右双旋  c变黑 g变红{//          g//   p     u//       cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//情况2和情况3都要跳出循环}}}_root->_col = BLACK; //预防情况1出现 parent就是根的情况 此时无论如何_root变成黑,总没错return true;
}

 2.6 红黑树的验证规则

bool IsBalance()
{if (_root && _root->_col == RED){cout << "根节点颜色是红色" << endl;return false;}int benchmark = 0;//找到一条路径作为基准值 然后看看其他路径是否相等Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}// 连续红色节点return _Check(_root, 0, benchmark);
}
bool _Check(Node* root, int blackNum, int benchmark)
{if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED&& root->_parent&& root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);
}

2.7 RB和AVL的比较

1、红黑树不追求"完全平衡",即不像AVL那样要求节点的高度差 <= 1,它只要求部分达到平衡,但是提出了为节点增加颜色,红黑树是用非严格的平衡来换取增删节点时候旋转次数的降低。

2、就插入节点导致树失衡的情况,AVL和RB-Tree都是最多两次树旋转来实现复衡rebalance,旋转的量级是O(1),删除节点导致失衡,AVL需要维护从被删除节点到根节点root这条路径上所有节点的平衡,旋转的量级为O(logN),而RB-Tree最多只需要旋转3次实现复衡,只需O(1),所以说RB-Tree删除节点的rebalance的效率更高,开销更小!

 总结:

( 1 )AVL更平衡,结构上更加直观,时间效能针对读取而言更高(搜索效率高);维护稍慢,空间开销较大。
( 2 ) 红黑树,读取略逊于AVL,维护强于AVL(复衡效率高),空间开销与AVL类似,内容极多时略优于AVL,维护优于AVL。

(3)总体来说RB的整体性能高于AVL,因此在实际应用中基本上都是用的RB。

2.8 红黑树的实际应用

1. C++ STL库 -- map/set、mutil_map/mutil_set
2. Java 库
3. linux内核
4. 其他一些库

     在后面关于封装map和set的过程中,会再次用到红黑树的知识,因为STL底层的架子就是用的红黑树。

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

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

相关文章

easyx库的学习(鼠标信息)

前言 本次博客是作为介绍easyx库的使用&#xff0c;最好是直接代码打到底&#xff0c;然后看效果即可 代码 int main() {initgraph(640, 480, EX_SHOWCONSOLE|EX_DBLCLKS);setbkcolor(RGB(231, 114, 227));cleardevice();//定义消息结构体ExMessage msg { 0 };//获取消息wh…

大语言模型微调过程中的 RLHF 和 RLAIF 有什么区别?

目前想要深入挖掘大型语言模型&#xff08;LLM&#xff09;的全部潜力需要模型与我们人类的目标和偏好保持一致。从而出现了两种方法&#xff1a;来自人类反馈的人力强化学习&#xff08;RLHF&#xff09;和来自人工智能反馈的人工智能驱动的强化学习&#xff08;RLAIF&#xf…

FPGA秋招-笔记整理(1)

一、关键路径 关键路径通常是指同步逻辑电路中&#xff0c;组合逻辑时延最大的路径&#xff08;这里我认为还需要加上布线的延迟&#xff09;&#xff0c;也就是说关键路径是对设计性能起决定性影响的时序路径。也就是静态时序报告中WNS&#xff08;Worst Nagative Slack&…

如何从架构层面降低公有云多可用区同时故障的概率

阿里云和腾讯云都曾出现过因一个组件故障而导致所有可用区同时瘫痪的情况。本文将探讨如何从架构设计的角度减小故障域&#xff0c;在故障发生时最小化业务损失&#xff0c;并以 Sealos 的稳定性实践为例&#xff0c;分享经验教训。 抛弃主从&#xff0c;拥抱点对点架构 从腾…

Linux之yum和vim的使用

一、yum的使用 yum 后面跟install要安装的文件名&#xff1a; 若你要安装的文件已经存在&#xff0c;则会出现&#xff1a; 要删除文件&#xff1a; yum remore文件名即可删除 在我们安装完lrzsz之后&#xff0c;可以用rz指令和sz指令&#xff1a; rz指令可以从window窗口中…

鸿蒙OpenHarmony【小型系统运行案例】 (基于Hi3516开发板)

运行 启动系统 在完成Hi3516DV300的烧录后&#xff0c;还需要设置BootLoader引导程序&#xff0c;才能运行OpenHarmony系统。 在Hi3516DV300任务中&#xff0c;单击Configure bootloader&#xff08;Boot OS&#xff09;进行配置即可。 说明&#xff1a; DevEco Device Tool…

MT8788智能模块简介_MTK联发科安卓核心板方案厂商

MT8788安卓核心板是一款具备超高性能和低功耗的4G全网通安卓智能模块。该模块采用联发科AIOT芯片平台&#xff0c;供货周期长。 MT8788核心板搭载了12nm制程的四个Cortex-A73处理器核心和四个Cortex-A53处理器核心&#xff0c;最高主频可达2.0GHz。板载内存容量可选为4GB64GB(也…

《系统架构设计师教程(第2版)》第15章-面向服务架构设计理论与实践-05-SOA设计模式

文章目录 1. 服务注册表模式1.1 服务注册表1.2 SOA治理功能1.3 注册表中的配置文件 2. 企业服务总线&#xff08;ESB&#xff09;模式3. Synchro ESB3. 微服务模式3.1 概述3.2 微服务架构模式方案3.2.1 聚合器微服务1&#xff09;概述2&#xff09;几种特殊的聚合微服务 3.2.2 …

Ubuntu20.04安装redis5.0.7

redis下载命令&#xff1a; wget https://download.redis.io/releases/redis-5.0.7.tar.gz 解压到 opt目录下 tar -zxvf redis-5.0.7.tar.gz -C /opt apt install -y gcc # 安装gccapt install make # 安装make 后面执行make一直报错 make报错后清除&#xff1a; make …

parallels desktop19.3最新版本软件新功能详细介绍

Parallels Desktop是一款运行在Mac电脑上的虚拟机软件&#xff0c;它允许用户在Mac系统上同时运行多个操作系统&#xff0c;比如Windows、Linux等。通过这款软件&#xff0c;Mac用户可以轻松地在同一台电脑上体验不同操作系统的功能和应用程序&#xff0c;而无需额外的硬件设备…

分布式与一致性协议之拜占庭将军问题(三)

拜占庭将军问题 叛将先发送消息 如果是叛将楚先发送作战消息&#xff0c;干扰作战计划&#xff0c;结果会有所不同吗&#xff1f; 在第一轮作战信息协商中&#xff0c;楚向苏秦发送作战指令"进攻",向齐、燕发送作战指令"撤退"&#xff0c;如图所示(当然还…

腾讯云向量数据库-RAG介绍2

1.chunk拆分对最终效果的影响 2.改进知识的拆分方案 3.AI套件 4.相似性检索的关键&#xff1a;embedding技术 嵌入技术是相似性检索的关键&#xff0c;它能够将数据转换为向量表示&#xff0c;并通过比较向量之间的相似性来实现相似性检索&#xff1b;embedding&#xff1a;将…

Jackson 2.x 系列【30】Spring Boot 集成之数据脱敏

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Jackson 版本 2.17.0 本系列Spring Boot 版本 3.2.4 源码地址&#xff1a;https://gitee.com/pearl-organization/study-jaskson-demo 文章目录 1. 概述2. 实现思路3. 案例演示3.1 脱敏规则3.2 自…

解决VSCode中“#include错误,请更新includePath“问题

目录 1、问题原因 2、解决办法 1、问题原因 在编写C程序时&#xff0c;想引用头文件但是出现如下提示&#xff1a; &#xff08;1&#xff09;首先检查要引用的头文件是否存在&#xff0c;位于哪里。 &#xff08;2&#xff09;如果头文件存在&#xff0c;在编译时提醒VSCo…

如何理解自然语言处理中的位置编码(Positional Encoding)

在自然语言处理和特别是在使用Transformer模型中,位置编码(Positional Encoding)是一个关键的概念。它们的作用是为模型提供序列中各个元素的位置信息。由于Transformer架构本身并不像循环神经网络(RNN)那样具有处理序列的固有能力,位置编码因此显得尤为重要。 为什么需…

【学习】服务器解决:重新分配同样端口号后,连不上VScode

原来服务器分配的环境有问题&#xff0c;重新分配了一下。还是同样的端口号&#xff0c;Xshell和xftp能够连接上&#xff0c;但是VScode连接不上。 问题解决: 清除本地 SSH 缓存中与远程主机相关的条目可以通过编辑 known_hosts 文件来实现。这个文件包含了您曾经连接过的远程主…

Linux报错处理:‘abrt-cli status’ timed out

最近登录服务器时出现报错&#xff0c;后来查阅资料发现是因为ssh登录时间很久&#xff0c;登录后出现abrt-cli status timed out 的报错。 1.问题分析 abrt-cli是ABRT(Automated Bug Reporting Tool)的命令行接口&#xff0c;用于在Linux系统中处理和报告程序崩溃。 如果abr…

[Qt的学习日常]--初识Qt

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、Qt的基本…

代码随想录算法训练营第四十六天| LeetCode139.单词拆分

一、LeetCode139.单词拆分 题目链接/文章讲解/视频讲解&#xff1a;https://programmercarl.com/0139.%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86.html 状态&#xff1a;已解决 1.思路 单词明显就是物品&#xff0c;字符串s明显就是背包&#xff0c;那么问题就变成了物品能不能把背…

数据可视化———Tableau

基本认识&#xff1a; 维度&#xff1a;定性—字符串文本&#xff0c;日期和日期时间等等 度量&#xff1a;定量—连续值&#xff0c;一般属于数值 数据类型&#xff1a; 数值 日期/日期时间 字符串 布尔值 地理值 运算符 算数运算符&#xff1a;加减乘除,%取余&#xff0c;…