【C++】AVL树和红黑树模拟实现

AVL树和红黑树

  • 1. 背景
  • 2. AVL树的概念
  • 3. AVL树节点的定义
  • 4. AVL树的插入
  • 5. AVL树的旋转
    • 5.1. 左单旋
    • 5.2. 右单旋
    • 5.3. 左右单旋
    • 5.4. 右左单旋
    • 5.5. 旋转总结
  • 6. AVL树的验证
  • 7. AVL树的性能
  • 8. 红黑树的概念
  • 9. 红黑树的节点的定义
  • 10. 红黑树的插入
    • 10.1. 情况一
    • 10.2.情况二
  • 11. 红黑树的验证
  • 12. 红黑树与AVL树的比较

1. 背景

  1. set、map、multiset、multimap,这几个关联式容器的共同点是:底层都是用二叉搜索树来实现的,但二叉搜索树自身有缺陷,若插入的元素有序或者接近有序,二叉搜索树就会退化为单支树,查询时相当于在顺序表中进行查询,效率低下,时间复杂度退化为O(n),因此map、set等关联式容器的底层结构对二叉搜索树进行了平衡处理,即采用平衡树来实现。

  2. 通过保证每个节点的左、右子树高度差不超过1,对树中的节点进行调整,即可降低树的高度,从而减少树的搜索长度,时间复杂度为O(longn)。

2. AVL树的概念

  1. AVL树具有的性质:a. 每个节点的左右子树都是AVL树, b. 每个节点的左右子树高度差(平衡因子)的绝对值不超过1(0、-1、1)。

  2. 平衡因子:右子树高度-左子树高度,不是必须存在的,只是一种控制方式,帮助我们更便捷的++控制树,检查是否为AVL树。

image.png

  • 如果一颗二叉搜索树高度是平衡的,那么它就是AVL树,如果它有n个节点,它的高度就是longn,则搜索的时间复杂度为O(longn)。

  • 无法做到平衡因子的值恒为0,因为二叉树插入节点是一个一个插入进去的,对于2个或者4个节点的树是无法做到左右子树高度差等于0,最优的高度差就是1。

3. AVL树节点的定义

template<class K, class V>   //AVL树的节点(KV模型)
struct AVLTreeNode{  typedef AVLTreeNode<K, V> Node;  Node* _left;      //该节点的左孩子Node* _right;    //该节点的右孩子Node* _parent;  //该节点的父亲,便于向上更新int _bf;       //该节点的平衡因子pair<K, V> _kv;  AVLTreeNode(const pair<K, V>& kv)  //构造函数:_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0)  { }
};

4. AVL树的插入

方法:先按二叉搜索树的方式插入新节点,在更新平衡因子。

AVL的插入.png

void RotateL(Node* parent)  //右右—左单旋
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pphead = parent->_parent;parent->_right = subRL;   //b变成父节点的右孩子if (subRL)   //当前节点的左孩子可能存在,也可能不存在  h=0,b=0subRL->_parent = parent;   //更新当前节点右孩子的父亲subR->_left = parent;   //父节点变为当前节点的左孩纸parent->_parent = subR;   //更新父节点的父亲//更新当前节点的父亲,父节点可能为根节点,也可能为根节点if (parent == _root)  //根节点 {_root = subR;subR->_parent = nullptr; //更新当前节点的父亲}else{if (pphead->_left == parent)   //子树pphead->_left = subR;elsepphead->_right = subR;subR->_parent = pphead; //更新当前节点的父亲}subR->_bf = 0;  //更新平衡因子parent->_bf = 0;
}void RotateR(Node* parent)  //左左—右单旋
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pphead = parent->_parent;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (pphead->_left == parent)pphead->_left = subL;elsepphead->_right = subL;subL->_parent = pphead;}subL->_bf = 0;  //更新平衡因子parent->_bf = 0;
}void RotateRL(Node* parent)  //右左—先右旋再左旋
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* bf = subRL->_bf;   //因为旋转后会改变bf,需要提前记录RotateR(subR); RotateL(parent);subRL->_bf = 0;   //更新平衡因子if (bf == 0) //情况一:当h = 0,subRL为新增节点{subR->_bf = 0;parent->_bf = 0;}else if (bf == -1) //情况二:当h > 0,b插入,b的高度变为h,引发旋转{subR->_bf = 1;parent->_bf = 0;}else if(bf == 1) //情况三:当h > 0,c插入,c的高度变为h,引发旋转{subR->_bf = 0;parent->_bf = -1;}else  //平衡因子异常{assert(false);}
}void RotateLR(Node* parent)  //左右—先左旋再右旋
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);subLR->_bf = 0; //更新平衡因子if (bf == 0){subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;parent->_bf = 0;}else if(bf == -1){subL->bf = 0;parent->_bf = 1;}else{assert(false);}
}bool insert(const pair<K, V>& kv)  //插入
{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 == nullptr) //空树{_root = cur;return true;}if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;  //记录当前节点的父亲while (parent)  //更新结束: 情况1,更新到根节点{if (parent->_left == cur) //再更新平衡因子parent->_bf--;elseparent->_bf++;if (parent->_bf == 0) //更新结束: 情况2,p的高度不变,不会影响上层的bfbreak;else if (parent->_bf == 1 || parent->_bf == -1) //更新继续:p的高度改变,会影响上层的bf,但未违反平衡树性质{cur = cur->_parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2) //违反了平衡树的规则,做处理-》旋转 ->更新结束{   //注意:以下4种旋转,只要完成了某一种,都会更新结束,需要breakif (parent->_bf == 2 && cur->_bf == 1) //插入到较高右子树的右边{RotateL(parent); //右右—左单旋break;  }else if (parent->_bf == -2 && cur->_bf == -1) //插入到较高左子树的左边{RotateR(parent);  //左左—右单旋break;}else if (parent->_bf == 2 && cur->_bf == -1)  //插入到较高右子树的左边{ RotateRL(parent);  //右左—先右旋再左旋break;}else if (parent->_bf == -2 && cur->_bf == 1)  //插入到较高左子树的右边{ RotateLR(parent);  //左右—先左旋再右旋break;}else   //在插入前就不是AVL树    必须保证再插入前是一颗AVL树 {assert(false);break;}}}
}

5. AVL树的旋转

5.1. 左单旋

void RotateL(Node* parent)  //右右—左单旋
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pphead = parent->_parent; parent->_right = subRL;   //b变成父节点的右孩子if (subRL)   //当前节点的左孩子可能存在,也可能不存在  h=0,b=0subRL->_parent = parent;   //更新当前节点右孩子的父亲subR->_left = parent;   //父节点变为当前节点的左孩纸parent->_parent = subR;   //更新父节点的父亲//更新当前节点的父亲,父节点可能为根节点,也可能为根节点if (parent == _root)  //根节点 {_root = subR;subR->_parent = nullptr; //更新当前节点的父亲}else{if (pphead->_left == parent)   //子树pphead->_left = subR;elsepphead->_right = subR;subR->_parent = pphead; //更新当前节点的父亲}subL->_bf = 0;  //更新平衡因子parent->_bf = 0;
}

左单旋.png

5.2. 右单旋

void RotateR(Node* parent)  //左左—右单旋
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pphead = parent->_parent;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (pphead->_left == parent)pphead->_left = subL;elsepphead->_right = subL;subL->_parent = pphead;}subL->_bf = 0;  //更新平衡因子parent->_bf = 0;
}

image.png

5.3. 左右单旋

void RotateLR(Node* parent)  //左右—先左旋再右旋
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);subLR->_bf = 0; //更新平衡因子if (bf == 0){subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;parent->_bf = 0;}else if(bf == -1){subL->bf = 0;parent->_bf = 1;}else{assert(false);}
}

左右旋.png

5.4. 右左单旋

void RotateRL(Node* parent)  //右左—先右旋再左旋
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* bf = subRL->_bf;   //因为旋转后会改变bf,需要提前记录RotateR(subR); RotateL(parent);subRL->_bf = 0;  //更新平衡因子if (bf == 0) //情况一:当h = 0,subRL为新增节点{subR->_bf = 0;parent->_bf = 0;}else if (bf == -1) //情况二:当h > 0,b插入,b的高度变为h,引发旋转{subR->_bf = 1;parent->_bf = 0;}else if(bf == 1) //情况三:当h > 0,c插入,c的高度变为h,引发旋转{subR->_bf = 0;parent->_bf = -1;}else  //平衡因子异常{assert(false);}
}

AVL右左双旋.png

5.5. 旋转总结

image.png

6. AVL树的验证

方法:先验证其为二叉搜索树(中序遍历得到有序序列),再验证其为平衡树(左右子树高度差的绝对值不超过1、平衡因子正常)。

int _Height(Node* root) //后序遍历
{if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}bool _Isbalance(Node* root) //前序遍历  验证其为平衡树
{if (root == nullptr)return true;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);if (abs(rightHeight - leftHeight) >= 2){cout << "不平衡" << endl; return false;}if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << ":平衡因子异常" << endl;return false;}return _Isbalance(root->_left) && _Isbalance(root->_right);
}void _Inorder(Node* root) //中序遍历 验证其为二叉搜索树
{if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << "[" << root->_bf << "]" << endl;_Inorder(root->_right);
}
  • 不能采用层序遍历、看bf的值,原因:对于完全二叉树、满二叉树通过层序遍历可以推出树的正确结构,AVL树不一定为完全、满二叉树。在旋转时,bf可能更新异常,此时看bf的值,会造错误构。

  • 验证其为平衡二叉树,采用前序遍历,缺点:先求高度,再判平衡,递归式判子树平衡,导致重复求子树的高度,效率低——》解决方法:将前序改成后序,并将高度作为引用参数(引用,各个栈帧相互影响),边判平衡边求高度。

bool _Isbalance(Node* root, int& height) //后序遍历+引用参数
{if (root == nullptr) //空树也是AVL树{height = 0;return true;}int leftHeight = 0, rightHeight = 0;if(!_Isbalance(root->_left, leftHeight) || !_Isbalance(root->_right, rightHeight)) return false;  //只要左右子树有一颗不是平衡树,该树就不是平衡树height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;return true;
}
int main()
{int a[] = { 8, 5, 2, 6, 3, 7, 4, 1 };AVLTree<int, int> t;for (auto& e : a){t.insert(make_pair(e, e));}t.Inorder();cout << t.Isbalance() << endl;return 0;
}

image.png
image.png

7. AVL树的性能

  • AVL是一颗高度平衡的二叉搜索树,查询时效率高,时间复杂度为O(longn),但如果要对AVL树进行结构修改操作,性能非常低,如:插入时,为了维持平衡需要进行多次旋转,删除可能旋转要持续到根节点位置处。

  • 如果需要一种查询高效且数据有序的数据结构,数据的个数为静态的(不会插入元素,结构不会发生改变),可以考虑使用AVL树。如果一个结构需要经常的修改,AVL树不太适用。

8. 红黑树的概念

  1. 红黑树的概念:红黑树,是一种二叉搜索树,在每个节点上增加一个存储位来表示节点的颜色,red或者black,通过对每条路径上节点着色方式的控制,红黑树保证了最长路径不超过最短路径的二倍,其他路径的长度位于最短路径长度和最长路径长度之间,即:每条路径不会比其他路径长出两倍,因此它接近于平衡。

  2. 红黑树的性质
    a.每个节点的颜色不是红色就是黑色 ;
    b.根节点的颜色一定是黑色 ;
    c.如果有一个节点的颜色是红色,则它的孩子节点的颜色一定是黑色,即:没有连续的红色节点;
    d.每条路径都具有相同数量的黑色节点。
    e.每个叶子节点都是黑色的,叶子节点指的是空节点,又名为NIL节点,是为了避免路径(根节点到空节点)数错的误区。

  3. 问:只要满足以上的性质,为什么红黑树就能保证最长路径中节点个数不超过最短路径中节点个数的两倍?
    答:因为在极端情况下,最短路径:全黑,最长路径:一黑一红。

image.png

9. 红黑树的节点的定义

enum Color {  //枚举,一一列举出事物具有的所有可能Red,  //枚举常量,给枚举变量进行赋值Black,
};template<class K, class V>//红黑树的节点(KV模型)
struct RBTreeNode {typedef RBTreeNode<K, V> Node;Node* _left;      //该节点的左孩子Node* _right;    //该节点的右孩子Node* _parent;  //该节点的父亲,便于向上更新pair<K, V> _kv;   Color _col;RBTreeNode(const pair<K, V>& kv, Color col = Red)  //构造函数:_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(col)   //默认新插入节点的颜色为红色{ }
};

10. 红黑树的插入

方法:先按照二叉搜素树的方式插入,在更新颜色。

红黑树的插入.png

void RotateL(Node* parent)  //右右—左单旋
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pphead = parent->_parent;parent->_right = subRL;  if (subRL)   subRL->_parent = parent;  subR->_left = parent;  parent->_parent = subR;   if (parent == _root) {_root = subR;subR->_parent = nullptr; }else{if (pphead->_left == parent) pphead->_left = subR;elsepphead->_right = subR;subR->_parent = pphead;}
}void RotateR(Node* parent)  //左左—右单旋
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pphead = parent->_parent;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (pphead->_left == parent)pphead->_left = subL;elsepphead->_right = subL;subL->_parent = pphead;}
}void RotateRL(Node* parent)  //右左—先右旋再左旋
{Node* subR = parent->_right;Node* subRL = subR->_left;RotateR(subR);RotateL(parent);
}void RotateLR(Node* parent)  //左右—先左旋再右旋
{Node* subL = parent->_left;Node* subLR = subL->_right;RotateL(subL);RotateR(parent);
}bool insert(const pair<K, V>& kv)  //插入
{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 == nullptr) //空树{_root = cur;_root->_col = Black;  //跟节点为黑return true;}if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;  //记录当前节点的父亲//更新颜色//插入结束:1.插入节点的父亲是黑色,因为插入前该树就为红黑树 2.情况一处理完后,cur为根节点,且为黑色while (parent && parent->_col == Red) { //爷爷一定存在,因为c为红,p为红,所以p一定不是根节点,且一定有父节点Node* grandfather = parent->_parent; if (parent == grandfather->_left)  //旋转需要确定方向{Node* uncle = grandfather->_right;  if (uncle && uncle->_col == Red) //情况一:叔叔存在且为红->无方向(p、u为g的任意边,c为p的任一边){  //cur可能为新增节点,也可能一开始为黑色,cur的子树(下一层为红,下一层为新插入节点)在调整过程中将cur由黑变为红parent->_col = uncle->_col = Black; //p、u变为黑,g变为红grandfather->_col = Red;//g可能为根节点(更新结束),也可能为子树(继续向上更新)cur = grandfather;   parent = cur->_parent; }else  //情况二:叔叔不存在 或者 叔叔存在且为黑{  //叔叔不存在,cur为新增节点 或 cur原来为黑,经子树调整由黑变红if (parent->_left == cur)  //左左——右单旋{RotateR(grandfather);parent->_col = Black; //p变为黑,g变为红grandfather->_col = Red;}else    //左右——左右单旋 {RotateL(parent); RotateR(grandfather);cur->_col = Black;  //c变黑,g变红grandfather->_col = Red;}break;  //更新结束:3.旋转+颜色处理后就是红黑树了}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == Red){parent->_col = uncle->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur)  //右右——左单旋{RotateL(grandfather);parent->_col = Black;grandfather->_col = Red;}else   //右左——右左单旋{RotateR(parent);RotateL(grandfather);cur->_col = Black;grandfather->_col = Red;}break;  }}}_root->_col = Black;  //g为根,颜色变为黑,更新结束return true;  //情况一,插入节点的父亲为黑,插入结束
}

10.1. 情况一

  1. 叔叔存在且为红色——将p、u变为黑色,g变为红色,在把g给c。若c为根节点,将c变为黑色,插入结束(break)。若c不为根节点,继续向上更新颜色,直到c为根 或则 c的父亲为黑色。

  2. 无方向——p、u是g的左右都可以,c是p的左右都可以,处理方式都是一样的。

if (uncle && uncle->_col == Red) //情况一:叔叔存在且为红->无方向(p、u为g的任意边,c为p的任一边)
{  //cur可能为新增节点,也可能一开始为黑色,cur的子树(下一层为红,下一层为新插入节点)在调整过程中将cur由黑变为红parent->_col = uncle->_col = Black; //p、u变为黑,g变为红grandfather->_col = Red;//g可能为根节点(更新结束),也可能为子树(继续向上更新)cur = grandfather;   parent = cur->_parent; 
}

红黑树插入情况一.png

10.2.情况二

  1. 叔叔不存在 或者 叔叔存在且为黑色——旋转(有方向,4种) + 颜色处理。

  2. a. p为g的左孩子,c为p的左孩子,左左,右单旋。b. p为g的右孩子,c为p的右孩子,右右,左单旋,将p变为黑色,将g变为红色—》将p变为黑色,将g变为红色。c. p为g的左孩子,c为p的右孩子,左右旋。d. p为g的右孩子,c为p的左孩子,右左旋——》将c变为黑色,g变为红色。

  3. 若叔叔不存在,c只可以是新增节点,因为若c不是新增节点,该树最短路径长度为1,最长路径长度为4,在插入前就已经违反了红黑树性质3。若叔叔存在且为红,c只能是原来为黑色,经子树调整由黑色变为红色。

  4. 情况二在插入前最短路径长度:最短路径长度=1:2,插入后,违反了红黑树性质4,所以需要做旋转处理。

if (parent->_left == cur)  //左左——右单旋
{RotateR(grandfather);parent->_col = Black; //p变为黑,g变为红grandfather->_col = Red;
}
else    //左右——左右单旋 
{RotateL(parent); RotateR(grandfather);cur->_col = Black;  //c变黑,g变红grandfather->_col = Red;
}
break;  //更新结束:3.旋转+颜色处理后就是红黑树了

红黑树插入情况二.png

11. 红黑树的验证

方法:先验证其为二叉搜索树(中序遍历得到有序序列),再验证其是否满足红黑树的性质。

void Inorder() //验证其为二叉搜索树,中序遍历
{_Inorder(_root);
}bool Balance() //验证其是否满足红黑树的性质
{if (_root && _root->_col == Red) //红黑树性质2,跟节点为黑色return false;int refvalue = 0;  //红黑树性质4,每条路径具有相同数量的黑节点Node* cur = _root; while (cur){if (cur->_col == Black)refvalue++;   //找参考值cur = cur->_left;}_Balance(_root, 0, refvalue);
}//前序遍历,将每条路径黑色节点的数量于参考值比较,验证性质4
bool _Balance(Node* root, int blackcount, int& refvalue)
{if (root == nullptr){if (blackcount != refvalue)return false;return true;}if (root->_col == Red && root->_parent->_col == Red)return false;   //验证红黑性质3,没有连续的红节点,孩子找爹if (root->_col == Black) //blackcount没有用引用,因为下层不能影响上次,blackcount表示的是当前节点到根节点路径上黑色节点的数量blackcount++;return _Balance(root->_left, blackcount, refvalue) && _Balance(root->_right, blackcount, refvalue); //放在后面,从前往后
}void _Inorder(Node* root) //中序遍历
{if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first <<  endl;_Inorder(root->_right);
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"RBTree.h"void test1()
{int a[] = { 5, 4, 8, 6, 2, 9, 1, 3, 7 };RBTree<int, int> rb;for (auto& e : a){rb.insert(make_pair(e, e));}rb.Inorder();cout << rb.Balance() << endl;
}int main()
{test1();return 0;
}

image.png
image.png

12. 红黑树与AVL树的比较

  • 红黑树和AVL树都是高效的平衡二叉搜索树,增删查改的时间复杂度都为O(longn),但红黑树不要求绝对平衡,只需保证最长路径的长度不超过最短路径长度的两倍,相对而言,降低了插入、删除时旋转的次数,所以在经常进行增删查改的结构中红黑树的性能更优,在实际应用红黑树的更多。

  • AVL树的高度接近于longn,红黑树的高度接近于2longn,所以搜索效率AVL树略优于红黑树,但是几乎可以忽略不记,因为n足够大时,longn就足够小,两者的差距微乎其微。当插入同样得数据,AVL树的高度更低,通过多次旋转达到的。

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

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

相关文章

Transformer详解(2)-位置编码

位置编码公式 偶数位置用sin,奇数位置用cos. d_model 表示token的维度&#xff1b;pos表示token在序列中的位置&#xff1b;i表示每个token编码的第i个位置&#xff0c;属于[0,d_model)。 torch实现 import math import torch from torch import nn from torch.autograd im…

pycharm配置python开发环境—miniconda+black+gitlab

下载miniconda管理python开发环境 miniconda下载地址&#xff1a;https://docs.anaconda.com/free/miniconda/ miniconda最新版本的python版本是python3.12.2&#xff0c;下载这个miniconda最新版本后&#xff0c;会导致执行conda create -n py31013 python3.10.13指令配置py…

使用HTTP长连接减少文件描述符和端口占用

在当今互联网技术飞速发展的背景下&#xff0c;高并发处理能力已经成为衡量服务器性能的一个重要标准。面对高并发场景&#xff0c;服务器需要同时应对大量的请求&#xff0c;这就带来了一个棘手的问题&#xff1a;资源有限。具体来说&#xff0c;文件描述符和端口号&#xff0…

数据结构---优先级队列(堆)

博主主页: 码农派大星. 数据结构专栏:Java数据结构 关注博主带你了解更多数据结构知识 1. 优先级队列 1.1 概念 前面介绍过队列&#xff0c;队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c;一般出队 列时&am…

微软Copilot+ PC:Phi-Silica

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调重新阅读。而最新科技&#xff08;Mamba&#xff0c;xLSTM,KAN&#xff09;则提供了大模…

C++入门:从C语言到C++的过渡(2)

目录 1.缺省参数 1.1缺省参数的概念 1.2缺省参数的分类及使用 1.3注意点 2.函数重载 2.1函数重载的定义 2.2函数重载的情况分类 2.3注意 2.4函数名修饰规则 3.引用 3.1引用的概念 3.2注意事项 3.3常引用 4.4引用的使用场景 4.4.1作为函数的参数 4.4.2做函数返回…

【学习笔记】Windows GDI绘图目录

题外话 不知几时开始&#xff0c;觉得学习过程中将内容记录下来&#xff0c;有助于加强记忆&#xff0c;还方便后续查找&#xff0c;顺便帮助有需要的人&#xff0c;更有来自您阅读、点赞、收藏和评论时给我带来的动力与兴奋。 目录 【学习笔记】Windows GDI绘图(一)图形概述…

B站大数据分享视频创作300天100+原创内容4000+粉

以今年五一作为一个里程碑参考点&#xff0c;给明年一个可以比较的数据。 我正经发力创作是2023.06.17 (前面几个视频是试水)&#xff0c;300天不到一年时间 创作了100原创数据相关视频&#xff0c;创作频率应该很高了&#xff0c;收获了下面几个数字&#xff0c;审视自身&…

如何建设高效的外贸自建站?

建设高效的外贸自建站&#xff0c;首先要从明确目标和受众开始。了解你的目标市场和潜在客户是关键&#xff0c;这样你可以有针对性地进行设计和内容创作。站点的设计应该简洁明了&#xff0c;导航要方便&#xff0c;确保访客户能够快速找到所需的信息。 而内容是网站的核心。…

Java基础的语法---String

Java的String类是不可变的&#xff0c;意味着一旦创建&#xff0c;其值就不能被改变。String类提供了丰富的API来操作字符串。 以下是一些常用的方法&#xff1a; 构造方法&#xff1a; 有以下几种常见的&#xff1a; public class stringlearn {public static void main(S…

ATmega328P加硬件看门狗MAX824L看门狗

void Reversewdt(){ //硬件喂狗&#xff0c;11PIN接MAX824L芯片WDIif (digitalRead(11) HIGH) {digitalWrite(11, LOW); //低电平} else {digitalWrite(11, HIGH); //高电平 }loop增加喂狗调用 void loop() { …… Reversewdt();//喂狗 }

从0到1!得物如何打造通用大模型训练和推理平台

1.背景 近期&#xff0c;GPT大模型的发布给自然语言处理&#xff08;NLP&#xff09;领域带来了令人震撼的体验。随着这一事件的发生&#xff0c;一系列开源大模型也迅速崛起。依据一些评估机构的评估&#xff0c;这些开源模型大模型的表现也相当不错。一些大模型的评测情况可…

佩戴安全头盔监测识别摄像机

佩戴安全头盔是重要的安全措施&#xff0c;尤其在工地、建筑工程和工业生产等领域&#xff0c;安全头盔的佩戴对于工人的生命安全至关重要。为了更好地管理和监控佩戴安全头盔的情况&#xff0c;监测识别摄像机成为了一项重要的工具。监测识别摄像机可以通过智能技术监测并记录…

JavaScript-数组的增删改查

数组的操作一共有四种&#xff1a; 查询数组数据修改数组中元素的值数组添加新的数据删除数组中的元素 数组的初始化 有些编程语言的数组初始化是用{}包着的&#xff0c;而JS的数组初始化用[] let num[2,6,1,77,52,25,7]; 数组的查询 想要具体查询数组中的某个元素 可以用数…

项目9-网页聊天室7(消息传输模块之解决之前存在的问题:获取最后一条消息)

1.服务器中转的原因 IPV4不够用 &#xff08;1&#xff09;使用服务器中转,最大原因, 就是 NAT 背景下,两个内网的设备无法直接进行通信(不在同一个局域网内) &#xff08;2&#xff09;另外一个原因,通过服务器中转,是更容易在服务器这里记录历史消息随时方便咱们来查询历史记…

C语言实现Hash Map(2):Map代码实现详解

在上一节C语言实现Hash Map(1)&#xff1a;Map基础知识入门中&#xff0c;我们介绍了Map的基础概念和在C中的用法。但我写这两篇文章的目的是&#xff0c;能够在C语言中实现这样的一个数据结构&#xff0c;毕竟有时我们的项目中可能会用到Map&#xff0c;但是C语言库中并没有提…

C语言学习笔记之指针(一)

目录 什么是指针&#xff1f; 指针和指针类型 指针的类型 指针类型的意义 指针-整数 指针的解引用 指针 - 指针 指针的关系运算 野指针 什么是野指针&#xff1f; 野指针的成因 如何规避野指针&#xff1f; 二级指针 什么是指针&#xff1f; 在介绍指针之前&#…

第十五届“北斗杯”全国青少年空天科技体验与创新大赛安徽赛区阜阳市解读会议

5月19日&#xff0c;第十五届“北斗杯”全国青少年空天科技体验与创新大赛安徽赛区阜阳解读活动在阜阳市图书馆隆重举行。共青团阜阳市委员会学少部副部长丁晓龙、阜阳市师范大学物理系副主任黄银生教授、安徽科技报阜阳站站长李伟、市人工智能学会秘书长郭广泽、“北斗杯”安徽…

【开发 | 环境配置】解决 VSCode 编写 eBPF 程序找不到头文件

问题描述&#xff1a; 在使用 vscode 编写 eBPF 程序时&#xff0c;如果不做一些头文件定位的操作&#xff0c;默认情况下头文件总是带有“红色下划线”&#xff0c;并且大部分的变量不会有提示与补全。 在编写代码文件较小时&#xff08;或者功能需求小时&#xff09;并不会…

开放式耳机什么牌子好用?五大高分力作安利,不容错过!

​开放式耳机如今备受瞩目&#xff0c;其独特的不入耳设计不仅避免了长时间佩戴对耳道的压力&#xff0c;还维护了耳朵的卫生健康&#xff0c;特别受运动爱好者和耳机发烧友青睐。然而&#xff0c;市场上的开放式耳机品质良莠不齐&#xff0c;让许多消费者在选择时陷入困惑。作…