当前位置: 首页 > news >正文

二叉搜索树:AVL平衡

文章目录

  • 一、 二叉搜索树
    • 1.1 概念
    • 1.2 操作
    • 1.3 代码实现
  • 二、二叉搜索树的应用
    • K模型和KV模型
  • 三、二叉搜索树的性能分析
  • 四、AVL树
    • 4.1 AVL树的概念
    • 4.2 AVL树的实现原理
    • 4.3 旋转
    • 4.4 AVL树最终代码


一、 二叉搜索树

1.1 概念

二叉搜索树Binary Search Tree,BST )是一种特殊的二叉树,它可以是空树,也可以是满足以下性质的一颗二叉树:

  • 若左子树不为空,左子树中所有节点的键值都小于根节点的值。
  • 若右子树不为空,右子树中所有节点的键值都大于根节点的值。
  • 左右子树也分别为二叉搜索树。

因此,二叉搜索树的中序遍历结果是一个有序序列。这个特性使得二叉搜索树在搜索、插入和删除操作时具有高效性能。

1

📝二叉搜索树的结构示意图


1.2 操作

⭕ 对如下二叉搜索树进行操作

在这里插入图片描述

  1. 查询

🔎假设要查询key值是否存在于二叉树中

  • 从根节点开始,key比当前节点的键值大,则往右继续找;key比当前节点的键值小,则往左继续找。
  • 若当前节点为空时还没找到,则key值不存在。

在这里插入图片描述

  1. 插入

1️⃣若树为空,则直接新增节点,作为该树的根节点
2️⃣若树不为空,则按二叉搜索树查询规则找到插入位置,再建立与父节点的链接关系。

在这里插入图片描述

  1. 删除

二叉搜索树的删除某个节点后,要想继续保持二叉搜索树的特性,需要进行一些调整。这里分三种情况。

假设将要删除node节点

1️⃣ 若node没有子树,即node为叶子节点,那么直接删除即可。

在这里插入图片描述

2️⃣ 若node左右子树有一边为空一边非空,则需“托孤”,即把非空一边的子树托付给node父节点。

在这里插入图片描述

3️⃣ 若node左右子树都存在,则需在左子树中找到最大节点(或在右子树中找到最小节点)来替代node,然后在左子树(或右子树)中删除node。

观察二叉搜索树的中序遍历序列,可见进行上述操作后,中序遍历序列依然有序,二叉搜索树保持其特性。

在这里插入图片描述
在这里插入图片描述

💡替换node后,在左子树(或右子树)中删除node,这样做还有一个好处:

因为node是与左子树中最大节点(或右子树中最小节点)替换后,所以替换后的node一定没有右子树(或左子树),因此在这种情况下删除node,必然是删除节点的1️⃣或2️⃣情况,避免了重复3️⃣情况删除而进入死循环。


1.3 代码实现

#include <iostream>
#include <algorithm>
using namespace std;// 二叉搜索树的节点
template <class K>
struct BSTreeNode
{typedef BSTreeNode<K> Self;Self* _pleft;Self* _pright;K _key;BSTreeNode(const K& key):_key(key),_pleft(nullptr),_pright(nullptr){}
};// 二叉搜索树
template <class K>
class BSTree
{typedef BSTreeNode<K> Node;typedef BSTreeNode<K>* pNode;public:// constructorBSTree():_root(nullptr){}// destructor~BSTree(){_destroy(_root);}// 中序遍历void Inorder(){_inorder(_root);cout << endl;}// 插入bool Insert(const K& key){// 空树if(_root == nullptr){_root = new Node(key);return true;}// 不为空树,要先找到插入位置else{pNode cur = _root;pNode parent = nullptr;while (cur){if (key > cur->_key){parent = cur;cur = cur->_pright;}else if (key < cur->_key){parent = cur;cur = cur->_pleft;}else{return false;}}cur = new Node(key);if (cur->_key > parent->_key)parent->_pright = cur;elseparent->_pleft = cur;return true;}}// 删除void Erase(const K& key){_erase(_root, key);}private:pNode _root;void _inorder(pNode root){if (root == nullptr)return;_inorder(root->_pleft);cout << root->_key << " ";_inorder(root->_pright);}void _destroy(pNode root){if (root == nullptr)return;_destroy(root->_pleft);_destroy(root->_pright);delete root;}bool _erase(pNode& root, const K& key){// 先找到key值的节点pNode cur = root;pNode parent = nullptr;while (cur){if (key > cur->_key){parent = cur;cur = cur->_pright;}else if (key < cur->_key){parent = cur;cur = cur->_pleft;}else // 相等,找到了break;}if (cur == nullptr) // 查无key值节点return false;// 1. cur左右至少有一个空(1️⃣、2️⃣情况)if (cur->_pleft == nullptr || cur->_pright == nullptr){pNode child = cur->_pleft;if (child == nullptr)child = cur->_pright;// cur为根if (cur == root){root = child;}// cur不为根else{if (cur == parent->_pleft){parent->_pleft = child;}else{parent->_pright = child;}}delete cur;cur = nullptr;}// 2. cur左右都非空(3️⃣情况)else{//(1)找到右边最小(也可以是左边最大,通常小的离根较近,我们选用右边最小)的节点代替curpNode minRight = cur->_pright;while (minRight->_pleft){minRight = minRight->_pleft;}swap(cur->_key, minRight->_key);//(2)转换为在cur的右子树删除minRight节点_erase(cur->_pright, minRight->_key); // 此时一定是1️⃣或2️⃣情况}return true;}
};

💡 _erase函数root参数设为引用的妙处在cur为根时,体现为以下两种情况相同处理

在这里插入图片描述

1. _erase(_root, key);
在这里插入图片描述
2. _erase(cur->_pright(或cur->_pleft), minRight->_key);
在这里插入图片描述


二、二叉搜索树的应用

K模型和KV模型

  1. K模型K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到
    的值。

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下: 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树,在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

  1. KV模型每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方
    式在现实生活中非常常见

比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英 文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

在这里插入图片描述

💬KV模型的代码实现:

// KV型
template <class K, class V>
struct BSTreeNode
{typedef BSTreeNode<K, V> Self;Self* _pleft;Self* _pright;K _key;V _val;BSTreeNode(const K& key, const V& val): _key(key), _val(val), _pleft(nullptr), _pright(nullptr){}
};template <class K, class V>
class BSTree
{typedef BSTreeNode<K, V> Node;typedef BSTreeNode<K, V>* pNode;public://constructorBSTree():_root(nullptr){}//destructor~BSTree(){_destroy(_root);}void Inorder(){_inorder(_root);cout << endl;}bool Insert(const K& key,const V& val){// 空树if (_root == nullptr){_root = new Node(key, val);return true;}// 不为空树,要先找到插入位置else{pNode cur = _root;pNode parent = nullptr;while (cur){if (key > cur->_key){parent = cur;cur = cur->_pright;}else if (key < cur->_key){parent = cur;cur = cur->_pleft;}else{return false;}}cur = new Node(key, val);if (cur->_key > parent->_key)parent->_pright = cur;elseparent->_pleft = cur;return true;}}void Erase(const K& key){_erase(_root, key);}pNode Find(const K& key){pNode cur = _root;while (cur){if (key > cur->_key){cur = cur->_pright;}else if (key < cur->_key){cur = cur->_pleft;}else{return cur;}}return nullptr;}
private:pNode _root;void _inorder(pNode root){if (root == nullptr)return;_inorder(root->_pleft);cout << root->_key << ":" << root->_val << endl;_inorder(root->_pright);}void _destroy(pNode root){if (root == nullptr)return;_destroy(root->_pleft);_destroy(root->_pright);delete root;}bool _erase(pNode& root, const K& key){// 先找到key值的节点pNode cur = root;pNode parent = nullptr;while (cur){if (key > cur->_key){parent = cur;cur = cur->_pright;}else if (key < cur->_key){parent = cur;cur = cur->_pleft;}else // 相等,找到了break; }if (cur == nullptr) // 查无key值节点return false;// 1. cur左右至少有一个空if (cur->_pleft == nullptr || cur->_pright == nullptr){pNode child = cur->_pleft;if (child == nullptr)child = cur->_pright;// cur为根if (cur == root){root = child;}// cur不为根else{// cur左右都为空if (child == nullptr){if (parent->_pleft->_key == cur->_key){parent->_pleft = nullptr;}else{parent->_pright = nullptr;}}// cur左右只有一个为空,不为空的为childelse{if (child->_key < parent->_key){parent->_pleft = child;}else{parent->_pright = child;}}if (cur == parent->_pleft){parent->_pleft = child;}else{parent->_pright = child;}}delete cur;cur = nullptr;}//2. cur左右都非空else{//找到右边最小的节点代替curpNode minRight = cur->_pright;while (minRight->_pleft){minRight = minRight->_pleft;}swap(cur->_key, minRight->_key);//转换为在cur的右子树删除minRight节点_erase(cur->_pright, minRight->_key);}return true;}
};

三、二叉搜索树的性能分析

💭 二叉搜索树的插入、删除等操作,时间大部分都花费在查找节点上了。因此分析二叉搜索树的性能,主要看查找的性能。

假设二叉树有N个节点

在这里插入图片描述

如图是最优情况下的查找,该二叉搜索树接近完全二叉树,此时查找节点的时间复杂度是O(logN)

在这里插入图片描述
*但也有上图所示的极端情况,有序插入节点后,二叉搜索树退化为单支,这种情况是最差情况,查找节点的时间复杂度为O(N)

💭 二叉搜索树退化为接近单支树时,其效率和性能就失去了。为了解决这个问题,使二叉搜索树始终保持最优情况,我们可以将二叉搜索树改造为AVL树和红黑树。下文分析AVL树。


四、AVL树

4.1 AVL树的概念

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

能满足这种特性的树称为AVL树

一颗AVL树可以是一棵空树,也可以是有以下性质的一棵二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度差(简称平衡因子)的绝对值不超过1

在这里插入图片描述

AVL树是一棵高度平衡的树。一棵n个节点的AVL树的高度为O(logn),查找的时间复杂度为O(logn)

定义

// AVL树的节点
template <class T>
struct AVLTreeNode
{AVLTreeNode(const T& val):_val(val),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}AVLTreeNode* _left; // 左指针AVLTreeNode* _right; // 右指针AVLTreeNode* _parent; // 双亲指针T _val;int _bf;// 平衡因子
};
// AVL树
template <class T>
class AVLTree
{typedef AVLTreeNode<T> node;typedef AVLTreeNode<T>* ptr;public:AVLTree():_root(nullptr){}bool insert(const T& val);private:ptr _root;
};

4.2 AVL树的实现原理

💭AVL树的原理主要体现在插入时要通过对节点的调节,以保证每个节点的左右子树高度差绝对值不超过1(即平衡因子不超过1)。插入后,分为以下三种情况。

默认平衡因子 = 右子树高度 - 左子树高度

  1. 插入后,父节点的平衡因子变为0

在这里插入图片描述

  1. 插入后,父节点的平衡因子变为1或-1

在这里插入图片描述

  1. 插入后,父节点的平衡因子变为2或-2

在这里插入图片描述

那么这里的旋转到底是怎么一回事?见后文详细分析。

📃 先搭建AVL树insert插入函数定义的大致框架

bool insert(const T& val){// 先按二叉搜索树规则,找到插入位置ptr cur = _root;ptr parent = nullptr;while (cur){if (val < cur->_val){parent = cur;cur = cur->_left;}else if (val > cur->_val){parent = cur;cur = cur->_right;}else{// 相同元素不能插入return false;}}// 创建新节点并插入cur = new node(val);// 若根为空,直接把cur作根if (_root == nullptr){_root = cur;return true;}else{if (cur->_val < parent->_val){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}}// 更新平衡因子while (parent){if (cur == parent->_left){(parent->_bf)--;}else{(parent->_bf)++;}// 1.parent的_bf更新后为0if (parent->_bf == 0){// 插入成功break;}// 2.parent的_bf更新后为1或-1,此时需要继续向上更新平衡因子else if (parent->_bf == 1 || parent->_bf == -1){//持续往上更新cur = parent;parent = parent->_parent;}// 3.parent的_bf更新后为2或-2,此时需要旋转,旋转后以parent为根的子树为平衡二叉树,不需要在继续向上更新平衡因子else if (parent->_bf == 2 || parent->_bf == -2){// 旋转...break;}}return true;}

4.3 旋转

🔎如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。这种调整称之为旋转。根据节点插入位置的不同,AVL树的旋转分为四种

  1. 左左 —— 右单旋

在这里插入图片描述
为什么能这样旋转?

  • b在20的左子树,肯定比20小,故能作20的左子树
  • 20和b都人于10,故以20为根的子树能作10的右子树

💬 代码实现

在这里插入图片描述

	// 左左--右单旋void RotateR(ptr pParent){ptr subL = pParent->_left;ptr subLR = subL->_right;ptr ppParent = pParent->_parent;//建立新的链接关系//1.pParent(父)和subLR(子)pParent->_left = subLR;if (subLR)subLR->_parent = pParent;//2.subL(父)和pParent(子)subL->_right = pParent;pParent->_parent = subL;//3.ppParent(父)和subL(子)if (pParent == _root){_root = subL;}else{if (ppParent->_left == pParent){ppParent->_left = subL;}else{ppParent->_right = subL;}}subL->_parent = ppParent;//4.更新平衡因子subL->_bf = pParent->_bf = 0;}
  1. 右右 —— 左单旋
    在这里插入图片描述

💬 代码实现

在这里插入图片描述

// 右右--左单旋void RotateL(ptr pParent){ptr subR = pParent->_right;ptr subRL = subR->_left;ptr ppParent = pParent->_parent;pParent->_right = subRL;if (subRL)subRL->_parent = pParent;subR->_left = pParent;pParent->_parent = subR;if (pParent == _root){_root = subR;}else{// 是否可以用函数参数引用 ptr& pParent 优化?//if (subR->_val < ppParent->_val)if (ppParent->_left == pParent){ppParent->_left = subR;}else{ppParent->_right = subR;}}subR->_parent = ppParent;subR->_bf = pParent->_bf = 0;}
  1. 左右 —— 左右双旋

在这里插入图片描述

🔎我们可以复用RotateL(左单旋)和RotateR(右单旋)来实现右左双旋,但需要注意的是,这两个函数会把平衡因子直接更新为0,但是观察右左双旋的过程图,发现结果所更新节点的平衡因子不全为0。因此,右左双旋要自己更新平衡因子,不能依赖于RotateL和RotateR。

  • 当在c子树插入新节点时,旋转后的结果

在这里插入图片描述

  • 当在b子树插入新节点时,旋转后的结果
    在这里插入图片描述

  • 特殊情况,当h==0时,30的右子树为空,60就是新插入节点。最终平衡因子全为0。
    在这里插入图片描述

💬 代码实现

在这里插入图片描述
🔎通过上两张图可以发现,当在c子树插入时,最终subL指向节点的平衡因子为-1,其他两个为0;当在b子树插入时,最终pParent指向节点的平衡因子为1,其他两个为0。因此,判断在哪颗树插入时决定最终如何更新平衡因子的关键,而插入后且调整前的subLR的平衡因子就可以判断。插入后且调整前,若subLR的平衡因子为1,则是在c子树插入;若subLR的平衡因子为-1,则是在b子树插入。还有h==0的特殊情况,此时subLR的平衡因子为0,作特殊处理。

	void RotateLR(ptr pParent){ptr subL = pParent->_left;ptr subLR = subL->_right;int bf = subLR->_bf; // 保存插入后调整前subLR的平衡因子// 两次旋转RotateL(subL);RotateR(pParent);// 更新平衡因子if (bf == 1){subLR->_bf = 0;subL->_bf = -1;pParent->_bf = 0;}else if (bf == -1){subLR->_bf = 0;subL->_bf = 0;pParent->_bf = 1;}else if (bf == 0){subLR->_bf = 0;subL->_bf = 0;pParent->_bf = 0;}else{assert(false);}}
  1. 右左 —— 右左双旋
    在这里插入图片描述
  • 当在c子树插入新节点时,旋转后的结果

在这里插入图片描述

  • 当在b子树插入新节点时,旋转后的结果
    在这里插入图片描述

💬代码实现

在这里插入图片描述

	void RotateRL(ptr pParent){ptr subR = pParent->_right;ptr subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(pParent);if (bf == 1){subRL->_bf = 0;subR->_bf = 0;pParent->_bf = -1;}else if (bf == -1){subRL->_bf = 0;subR->_bf = 1;pParent->_bf = 0;}else if (bf == 0){subRL->_bf = 0;subR->_bf = 0;pParent->_bf = 0;}else{assert(false);}}

4.4 AVL树最终代码

// AVL树的节点
template <class T>
struct AVLTreeNode
{AVLTreeNode(const T& val):_val(val),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;T _val;int _bf;// 平衡因子
};// AVL树
template <class T>
class AVLTree
{typedef AVLTreeNode<T> node;typedef AVLTreeNode<T>* ptr;public:// 构造函数AVLTree():_root(nullptr){}// 析构函数~AVLTree(){destroy(_root);}// 中序遍历void InOrder(){_inorder(_root);}// 插入新节点bool insert(const T& val){// 先按二叉搜索树规则,找到插入位置ptr cur = _root;ptr parent = nullptr;while (cur){if (val < cur->_val){parent = cur;cur = cur->_left;}else if (val > cur->_val){parent = cur;cur = cur->_right;}else{// 相同元素不能插入return false;}}// 创建新节点并插入cur = new node(val);if (_root == nullptr){_root = cur;return true;}else{if (cur->_val < parent->_val){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}}// 更新平衡因子while (parent){if (cur == parent->_left){(parent->_bf)--;}else{(parent->_bf)++;}// 1.parent的_bf更新后为0if (parent->_bf == 0){// 插入成功break;}// 2.parent的_bf更新后为1或-1,此时需要继续向上更新平衡因子else if (parent->_bf == 1 || parent->_bf == -1){//持续往上更新cur = parent;parent = parent->_parent;}// 3.parent的_bf更新后为2或-2,此时需要旋转,旋转后以parent为根的子树为平衡二叉树,不需要在继续向上更新平衡因子else if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else{assert(false);}break;}}return true;}bool IsBalance()  {return is_balance(_root);}int Height(){return get_height(_root);}private:// 检查该树是否平衡bool is_balance(ptr root){if (root == nullptr)return true;int leftHeight = get_height(root->_left);int rightHeight = get_height(root->_right);if (rightHeight - leftHeight != root->_bf){cout << root->_val << "平衡因子异常" << endl;}return (abs(rightHeight - leftHeight) == 1 || rightHeight == leftHeight)&& is_balance(root->_left)&& is_balance(root->_right);}// 获取以root为根的子树的高度int get_height(ptr root){if (root == nullptr)return 0;return 1 + max(get_height(root->_left), get_height(root->_right));}// 析构函数的子函数void destroy(ptr root){if (root == nullptr)return;destroy(root->_left);destroy(root->_right);delete root;}void _inorder(ptr root){if (root == nullptr)return;_inorder(root->_left);cout << root->_val << " ";_inorder(root->_right);}// 左左--右单旋void RotateR(ptr pParent){ptr subL = pParent->_left;ptr subLR = subL->_right;ptr ppParent = pParent->_parent;//1.pParent(父)和subLR(子)pParent->_left = subLR;if (subLR)subLR->_parent = pParent;//2.subL(父)和pParent(子)subL->_right = pParent;pParent->_parent = subL;//3.ppParent(父)和subL(子)if (pParent == _root){_root = subL;}else{// 是否可以用函数参数引用 ptr& pParent 优化?if (ppParent->_left == pParent){ppParent->_left = subL;}else{ppParent->_right = subL;}}subL->_parent = ppParent;//4.更新平衡因子subL->_bf = pParent->_bf = 0;}// 右右--左单旋void RotateL(ptr pParent){ptr subR = pParent->_right;ptr subRL = subR->_left;ptr ppParent = pParent->_parent;pParent->_right = subRL;if (subRL)subRL->_parent = pParent;subR->_left = pParent;pParent->_parent = subR;if (pParent == _root){_root = subR;}else{// 是否可以用函数参数引用 ptr& pParent 优化?//if (subR->_val < ppParent->_val)if (ppParent->_left == pParent){ppParent->_left = subR;}else{ppParent->_right = subR;}}subR->_parent = ppParent;subR->_bf = pParent->_bf = 0;}void RotateLR(ptr pParent){ptr subL = pParent->_left;ptr subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(pParent);if (bf == 1){subLR->_bf = 0;subL->_bf = -1;pParent->_bf = 0;}else if (bf == -1){subLR->_bf = 0;subL->_bf = 0;pParent->_bf = 1;}else if (bf == 0){subLR->_bf = 0;subL->_bf = 0;pParent->_bf = 0;}else{assert(false);}}void RotateRL(ptr pParent){ptr subR = pParent->_right;ptr subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(pParent);if (bf == 1){subRL->_bf = 0;subR->_bf = 0;pParent->_bf = -1;}else if (bf == -1){subRL->_bf = 0;subR->_bf = 1;pParent->_bf = 0;}else if (bf == 0){subRL->_bf = 0;subR->_bf = 0;pParent->_bf = 0;}else{assert(false);}}ptr _root;
};

完。

相关文章:

二叉搜索树:AVL平衡

文章目录一、 二叉搜索树1.1 概念1.2 操作1.3 代码实现二、二叉搜索树的应用K模型和KV模型三、二叉搜索树的性能分析四、AVL树4.1 AVL树的概念4.2 AVL树的实现原理4.3 旋转4.4 AVL树最终代码一、 二叉搜索树 1.1 概念 二叉搜索树&#xff08; Binary Search Tree&#xff0c;…...

数据结构和算法(1):数组

目录概述动态数组二维数组局部性原理越界检查概述 定义 在计算机科学中&#xff0c;数组是由一组元素&#xff08;值或变量&#xff09;组成的数据结构&#xff0c;每个元素有至少一个索引或键来标识 In computer science, an array is a data structure consisting of a col…...

python+django+vue全家桶鲜花商城售卖系统

重点&#xff1a; &#xff08;1&#xff09; 网上花店网站中各模块功能之间的的串联。 &#xff08;2&#xff09; 网上花店网站前台与后台的连接与同步。 &#xff08;3&#xff09; 鲜花信息管理模块中鲜花的发布、更新与删除。 &#xff08;4&#xff09; 订单…...

一文带你领略 WPA3-SAE 的 “安全感”

引入 WPA3-SAE也是针对四次握手的协议。 四次握手是 AP &#xff08;authenticator&#xff09; 和 &#xff08;supplicant&#xff09;进行四次信息交互&#xff0c;生成一个用于加密无线数据的秘钥。 这个过程发生在 WIFI 连接 的 过程。 为了更好的阐述 WPA3-SAE 的作用 …...

Python解题 - CSDN周赛第38期

又来拯救公主了。。。本期四道题还是都考过&#xff0c;而且后面两道问哥在以前写的题解里给出了详细的代码&#xff08;当然是python版&#xff09;&#xff0c;直接复制粘贴就可以过了——尽管这样显得有失公允&#xff0c;考虑到以后还会出现重复的考题&#xff0c;所以现在…...

Android绘制——自定义view之onLayout

简介 在自定义view的时候&#xff0c;其实很简单&#xff0c;只需要知道3步骤&#xff1a; 测量——onMeasure()&#xff1a;决定View的大小&#xff0c;关于此请阅读《Android自定义控件之onMeasure》布局——onLayout()&#xff1a;决定View在ViewGroup中的位置绘制——onD…...

用Qt画一个温度计

示例1 以下是用Qt绘制一个简单的温度计的示例代码&#xff1a; #include <QPainter> #include <QWidget> #include <QApplication> class Thermometer : public QWidget { public:Thermometer(QWidget *parent 0); protected:void paintEvent(QPaintEvent …...

Java设计模式 04-建造者模式

建造者模式 一、 盖房项目需求 1)需要建房子&#xff1a;这一过程为打桩、砌墙、封顶 2)房子有各种各样的&#xff0c;比如普通房&#xff0c;高楼&#xff0c;别墅&#xff0c;各种房子的过程虽然一样&#xff0c;但是要求不要相同的. 3)请编写程序&#xff0c;完成需求. …...

安语未公告于2023年3月20日发布

因一些特殊原因&#xff0c;凡事都是有开始&#xff0c;高潮和结束三大过程&#xff0c;做出以下决定&#xff1a; 所有对 安语未文章 为之热爱、鞭策、奉献&#xff0c;和支持过的开发者&#xff1a; 注&#xff1a;所有资源以及资料都会正常下载和查看 如需联系&#xff1…...

进销存是什么?如何选择进销存系统?

什么是进销存&#xff1f;进销存软件概念起源于上世纪80年代&#xff0c;由于电算化的普及&#xff0c;计算机管理的推广&#xff0c;不少企业对于仓库货品的进货&#xff0c;存货&#xff0c;出货管理&#xff0c;有了强烈的需求&#xff0c;进销存软件的发展从此开始。 进入…...

基于BP神经网络的图像跟踪,基于BP神经网络的细胞追踪识别

目录 摘要 BP神经网络的原理 BP神经网络的定义 BP神经网络的基本结构 BP神经网络的神经元 BP神经网络激活函数及公式 基于BP神经网络的细胞识别追踪 matab编程代码 效果 结果分析 展望 摘要 智能驾驶,智能出行是现代社会发展的趋势之一,其中,客量预测对智能出行至关重要,…...

Java面试总结篇

引用介绍 1.线程安全不安全的概念 ​ 线程安全: 指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。 ​ 线程不安全: 是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏…...

100天精通Python(可视化篇)——第80天:matplotlib绘制不同种类炫酷柱状图代码实战(簇状、堆积、横向、百分比、3D柱状图)

文章目录0. 专栏导读1. 普通柱状图2. 簇状柱形图3. 堆积柱形图4. 横向柱状图5. 横向双向柱状图6. 百分比堆积柱形图7. 3D柱形图8. 3D堆积柱形图9. 3D百分比堆积柱形图0. 专栏导读 &#x1f3c6;&#x1f3c6;作者介绍&#xff1a;Python领域优质创作者、CSDN/华为云/阿里云/掘金…...

【Java】UDP网络编程

文章目录前言DatagramSocketDatagramPacket注意事项与区别代码演示前言 UDP&#xff08;user datagram protocol&#xff09;的中文叫用户数据报协议&#xff0c;属于传输层。 UDP是面向非连接的协议&#xff0c;它不与对方建立连接&#xff0c;而是直接把我要发的数据报发给对…...

Springboot源代码总结

前言 编写微服务,巩固知识 文章目录 前言springboot原理springboot启动流程SpringBoot自动配置底层源码解析自动配置到底配了什么?自动配置类条件注解Starter机制@ConditionalOnMissingBeanSpringBoot启动过程源码解析构造SpringApplication对象SpringBoot完整的配置优先级s…...

JVM监控搭建

文章目录JVM监控搭建整体架构JolokiaTelegrafInfluxdbGrafanaJVM监控搭建 整体架构 JVM 的各种内存信息&#xff0c;会通过 JMX 接口进行暴露。 Jolokia 组件负责把 JMX 信息翻译成容易读取的 HTTP 请求。Telegraf 组件作为一个通用的监控 agent&#xff0c;和 JVM 进程部署在…...

java中如何优化大量的if...else...

目录 策略模式&#xff08;Strategy Pattern&#xff09; 工厂模式&#xff08;Factory Pattern&#xff09; 映射表&#xff08;Map&#xff09; 数据驱动设计&#xff08;Data-Driven Design&#xff09; 策略模式&#xff08;Strategy Pattern&#xff09; 将每个条件分…...

24. linux系统基础

两个进程间想通讯&#xff0c;必须要通过内核&#xff0c;今天讲的信号其实本质也是讲进程间通讯的意思&#xff0c;那么你为什么可以在shell环境下&#xff0c;可以和一个进程发kill-9啊&#xff1f; shell是不是相当于一个进程&#xff1f;你自己运行的那个进程是不是也相当于…...

【C++】面试101,二叉搜索树的最近公共祖先,在二叉树中找到两个节点的最近公共祖先,序列化二叉树,重建二叉树,输出二叉树的右视图,组队竞赛,删除公共字符

目录 1.二叉搜索树的最近公共祖先 2.在二叉树中找到两个节点的最近公共祖先 3.序列化二叉树 4.重建二叉树 5.输出二叉树的右视图 6.组队竞赛 7.删除公共字符 1.二叉搜索树的最近公共祖先 这是一个简单的问题&#xff0c;因为是二叉搜索树&#xff08;有序&#xff09;&am…...

Java常见面试题及解答

Java常见面试题及解答1 面向对象的三个特征2 this&#xff0c;super关键字3 基础数据类型4 public、protected、default、private5 接口6 抽象类6.1 抽象类和接口的区别7 重载&#xff08;overload&#xff09;、重写&#xff08;override&#xff09;8 final、finalize、final…...

【Docker】镜像的原理定制化镜像

文章目录镜像是什么UnionFS&#xff08;联合文件系统&#xff09;Docker镜像加载原理制作本地镜像 docker commit -m"提交的描述信息" -a"作者" 容器ID 要创建的目标镜像名:[标签名]案例演示ubuntu安装vim本地镜像发布到阿里云本地镜像发布到阿里云流程将本…...

国内版的ChatGPT弯道超车的机会在哪里?

前言 从去年11月最后一天ChatGPT诞生&#xff0c;截至目前&#xff0c;ChatGPT的热度可谓是爆了。众所周知&#xff0c;ChatGPT是美国“开放人工智能研究中心”研发的聊天机器人程序&#xff0c;它是一个人工智能技术驱动的自然语言处理工具&#xff0c;它能够通过学习和理解人…...

【字符串】

string1.char str[]类型fgets(s,10000,stdin) cin.getline(cin,10000) strlen(str)sizeof 求静态数组长度2.string类型getline(cin,a) cin.getline(cin,10000) str.lenth()str.size()cin 遇到空格就停止3.gets 函数char str[20];gets(str);4.puts 函数puts(str) 相当于 cout<…...

加载驱动之后无法在/dev/下生成vedio0

前言 环境介绍&#xff1a; 1.编译环境 Ubuntu 18.04.5 LTS 2.SDK orangepi Linux 5.4 SDK 3.uboot v2020.04 4.gcc gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf 5.单板 orangepi pc plus 一、问题 继上一篇成功加载gc2035.ko文件之后&#xff0c;理论上…...

Java之类与对象(图文结合)

目录 一、面向对象的初步认知 1、什么是面向对象 2、面向对象与面向过程 二、类定义和使用 1、简单认识类 2、类的定义格式 3、练习 &#xff08;1&#xff09;定义一个狗类 &#xff08;2&#xff09;定义一个学生类 三、类的实例化 1、什么是实例化 2、类和对象的…...

基于 VCS-NLP 的动态低功耗仿真验证介绍

&#x1f525;点击查看精选 IC 技能树系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#…...

ESP32-S3 自带usb/jtag初步尝试体验

一、背景 最近在做一台小机器&#xff0c;设备初步规划使用几个实体按钮&#xff0c;这样方便用户戴手套操作。但因为设备有一些需要配置的参数&#xff0c;有需要配备屏幕。但是开发时间比较紧。考虑再三&#xff0c;决定先在初步配备一个简单的控制箱。控制箱上不带屏幕。后…...

前端性能优化总结

前端性能优化是指在设计和开发网站时&#xff0c;采取一些措施来提升网站的性能。这对用户来说是非常重要的&#xff0c;因为高性能的网站可以带来更好的用户体验&#xff0c;同时也有助于提升搜索引擎排名。一、常见前端性能优化措施常见的前端性能优化方法有&#xff1a;压缩…...

React(四) ——hooks的使用

&#x1f9c1;个人主页&#xff1a;个人主页 ✌支持我 &#xff1a;点赞&#x1f44d;收藏&#x1f33c;关注&#x1f9e1; 文章目录⛳React Hooks&#x1f4b8;useState(保存组件状态)&#x1f948;useEffect(处理副作用)&#x1f50b;useCallback&#xff08;记忆函数&#…...

iphone手机热点卡顿多次断连解决办法

文章目录解决方法检查一下几个地方&#xff1a;1.个人热点是否打开2.查看手机是否为4g3.查看手机的最大兼容性开关是否关闭&#xff01;&#xff01;很重要解决方法 检查一下几个地方&#xff1a; 1.个人热点是否打开 这个个人热点容易自动断开&#xff0c;先检查一下是不是…...