数据结构 - 红黑树

文章目录

    • 前言
    • 一、红黑树介绍
      • 1、红黑树的概念
      • 2、红黑树的性质
    • 二、实现红黑树
      • 1、基本框架
      • 2、插入
      • 3、删除
      • 4、查找
      • 5、测试红黑树
      • 6、红黑树代码
    • 三、红黑树性能
    • 四、AVL树和红黑树的差别


前言

红黑树是一种二叉搜索树,所以学习前需要学会基本的二叉搜索树,并且需要了解左右旋转操作。

一、红黑树介绍

1、红黑树的概念

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

2、红黑树的性质

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

这些性质确保了红黑树从根到叶子的最长路径不会是最短路径的两倍长,从而保持树的相对平衡。

二、实现红黑树

1、基本框架

(1)红黑树节点

//状态
enum Color
{BLACK,	//黑色RED	//红色
};//树节点
template<class K,class T>
struct RBTreeNode
{RBTreeNode<K,T>* _left;	//左RBTreeNode<K, T>* _right;	//右RBTreeNode<K, T>* _parent;	//父Color _color;	//状态表示pair<K, T> _val;	//数据//构造函数	状态默认红色RBTreeNode(const pair<K, T>& val = pair<K, T>()):_val(val),_left(nullptr),_right(nullptr),_parent(nullptr),_color(RED){}
};

(2)红黑树类

template<class K,class T>
class RBTree
{//重命名typedef RBTreeNode<K, T> Node;public:RBTree() {};~RBTree() {};bool Insert(const pair<K, T>& val)bool Erase(const K& key);Node* Find(const K& key)private:
// 右单旋
void RotateR(Node* parent)
{//左节点Node* L = parent->_left;//左子树右边第一个节点Node* Lr = L->_right;//parent的父亲Node* pparent = parent->_parent;//连接过程L->_right = parent;parent->_parent = L;//该节点可能为空if (Lr){Lr->_parent = parent;}parent->_left = Lr;//更新L的父节点L->_parent = pparent;//是根的情况if (pparent == nullptr){_root = L;}else{if (parent == pparent->_left) pparent->_left = L;else pparent->_right = L;}
}
//左旋转
void RotateL(Node* parent)
{//右边第一个节点Node* R = parent->_right;//右子树第一个左节点Node* Rl = R->_left;//父节点Node* pparent = parent->_parent;//连接过程parent->_right = Rl;if (Rl){Rl->_parent = parent;}R->_left = parent;//更新parent的父节点parent->_parent = R;//更新R的父节点R->_parent = pparent;//是根的情况if (nullptr == pparent){_root = R;}else{if (pparent->_left == parent) pparent->_left = R;else pparent->_right = R;}}Node* _root;
};

2、插入

红黑树的插入操作是一个复杂但高效的过程,它确保了树在插入新节点后仍然保持平衡。
(1)基本步骤:

1、找到插入位置:与二叉搜索树相同,首先通过比较节点值找到新节点应该插入的位置。
2、插入新节点:将新节点插入到找到的位置,并将其初始颜色设置为红色。这是因为将新节点设置为红色可以最小化对树平衡性的影响,同时满足红黑树的性质(对于破坏性质4来说,破坏性质3代价更小)。
3、调整树以保持平衡:插入红色节点后,可能会破坏红黑树的性质。为了恢复这些性质,需要进行一系列的旋转和重新着色操作。

(2)讨论插入节点后维持树的性质的情况
在这里插入图片描述

情况一:如果插入节点的p为黑色,不做处理也满足红黑树性质,结束。

情况二:p和u都是红色。
分析:通过性质可以推导出g为黑色
解决:将p和u变为黑色,将g变为红色。如果g是根节点,则将其变为黑色。否则,继续对g进行同样的调整。

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/36110af0e1864cbba9252b99458232d2.png

情况三:p为红,u为黑或者为空
分析:
a.如果是u不存在,那么cur一定是新插入的节点,因为如果不是新插入节点,那么cur和p就一定会存在一个黑色节点,那么每条路径的黑色节点就不一致了。
b.如果u存在且为黑,那么cur不是新插入节点且一定是黑,因为p是红u为黑,那么说明p向下这条路径一定存在一个黑色节点,如果cur是新插入就会是红,就会导致原来的树就不满足性质4,如果cur原来是红色节点那么就不满足性质3了(为情况二转变而来)。
c.经过旋转后的树已经是红黑树了,结束。

子情况一:cur、p、g都在一边
解决:都在左边,g变红p变黑,对g使用右旋转(都在右边,g边红p变黑,对g使用左旋转)。
在这里插入图片描述
子情况二:cur和p,p和g不同边
解决:cur和p在右边,p和g在左边,cur变黑,g变红,对p使用左旋转,再对g使用右旋转(:cur和p在左边,p和g在右边,cur变黑,g变红,对p使用右旋转,再对g使用左旋转)。
在这里插入图片描述

3、删除

红黑树是一种自平衡的二叉搜索树,它通过修改节点颜色及执行特定的旋转操作来确保树在添加或删除节点后继续保持平衡。删除操作是红黑树中最复杂的操作之一,因为它需要在删除节点后恢复红黑树的性质。
(1)基本步骤:

1、查找节点: 首先,需要在树中找到需要删除的节点。如果找不到,则直接结束。
2、删除节点:如果找到的节点有两个子节点,通常的做法是用它的后继节点(即右子树中的最小节点)来替换它,并删除原后继节点。这保证了被删除的节点最多只有一个非空子节点。
3、修复树: 删除节点后,需要修复树以保持红黑树的特性: 每个节点是红色或黑色。 根节点是黑色。 每个叶子节点(NIL节点,空节点)是黑色。如果一个节点是红色的,则它的子节点必须是黑色的。 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

(2)讨论删除后恢复树的情况
情况一:cur是红色
解决:直接删除即可,不会影响红黑树的性质,结束。

情况二:cur为黑色,且cur存在一个子节点
分析:cur为黑色,且cur存在一个子节点,那么cur的子节点一定是红色。
解决:p连接cur的子节点,且cur子节点改为黑色,结束。

情况三:cur为黑色且不存在子节点
分析:此时想要不破坏性质就需要观察cur的兄弟节点b了。

子情况一:b为红色
解决:b在p的左侧,b变黑,p变红,再进行右旋转(b在p的右侧,b变黑,p变红,再进行左旋转),变化后仍然没有恢复红黑树的性质,需要再次对cur进行调整(此时可能会转化成其他情况),继续。

在这里插入图片描述

子情况二:b为黑,b的子节点都为空或者孩子节点都为黑
解决:将b改为红,如果p为红将p改为黑结束,如果p不为红,进行迭代,cur = p,继续。
在这里插入图片描述
子情况三:b为黑且存在一个红色子节点(剩余的一个子树任意)
假设:cur在p的左侧且b的右节点br为红
解决:将b变为p的颜色,p和br变为黑色,再对p左旋转,旋转完红黑树恢复,结束。
在这里插入图片描述
假设:cur在p的右侧且b的左节点bl为红
解决:将b变为p的颜色,p和bl变为黑色,再对p右旋,旋转完红黑树恢复,结束。
在这里插入图片描述
假设:cur在p的左侧且b的左节点bl为红
解决:将bl变为p的颜色,p和b变为黑色,先进行右旋转b再进行p左旋,旋转完红黑树恢复,结束。
在这里插入图片描述

假设:cur在p的右侧且b的右节点br为红
解决:将br变为p的颜色,p和b变为黑色,先进行对b左旋再进行p右旋,旋转完红黑树恢复,结束。
在这里插入图片描述

4、查找

红黑树查找与二叉搜索树一样。
步骤:

1、开始于根节点:查找操作从树的根节点开始。 比较节点值:将查找值与当前节点的值进行比较。
2、
如果查找值等于当前节点的值,则查找成功,返回该节点。
如果查找值小于当前节点的值,则移动到左子节点。
如果查找值大于当前节点的值,则移动到右子节点。

3、重复步骤2:继续比较并移动,直到找到包含查找值的节点,或者遇到一个叶子节点(NIL节点),这意味着查找值不在树中。
4、查找结束:如果找到包含查找值的节点,则返回该节点;否则,返回NULL或者一个特殊的值表示查找失败。

5、测试红黑树

通过判断根节点是否为黑+每一条路径黑色节点数量是否一样+是否存在父子都为红。
测试代码:

bool IsBalance()
{if (_root == nullptr)return true;if (_root->_color == RED){return false;}// 参考值int refNum = 0;Node* cur = _root;while (cur){if (cur->_color == BLACK){++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);
}
bool Check(Node* root, int blackNum, const int refNum)
{if (root == nullptr){//cout << blackNum << endl;if (refNum != blackNum){cout << "存在黑色节点的数量不相等的路径" << endl;return false;}return true;}//cout << root->_val.first << endl;if (root->_color == RED && root->_parent->_color == RED){cout << root->_val.first << "存在连续的红色节点" << endl;return false;}if (root->_color == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);
}
void test()
{RBTree<int, int> rb;vector<int> arr;for (int i = 1; i <= 10000; i++){arr.push_back(rand());}for (int i = 0; i < arr.size(); i++){int e = arr[i];rb.Insert({ e,e });if (rb.IsBalance())cout << "是红黑树" << endl;elseassert(false);}for (int i = 0; i < arr.size(); i++){rb.Erase(arr[i]);if (rb.IsBalance())cout << "是红黑树" << endl;elseassert(false);}
}

通过插入10000随机数观察是否报错。
在这里插入图片描述
在运行结束后仍然没有报错说明插入、删除没有问题。

6、红黑树代码

#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;//状态
enum Color
{BLACK,	//黑色RED	//红色
};//树节点
template<class K,class T>
struct RBTreeNode
{RBTreeNode<K,T>* _left;	//左RBTreeNode<K, T>* _right;	//右RBTreeNode<K, T>* _parent;	//父Color _color;	//状态表示pair<K, T> _val;	//数据//构造函数	状态默认红色RBTreeNode(const pair<K, T>& val = pair<K, T>()):_val(val),_left(nullptr),_right(nullptr),_parent(nullptr),_color(RED){}
};template<class K,class T>
class RBTree
{//重命名typedef RBTreeNode<K, T> Node;public:RBTree() {};~RBTree() {};//插入bool Insert(const pair<K, T>& val){//找到放val的位置Node* cur = _root;//作为前驱指针,与val节点连接Node* parent = nullptr;while (cur){//向左if (cur->_val.first > val.first){parent = cur;cur = cur->_left;}//向右else if (cur->_val.first < val.first){parent = cur;cur = cur->_right;}//存在相同的值else{return false;}}//插入新节点cur = new Node(val);//不存在根节点,作为根节点if (parent == nullptr) _root = cur;//连接在前驱指针左侧else if (parent->_val.first > val.first){cur->_parent = parent;parent->_left = cur;}//连接在前驱指针右侧else{cur->_parent = parent;parent->_right = cur;}//父亲不为空且孩子和父亲都为红 --- 违规规则三while (parent != nullptr &&parent->_color == RED && cur->_color == RED){Node* grandfather = parent->_parent;	//祖父Node* uncle = nullptr;	//叔叔if (grandfather->_left == parent)uncle = grandfather->_right;elseuncle = grandfather->_left;//情况一 uncle存在且为红 - 祖父变红父亲和叔叔变黑,向上更新if (uncle != nullptr && uncle->_color == RED){parent->_color = uncle->_color = BLACK;grandfather->_color = RED;}//情况二 uncle不存在\存在else{//子情况一,cur 、parent在一边//都在左边 -- 使用右旋转if (grandfather->_left == parent && parent->_left == cur){RotateR(grandfather);grandfather->_color = RED;parent->_color = BLACK;}//都在右边 --- 使用左旋转else if (grandfather->_right == parent && parent->_right == cur){RotateL(grandfather);grandfather->_color = RED;parent->_color = BLACK;}//子情况二	cur与parent不再同一边//cur在右边parent在左边  --对parent左旋转再对grandfather右旋转else if (grandfather->_left == parent && parent->_right == cur){RotateL(parent);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;}//cur在左边parent在右边  --对parent右旋转再对grandfather左旋转else if (grandfather->_right == parent && parent->_left == cur){RotateR(parent);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}//经过旋转后树达到平衡break;}//迭代cur = grandfather;parent = cur->_parent;}//根节点一定是黑色的_root->_color = BLACK;return true;}//删除bool Erase(const K& key){//从根节点开始搜索Node* cur = _root;//作为cur的前驱指针Node* parent = nullptr;//搜索查找while (cur){if (cur->_val.first > key){parent = cur;cur = cur->_left;}else if (cur->_val.first < key){parent = cur;cur = cur->_right;}elsebreak;}//找不到if (cur == nullptr) return false;//假设cur左右节点都存在,找右边最小值替换if (cur->_left != nullptr && cur->_right != nullptr){Node* tmp1 = cur->_right;Node* tmp2 = nullptr;while (tmp1->_left){tmp2 = tmp1;tmp1 = tmp1->_left;}cur->_val = tmp1->_val;//tmp1左边没有节点,自己就是最小的节点if (tmp2 == nullptr){parent = cur;cur = tmp1;}else{cur = tmp1;parent = tmp2;}}//如果是cur根节点if (parent == nullptr){if (cur->_left == nullptr){_root = cur->_right;if (_root != nullptr)_root->_color = BLACK;}else{_root = cur->_left;_root->_color = BLACK;}delete cur;return true;}//标记要删除的节点Node* deletecur = cur;//删除节点为红色,直接删除结束if (cur->_color == RED){if (cur->_left == nullptr){if (cur == parent->_left)	parent->_left = cur->_right;else parent->_right = cur->_right;}else{if (cur == parent->_right)	parent->_right = cur->_left;else parent->_right = cur->_left;}}//删除节点为黑色else{//存在左子树,变为黑色if (cur->_left != nullptr){cur->_left->_color = BLACK;if (cur == parent->_right)parent->_right = cur->_left;elseparent->_left = cur->_left;cur->_left->_parent = parent;}//存在右子树else if (cur->_right != nullptr){cur->_right->_color = BLACK;if (cur == parent->_left)	parent->_left = cur->_right;elseparent->_right = cur->_right;cur->_right->_parent = parent;}//不存在孩子else{while (parent != nullptr){int sign = 0;if (cur == parent->_left) sign = -1;else sign = 1;if (cur == deletecur){if (sign == -1) parent->_left = nullptr;else parent->_right = nullptr;}//兄弟节点Node* brothers = nullptr;if (sign == -1) brothers = parent->_right;else brothers = parent->_left;//兄弟节点为红色if (brothers->_color == RED){//变色brothers->_color = BLACK;parent->_color = RED;//在右边-》左转if (brothers == parent->_right){RotateL(parent);}//在左边-》右转else{RotateR(parent);}//重新更新brothersif (sign == -1) brothers = parent->_right;else brothers = parent->_left;}//兄弟节点的孩子节点为空或者孩子节点为黑if ((brothers->_left == nullptr || brothers->_left->_color == BLACK) && (brothers->_right == nullptr || brothers->_right->_color == BLACK)){brothers->_color = RED;//父亲为红色时直接结束if (parent->_color == RED){parent->_color = BLACK;break;}//更新继续cur = parent;parent = parent->_parent;}//兄弟节点不为空有一个孩子为红或者都为红else{//cur作为parend的左孩子if (sign == -1){//作为brothers右孩子--左旋if (brothers->_right && brothers->_right->_color == RED){brothers->_color = parent->_color;parent->_color = brothers->_right->_color = BLACK;RotateL(parent);}//作为brothers左孩子,先右旋再左旋else if (brothers->_left && brothers->_left->_color== RED){brothers->_left->_color = parent->_color;brothers->_color = parent->_color = BLACK;			RotateR(brothers);RotateL(parent);}}else{//作为brothers左孩子--右旋if (brothers->_left&&brothers->_left->_color == RED){brothers->_color = parent->_color;parent->_color = brothers->_left->_color = BLACK;RotateR(parent);}//作为brothers右孩子--先左旋再右旋else if (brothers->_right && brothers->_right->_color == RED){brothers->_right->_color = parent->_color;brothers->_color = parent->_color = BLACK;RotateL(brothers);RotateR(parent);}}//再旋转完后性质恢复,结束break;}}}}delete deletecur;return true;}//查找Node* Find(const K& key){//从根节点开始Node* cur = _root;while (cur){//大了就去左子树中搜索if (cur->_val.first > key){cur = cur->_left;}//小了就去右子树中搜索else if (cur->_val.first < key){cur = cur->_right;}else{//找到返回当前节点return cur;}}return nullptr;}bool IsBalance(){if (_root == nullptr)return true;if (_root->_color == RED){return false;}// 参考值int refNum = 0;Node* cur = _root;while (cur){if (cur->_color == BLACK){++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);}
private:// 右单旋void RotateR(Node* parent){//左节点Node* L = parent->_left;//左子树右边第一个节点Node* Lr = L->_right;//parent的父亲Node* pparent = parent->_parent;//连接过程L->_right = parent;parent->_parent = L;//该节点可能为空if (Lr){Lr->_parent = parent;}parent->_left = Lr;//更新L的父节点L->_parent = pparent;//是根的情况if (pparent == nullptr){_root = L;}else{if (parent == pparent->_left) pparent->_left = L;else pparent->_right = L;}}//左旋转void RotateL(Node* parent){//右边第一个节点Node* R = parent->_right;//右子树第一个左节点Node* Rl = R->_left;//父节点Node* pparent = parent->_parent;//连接过程parent->_right = Rl;if (Rl){Rl->_parent = parent;}R->_left = parent;//更新parent的父节点parent->_parent = R;//更新R的父节点R->_parent = pparent;//是根的情况if (nullptr == pparent){_root = R;}else{if (pparent->_left == parent) pparent->_left = R;else pparent->_right = R;}}bool Check(Node* root, int blackNum, const int refNum){if (root == nullptr){//cout << blackNum << endl;if (refNum != blackNum){cout << "存在黑色节点的数量不相等的路径" << endl;return false;}return true;}//cout << root->_val.first << endl;if (root->_color == RED && root->_parent->_color == RED){cout << root->_val.first << "存在连续的红色节点" << endl;return false;}if (root->_color == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}Node* _root;
};

三、红黑树性能

  1. 时间复杂度 查找、插入和删除操作:红黑树通过特定的旋转和重新着色操作来保持树的平衡,从而确保这些操作的时间复杂度保持在O(log n),其中n是树中元素的数目。这使得红黑树在大数据集上仍然能够保持高效的性能。
  2. 平衡性 平衡因子:与AVL树不同,红黑树并不直接维护每个节点的平衡因子(左子树高度减去右子树高度)。相反,它通过维护一系列颜色相关的性质来间接保持树的平衡。这些性质包括节点的颜色(红色或黑色)、根节点是黑色、所有叶子节点是黑色、红色节点的两个子节点都是黑色等。
    路径长度限制:红黑树的这些性质确保了从根到任意叶子节点的最长路径不会超过最短路径的两倍。这种平衡性保证了在最坏情况下,查找、插入和删除操作的时间复杂度仍然是O(log
    n)。
  3. 插入和删除操作的性能 插入操作:在插入新节点时,红黑树会首先将其标记为红色,并通过一系列的旋转和重新着色操作来恢复树的平衡。由于红黑树的自平衡策略相对宽松,因此在插入操作中通常比AVL树具有更好的性能。
    删除操作:删除操作可能稍微复杂一些,因为它涉及到节点的删除以及后续可能的旋转和重新着色操作来恢复树的平衡。然而,与插入操作类似,删除操作的时间复杂度也保持在O(log
    n)。

四、AVL树和红黑树的差别

  1. 调整平衡的实现机制 AVL树:AVL树通过维护每个节点的平衡因子(左子树高度减去右子树高度)来实现平衡。在插入或删除节点后,如果某个节点的平衡因子绝对值大于1,则需要通过旋转操作来恢复平衡。AVL树的平衡条件是严格的,即所有节点的左右子树高度差的绝对值不超过1。
    红黑树:红黑树则通过节点的颜色(红色或黑色)和一系列旋转操作来保持树的平衡。红黑树的平衡条件相对宽松,它要求从根节点到叶子节点的所有路径上黑色节点的数量相同,从而间接保证了树的平衡性。在插入或删除节点后,红黑树通过重新着色和旋转操作来恢复平衡,且任何不平衡都可以在三次旋转之内解决。
  2. 性能差异 插入和删除操作:由于AVL树需要保持严格的平衡性,因此在插入或删除节点时可能需要进行更多的旋转操作,这在一定程度上降低了其性能。相比之下,红黑树通过非严格的平衡条件换取了更少的旋转次数,从而在插入和删除操作上表现出更高的效率。
    查找操作:在查找操作上,AVL树和红黑树的性能相当。它们都保持了二叉查找树的性质,可以在对数时间内完成查找操作。然而,由于AVL树的高度通常比红黑树更低(在相同节点数的情况下),因此从理论上讲,AVL树的查找效率可能会略高于红黑树,但这种差异在实际应用中往往可以忽略不计。
  3. 内存占用 AVL树需要存储每个节点的平衡因子,这增加了额外的内存开销。 红黑树则通过节点的颜色属性来间接维护平衡性,无需存储额外的平衡因子信息,因此在内存占用上更为节省。
  4. 适用场景 AVL树:适用于查找操作频繁且对内存占用要求不高的场景。由于AVL树保持了严格的平衡性,因此在查找性能上具有一定优势。然而,其插入和删除操作的性能相对较低,不适合数据变动频繁的场景。
    红黑树:适用于插入和删除操作频繁且对查找性能要求不是极端严格的场景。红黑树通过牺牲一定的平衡性来换取更高的插入和删除效率,因此在这些场景下表现出更好的整体性能。此外,红黑树的内存占用更低,也使其在一些内存受限的环境中更具优势。
    综上所述,AVL树和红黑树在调整平衡的实现机制、性能差异、内存占用和适用场景等方面存在显著的区别。在选择使用哪种数据结构时,需要根据具体的应用场景和需求进行综合考虑。

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

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

相关文章

OnlyOffice在线部署

部署服务环境&#xff1a;Centos7.6 curl -sL https://rpm.nodesource.com/setup_6.x | sudo bash 安装yum-utils工具 yum install yum-utils 添加nginx.repo源(Nginx官网有最新版&#xff0c;直接copy即可) vim /etc/yum.repos.d/nginx.repo [nginx-stable] namenginx st…

Stable Diffusion 使用详解(4)---- 制作情景文本

目录 背景 制作流程 绘制底图 书写提示词 选底模 常规参数设置 controlNet 处理 Candy controlNet 设置 Depth controlNet 设置 输出效果 改进 适当修改提示词 适当修改controlNet 适当修改底模 背景 制作情景文本&#xff0c;将文本较好的融入背景图片。首先要…

c->c++(三):stl

本文主要探讨c的stl相关知识:模版&#xff0c;容器&#xff0c;泛型算法&#xff0c;萃取特化&#xff0c;智能指针等。 模版 模板typename和class均可定义 模板参数可是类型,还可是值 模板编译根据调用实参类型推导参数类型 编译器用值的类型…

以西门子winCC为代表的组态界面,还是有很大提升空间的。

组态界面向来都是功能为主&#xff0c;美观和体验性为辅的&#xff0c;这也导致了国内的一些跟随者如法炮制&#xff0c;而且很多操作的工程师也是认可这重模式&#xff0c;不过现在一些新的组态软件可是支持精美的定制化界面&#xff0c;还有3D交互效果&#xff0c;这就是确实…

Arthas在线诊断案例实战整理

会一直持续更新。。。 Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时…

高清无水印,录屏软件对比盘点

现在生活中不论是想要记录赛事精彩瞬间、制作教学视频&#xff0c;都可以用录屏大师这样的录屏软件来实现。今天我就介绍几款备受好评的录屏工具。 1.福昕录屏大师 链接直达&#xff1a;https://www.foxitsoftware.cn/REC/ 这个软件就是一个专业的录屏工具。它可以控制屏幕…

负载均衡、软件平滑升级

安装nginx 1.26.1 平滑升级、负载均衡 安装依赖 gcc gcc-c pcre-devel openssl-devel 七层负载均衡配置&#xff1a; [rootf ~]# vim /usr/local/nginx/conf/nginx.conf 43 location / {44 # root html;45 # index index.html index…

测试开发面试题,助你拿高薪offer

进入金九银十&#xff0c;很多小伙伴有被动跳槽的打算&#xff0c;所以更新一些测试开发 面试题&#xff0c;希望能帮到大家。 一 请说一下HTTP 状态码 HTTP状态码大致分为5类&#xff1a; 常见的http状态码如下: 二 python中“” 和“ is ”的区别 is 和 都可以进行对象比…

CVE-2022-21663: WordPress <5.8.3 版本对象注入漏洞深入分析

引言 在网络安全领域&#xff0c;技术的研究与讨论是不断进步的动力。本文针对WordPress的一个对象注入漏洞进行分析&#xff0c;旨在分享技术细节并提醒安全的重要性。特别强调&#xff1a;本文内容仅限技术研究&#xff0c;严禁用于非法目的。 漏洞背景 继WordPress CVE-2…

解决nginx端口转发后,获取不到真实IP问题

文章目录 1&#xff0c;设置nginx端口转发1.2&#xff0c;无法获取客户端真实IP 2&#xff0c;nginx配置文件增加配置&#xff0c;保留客户端信息2.2&#xff0c;可以看到真实IP信息 1&#xff0c;设置nginx端口转发 location /AWAPI/ {proxy_pass http://172.28.43.19:9607; …

UEFI DebugLib 介绍

1.我们调试中常用Debug 打印信息&#xff0c;这些会输出到BIOS串口日志中 EFI_STATUSEFIAPIHelloWorld2(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable){EFI_STATUS Status;StatusEFI_SUCCESS;gST->ConOut->OutputString(gST->ConOut,L&q…

HomeServer平台选择,介绍常用功能

​​ 平台选择 HomeServer 的性能要求不高&#xff0c;以下是我的硬件参数&#xff0c;可供参考&#xff1a; ‍ 硬件&#xff1a; 平台&#xff1a;旧笔记本CPU&#xff1a;i5 4210u内存 8G硬盘&#xff1a;128G 固态做系统盘&#xff0c;1T1T 机械盘组 RAID1 做存储。硬…

【数据结构与算法】详解计数排序:小范围整数排序的最佳选择

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 一、引言 二、计数排序的基本原理 三、实现步骤 1. 确定数据范围 2. 初始化计数数组…

Serverless Knative冷启动与自动扩缩容研究:从原理到实践

最近一个研究生网页的提问&#xff0c;然后就有了这篇博客&#xff01; 大佬你好&#xff0c;我看到您的关于Serverless的文章于是十分冒昧的向您提问。我现在是一名在研究通过Serverless容器调度解决冷启动问题的本科生&#xff0c;导师放养&#xff0c;就让看论文但是后面的代…

ubuntu20.04.6 安装Skywalking 10.0.1

1.前置准备 1.1. **jdk17&#xff08;Skywalking10 jdk22不兼容&#xff0c;用17版本即可&#xff09;**安装&#xff1a; https://blog.csdn.net/CsethCRM/article/details/140768670 1.2. elasticsearch安装&#xff1a; https://blog.csdn.net/CsethCRM/article/details…

Python入门宝藏《看漫画学Python》,495页漫画带你弄清python知识点!简单易懂 | 附PDF全彩版

华为出品的《看漫画学Python》全彩PDF教程是一本适合Python初学者的学习资料&#xff0c;通过漫画的形式将复杂的Python技术问题简单化&#xff0c;使学习过程更加生动有趣。以下是对该教程的内容简介、本书概要及本书目录的详细解析&#xff1a; 内容简介 《看漫画学Python》…

手机三要素接口怎么对接呢?(一)

一、什么是手机三要素&#xff1f; 手机三要素又叫运营商三要素&#xff0c;运营商实名认证&#xff0c;运营商实名核验&#xff0c;手机三要素实名验证&#xff0c;手机三要素实名核验&#xff0c;每个人的称呼都不同&#xff0c;但是入参和出参是一样的。 输入姓名、身份证…

MATLAB基础:函数与函数控制语句

今天我们继续学习Matlab中函数相关知识。 API的查询和调用 help 命令是最基本的查询方法&#xff0c;可查询所有目录、指定目录、命令、函数。 我们直接点击帮助菜单即可查询所需的API函数。 lookfor 关键字用于搜索相关的命令和函数。 如&#xff0c;我们输入lookfor inpu…

矩估计与最大似然估计的通俗理解

点估计与区间估计 矩估计与最大似然估计都属于点估计&#xff0c;也就是估计出来的结果是一个具体的值。对比区间估计&#xff0c;通过样本得出的估计值是一个范围区间。例如估计馒头店每天卖出的馒头个数&#xff0c;点估计就是最终直接估计每天卖出10个&#xff0c;而区间估…

【机器学习基础】机器学习的数学基础

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈Python机器学习 ⌋ ⌋ ⌋ 机器学习是一门人工智能的分支学科&#xff0c;通过算法和模型让计算机从数据中学习&#xff0c;进行模型训练和优化&#xff0c;做出预测、分类和决策支持。Python成为机器学习的首选语言&#xff0c;…