【C++进阶】二叉树进阶之二叉搜索树

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、什么是二叉搜索树
  • 二、二叉搜索树的实现(迭代版)
      • 2.1 定义二叉树的结点
      • 2.2 插入操作
      • 2.3 查找操作
      • 3.4 删除操作
      • 3.5 二叉搜索树的遍历
        • 3.5.1 中序遍历(重点)
        • 3.5.2 前序遍历
        • 3.5.3 后序遍历
  • 四、二叉搜索树的实现 (递归版)
      • 4.1 查找操作
      • 4.2 插入操作
      • 4.3 删除操作
  • 五、代码补充
      • 5.1 释放结点
      • 5.2 拷贝构造
      • 5.3 赋值运算符重载
  • 六、性能分析
  • 七、完整代码
        • 7.1 非递归
      • 7.2 递归

一、什么是二叉搜索树

二叉搜索树(Binary Search Tree)是基于二叉树的一种改进版本。因为普通二叉树没有实际价值,无法进行插入、删除等操作,但二叉搜索树就不一样了。

二叉搜索树又称二叉排序树,它或者是一棵空树。如果不为空则满足一下性质:

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

总之,左节点比根小,右节点比根大。因此二叉搜索树的查找效率极高,具有一定的实际价值。

下图展示了二叉搜索树

在这里插入图片描述

搜索二叉树还有一个非常重要的特性:中序遍历(左子树 根 右子树)的结果为升序。

二、二叉搜索树的实现(迭代版)

2.1 定义二叉树的结点

一般来说,二叉树使用链表来定义,不同的是,由于二叉树每个结点都存在两条出边,因此指针域变为两个,分别指向左子树和右子树的根结点地址,因此又把这种链表叫做二叉链表。

template<class K>
struct BSTreeNode // 结点
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;// 默认构造BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}
};

2.2 插入操作

  1. 当插入一个值时,必须先找到满足二叉搜索性质的位置
  2. 如果当前位置不为空或者插入的值和已有的值重复,则插入失败。
  3. 如果找到满足条件的位置并且为空,则结束循环,进行插入。步骤:创建新节点、判断需要插在左边还是右边、链接新节点
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:BSTree():_root(nullptr){}// 插入操作bool Insert(const K& key){// 一开始树为空,直接插入if (_root == nullptr){_root = new Node(key);return true;}// cur用来查找合适的位置Node* cur = _root;// parent用来找cur的父亲结点Node* parent = nullptr;while (cur){// 插入的结点大于当前结点// 往右走if (cur->_key < key){parent = cur;cur = cur->_right;}// 插入的结点小于当前结点// 往左走else if (cur->_key > key){parent = cur;cur = cur->_left;}// 最后一种情况就是出现冗余// 直接返回else{return false;}}// 当循环来到此处说明已经找到合适的位置了// 直接开始插入操作// 1. 创建新的结点cur = new Node(key);// 2. 判断是插在父亲的左边还是右边if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}
private:Node* _root;
};

【插入成功】

在这里插入图片描述

【插入失败】

在这里插入图片描述

2.3 查找操作

  1. 从根结点开始比较,比根大的则往右查找;比根小的往左查找。
  2. 走到空还没找到,说明值不存在。否则就是存在
bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return true;}}return false;}

【查找成功】

在这里插入图片描述

【查找失败】

在这里插入图片描述

3.4 删除操作

二叉搜索树的删除是个麻烦事,需要考虑很多情况,因此大多数面试都会考察搜索二叉树的删除操作

删除逻辑:

首先查找元素是否在二叉搜索树中,如果不存在,则直接返回;否在要删除的结点就要分以下四种情况

1、 要删除的结点只有左孩子

只需要将被删除结点的左子树与父节点进行链接即可。前提是要判断删除的结点是父结点的左还是右。

在这里插入图片描述

2、 要删除的结点只有右孩子

同理,左子树为空时,将其右子树与父节点进行判断链接,链接完成后删除目标节点

3、要删除的结点有两个孩子

当左右都不为空时,就有点麻烦了,需要找到一个合适的值(即>左子树所有节点的值,又<右子树所有节点的值),确保符合二叉搜索树的基本特点

符合条件的值有:左子树的最右节点(左子树中最大的)或者右子树的最左节点(右子树中最小的),将这两个值中的任意一个覆盖待删除节点的值,都能确保符合要求

以左子树的最右节点(左子树中最大的)为例:

在这里插入图片描述

4、要删除的结点没有孩子

这种情况可以包含在1或者2中

下面是代码实现(详细的代码解释)

bool Erase(const K& key)
{// parent - 用来记录删除结点的父亲Node* parent = nullptr;// cur - 记录被删除的结点Node* cur = _root;// 查找key(被删除的结点)while (cur){// 如果key比当前元素要大// 就往右找if (cur->_key < key){parent = cur;cur = cur->_right;}// key比当前元素要小// 就往左找else if (cur->_key > key){parent = cur;cur = cur->_left;}// 最后一种情况就是找到了// 对于找到要分情况讨论	else {// 1. 如果删除的结点的左孩子为空// 右孩子可能为空(包含删除叶子结点的情况)// 也可能不为空if (cur->_left == nullptr){// 可能删除的结点是根结点// 也就是以下这种情况// 1 --> root cur//   2//     3// 没有考虑这个问题会引发parent野指针问题if (cur == _root){_root = cur->_right;}else{// 如果删除的对象是父亲的右孩子// 直接将被删除的结点的孩子接上去// (被删除结点右孩子是存在的)if (parent->_right == cur){parent->_right = cur->_right;}// 否则就删是删除父亲的左孩子else{parent->_left = cur->_right;}}}	// 以下代码逻辑和上面类似// 如果删除的结点,右孩子为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}} // 左右都不为空 // 左右子树都不为空的场景中// parent 要初始化为cur,避免后面的野指针问题else{// 找替代节点// 以左子树的最右节点(左子树中最大的)为例Node* parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}// 替换swap(cur->_key, leftMax->_key);if (parent->_left == leftMax){parent->_left = leftMax->_left;}else{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}return false;
}

3.5 二叉搜索树的遍历

二叉搜索树的遍历操作和二叉树一模一样的,因此可以有前序遍历、中序遍历以及后序遍历。

3.5.1 中序遍历(重点)

搜索二叉树的 中序遍历(左子树 根 右子树)的结果为升序

void InOrder(Node* root)
{if (root == NULL){return;}InOrder(root->_left);cout << root->_key << " ";InOrder(root->_right);
}

但因为这里是一个被封装的类,所以面临着一个尴尬的问题:二叉搜索树的根root是私有,外部无法直接获取

解决办法:

  1. 公有化(破坏类的封装性,不推荐)
  2. 使用静态成员函数可以直接被类外使用。(也是ok,但是不推荐)
  3. 将这种需要用到根的函数再封装。(推荐)
public:
void InOrder()
{_InOrder(root);
}
private:
void _InOrder(Node* root)
{if (root == NULL){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}

实际调用时,只能调到InOrder,因为真正的函数_InOrder为是私有,除了类之外,其他地方不可访问。

3.5.2 前序遍历

public:
void PreOrder()
{_PreOrder(_root);
}private:void _PreOrder(Node* root){if (root == nullptr){return;}cout << root->_key << " ";_PreOrder(root->_left);_PreOrder(root->_right);}

3.5.3 后序遍历

public:
void PostOrder()
{_PostOrder(_root);
}
private:void _PostOrder(Node* root)
{if (root == nullptr){return;}_PostOrder(root->_left);_PostOrder(root->_right);cout << root->_key << ' ';
}

四、二叉搜索树的实现 (递归版)

4.1 查找操作

递归查找逻辑:

  1. 如果当前根的值<查找值,递归至右树查找
  2. 如果当前根的值>查找值,递归至左树查找

二叉树的递归常常是通过将问题分解为更小规模的子问题来解决的。通过传递根节点作为参数,我们可以在每次递归中处理当前节点,并将问题分解为处理左子树和右子树的子问题。因此,还需要对函数进行封装。

public:
bool Find(const K& key)
{return _Find(_root, key);
}
private:
bool _Find(Node* root, const k& key)
{// 如果根为空,说明查找失败(递归出口)if (root == nullptr)return false;// 查找的值比当前结点大,往右找if (root->_key < key){return _Find(root->_right);}// 查找的值比当前结点小,往左找else if (root->_key > key){return _Find(root->_left);}// 最后一种情况就是找到了elsereturn true;
}

4.2 插入操作

这里和其它不同的是,在传参的时候使用了引用传引用是为了在递归查找过程中能够修改目标节点的指针值。搜索二叉树的递归查找算法通常会通过比较当前节点的值与目标值来决定向左子树还是右子树继续查找。如果不传引用,每次递归调用时都会创建一个新的节点指针副本,这样就无法修改原始节点的指针值,从而导致无法正确返回目标节点的指针。

这么说有点抽象,可以先看代码理解;过会画完递归展开图大家就能够理解了。

public:
bool Insert(const K& key)
{return _Insert(_root, key);
}private:
bool _Insert(Node*& root, const K& key)
{// 当走到空,说明找到了合适的位置if (root == nullptr){// 还得找到父亲,为什么加个引用就搞定了?root = new Node(key);return true;}// 插入的值比当前结点大,往右找if (root->_key < key){return _Insert(root->_right);}// 插入的值比当前结点小,往左找else if (root->_key > key){return _Insert(root->_left);}// 当插入的值和树发生冗余,直接返回falseelse{return false;}
}

【递归展开图】

在这里插入图片描述

4.3 删除操作

递归删除时也使用了引用,其想法和插入一样,不需要找删除结点的父亲,直接修改目标节点的指针值

public:
bool Earse(const K& key)
{return _Erase(root, key);
}private:
bool _Erase(Node* root, const K& key)
{// 如果是空结点,说明删除失败if (root == nullptr)return false;if (root->_key < key){return _Erase(root->_right, key);}else if (root->_key > key){return _Erase(root->_left, key);}// 找到keyelse{Node* del = root;// 还是分三种情况// 1. 删除结点的左孩子为空if (root->_left == nullptr){root = root->_right;}// 2. 删除结点的右孩子为空else if (root->_right == nullptr){root = root->_left;}// 3. 删除结点孩子都不为空else{//递归为小问题去处理Node* maxLeft = root->_left;while (maxLeft->_right){maxLeft = maxLeft->_right;}//注意:需要交换std::swap(root->_key, maxLeft->_key);//注意:当前找的是左子树的最右节点,所以递归从左子树开始return _Erase(root->_left, key);}delete del;	//释放节点return true;}
}	

五、代码补充

5.1 释放结点

创建节点时,使用了new申请堆空间,根据动态内存管理原则,就需要使用delete释放申请的堆空间,但二叉搜索树是一棵树,不能直接释放,需要递归式的遍历每一个节点

释放思路:后序遍历思想

public:
~BSTree()
{_destory(_root);
}private:
void _destory(Node*& root)
{if (root == nullptr)return;//后序遍历销毁destory(root->_left);destory(root->_right);delete root;root = nullptr;
}

5.2 拷贝构造

销毁问题考虑完以后,就要想是否会有浅拷贝问题,因为浅拷贝问题会导致一个结点析构两次,程序就崩了。而我们在类和对象中说过,如果类中有动态分配内存的指针变量,则需要手动编写深拷贝的拷贝构造函数。

public:
BSTree(BSTree<K>& tree):_root(nullptr)
{_root = _Copy(tree._root);
}private:
Node* _Copy(Node* root)
{//递归拷贝if (root == nullptr)return nullptr;Node* new_root = new Node(root->_key);	new_root->_left = _Copy(root->_left);new_root->_right = _Copy(root->_right);return new_root;
}

5.3 赋值运算符重载

如果没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝(浅拷贝)。其默认成员函数和拷贝构造的默认函数类似:内置类型成员变量是直接赋值的,而自定义类型成员会去调用它的默认函数。因此赋值运算符也需要自己实现深拷贝

直接写现代写法

BSTree<K> operator=(BSTree<K> tree)
{std::swap(_root, tree._root);return *this;
}

六、性能分析

从名字上来看,二叉树的特性就是查找快。

搜索二叉树的时间复杂度取决于树的高度。因此当是一颗平衡二叉搜索树时,时间复杂度是O(log n)。因为每次搜索都可以通过比较目标值与当前节点的值来确定向左子树还是右子树进行搜索,这样每次都可以将搜索范围减半。
是不是有点类似于二分查找,但二分查找不是一个很实用的算法。因为对比二叉搜索树来说,二分查找(底层是一个数组)的删除和插入的效率低0(n)(特别是中间插入和删除)

在这里插入图片描述

二叉搜索树看起来这么完美,但下限没有保障。在最坏的情况下,当搜索二叉树是一个不平衡的树时,时间复杂度为O(n),其中n是树中节点的数量。这是因为在最坏情况下,每次搜索都要遍历树的所有节点。

在这里插入图片描述

因此,为了解决这个问题,引入了AVL树和红黑树(后续会讲解)

七、完整代码

7.1 非递归

#pragma once// 迭代版本
template<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:BSTree():_root(nullptr){}bool Insert(const K& key){// 如果一开始树为空,直接插入if (_root == nullptr){// 对自定义类型的new,会自动调用其默认构造函数_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return true;}}return false;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else // 找到了{// 左为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}}// 右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}} // 左右都不为空 else{// 找替代节点Node* parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}swap(cur->_key, leftMax->_key);if (parent->_left == leftMax){parent->_left = leftMax->_left;}else{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}return false;}void InOrder(){_InOrder(_root);}void PreOrder(){_PreOrder(_root);}void PostOrder(){_PostOrder(_root);}private:void _PostOrder(Node* root){if (root == nullptr){return;}_PostOrder(root->_left);_PostOrder(root->_right);cout << root->_key << ' ';}void _PreOrder(Node* root){if (root == nullptr){return;}cout << root->_key << " ";_PreOrder(root->_left);_PreOrder(root->_right);}void _InOrder(Node* root){if (root == NULL){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}private:Node* _root;
};

7.2 递归

#pragma once
template<class K>
struct BSTNode
{BSTNode<K> _left;BSTNode<K> _right;K _key;BSTNode(const K& key):_left(nullptr), _right(nullptr), key(key){}
};template<class K>
class BSTree
{typedef BSTNode<K> Node;
public:BSTree():_root(nullptr){}~BSTree(){_destory(_root);}bool Find(const K& key){return _Find(_root, key);}bool Insert(const K& key){return _Insert(_root, key);}bool Earse(const K& key){return _Erase(root, key);}BSTree(BSTree<K>& tree):_root(nullptr){_root = _Copy(tree._root);}BSTree<K> operator=(BSTree<K> tree){std::swap(_root, tree._root);return *this;}
private:Node* _Copy(Node* root){//递归拷贝if (root == nullptr)return nullptr;Node* new_root = new Node(root->_key);	new_root->_left = _Copy(root->_left);new_root->_right = _Copy(root->_right);return new_root;}void _destory(Node*& root){if (root == nullptr)return;//后序遍历销毁destory(root->_left);destory(root->_right);delete root;root = nullptr;}bool _Find(Node* root, const k& key){if (root == nullptr)return false;// 查找的值比当前结点大,往右找if (root->_key < key){return _Find(root->_right);}// 查找的值比当前结点小,往左找else if (root->_key > key){return _Find(root->_left);}// 最后一种情况就是找到了elsereturn true;}bool _Insert(Node*& root, const K& key){// 当走到空,说明找到了合适的位置if (root == nullptr){// 还得找到父亲,为什么加个引用就搞定了?root = new Node(key);return true;}// 插入的值比当前结点大,往右找if (root->_key < key){return _Insert(root->_right);}// 插入的值比当前结点小,往左找else if (root->_key > key){return _Insert(root->_left);}// 当插入的值和树发生冗余,直接返回falseelse{return false;}}bool _Erase(Node* root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _Erase(root->_right, key);}else if (root->_key > key){return _Erase(root->_left, key);}else{Node* del = root;// 还是分三种情况// 1. 删除结点的左孩子为空if (root->_left == nullptr){root = root->_right;}// 2. 删除结点的右孩子为空else if (root->_right == nullptr){root = root->_left;}// 3. 删除结点孩子都不为空else{//递归为小问题去处理Node* maxLeft = root->_left;while (maxLeft->_right){maxLeft = maxLeft->_right;}//注意:需要交换std::swap(root->_key, maxLeft->_key);//注意:当前找的是左子树的最右节点,所以递归从左子树开始return _Erase(root->_left, key);}delete del;	//释放节点return true;}}private:Node* _root;
};

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

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

相关文章

北斗高精度定位,破解共享单车停车乱象

如今&#xff0c;共享单车已经成为了许多人出行的首选方式&#xff0c;方便了市民们的“最后一公里”&#xff0c;给大家的生活带来了很多便利。然而&#xff0c;乱停乱放的单车也给城市治理带来了难题。在这种情况下&#xff0c;相关企业尝试将北斗导航定位芯片装载到共享单车…

SpringMVC综合案例

目录 一、SpringMVC常用注解 二、传递参数 2.1 基础类型String 2.2 复杂类型 2.3 RequestParam 2.4 PathVariable 2.5 RequestBody 2.6 RequestHeader 2.7 请求方法 三、返回值 3.1 void 3.2 String 3.3 StringModel 3.4 ModelAndView 四、页面跳转 4.1 转发 4…

蓝桥杯打卡Day6

文章目录 N的阶乘基本算术整数查询 一、N的阶乘OI链接 本题思路&#xff1a;本题是关于高精度的模板题。 #pragma GCC optimize(3) #include <bits/stdc.h>constexpr int N1010;std::vector<int> a; std::vector<int> f[N];std::vector<int> mul(in…

如何用 Java 找到字符串中的元音

这个题目其实不难&#xff0c;这是一个公司面试的时候要求的题目。 这个公司的面试有点意思&#xff0c;他们希望 Zoom 看我的电脑&#xff0c;然后让我解决问题。 题目 题目就非常简单了&#xff0c;他们给了我 2 个字符串。 其中一个是测试字符串&#xff0c;另外一个是元…

《热题100》字符串、双指针、贪心算法篇

思路&#xff1a;对于输入的的字符串&#xff0c;只有三种可能&#xff0c;ipv4,ipv6,和neither ipv4:四位&#xff0c;十进制&#xff0c;无前导0&#xff0c;小于256 ipv6:八位&#xff0c;十六进制&#xff0c;无多余0&#xff08;00情况不允许&#xff09;&#xff0c;不…

Lua02——应用场景及环境安装

应用场景 是当今游戏领域使用最广泛的脚本语言之一。 搭配 OpenResty 使用&#xff0c;可以扩展Nginx服务器的功能&#xff0c;使用者仅需要编写Lua代码就能轻松完成业务逻辑。 与 Redis 结合。 Adobe Photoshop Lightroom 搭配 Lua 编写插件。 与游戏结合&#xff1a; C/…

3.4 栈与递归

3.4.1 采用递归算法解决问题 3.4 栈与递归的关系 栈和递归之间有着紧密的关系&#xff0c;特别是在算法和程序设计中。栈作为一种数据结构&#xff0c;可以有效地支持递归算法的实现。本节我们将详细讨论栈在递归算法中的作用及其在程序设计中的重要性。 1. 递归算法的基本概…

Swift学习内容精选(一)

Swift 可选(Optionals)类型 Swift 的可选&#xff08;Optional&#xff09;类型&#xff0c;用于处理值缺失的情况。可选表示"那儿有一个值&#xff0c;并且它等于 x "或者"那儿没有值"。 Swfit语言定义后缀&#xff1f;作为命名类型Optional的简写&…

uni-app--》基于小程序开发的电商平台项目实战(一)

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;uni-app &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

vue响应式详解

1. 响应式的定义 我们都知道&#xff0c;vue是基于javascript的&#xff0c;那我们使用一段javascript代码来描述响应式 let a 1,b 1,c; c a b; console.log(c) // 输出 2 // 改变 a的值 a 3; // 重新给c赋值 即把 c a b; 再执行一遍c的值才能变为 4 // c a b; // …

小白也可以玩转CMake之常用必备

目录 1.设置编译器flags2.设置源文件属性3.链接器标志4.Debug与Release包 今天&#xff0c;分享一篇工作中经常用到的一些CMake命令&#xff0c;看完就学会了哦&#xff0c;更多CMake与C内容也期待加入星球与我一起学习呀~ 1.设置编译器flags 例如&#xff1a;设置C标准&#x…

C高级shell脚本

#!/bin/bash function fun() {sum0i0b($*)while [ $i -lt ${#b[*]} ]do((sum ${b[i]}))((i))doneecho $sum }read -p "请输入数组" -a a fun ${a[*]}function fun2() {aid -ubid -gecho $a $b } p(fun2) uid${p[0]} pid${p[1]} echo $uid $pidXMind

飞行动力学 - 第20节-横向静稳定性 之 基础点摘要

飞行动力学 - 第20节-横向静稳定性 之 基础点摘要 1. 横向静稳定性2. 横向静稳定准则3. 横向静稳定性的组成4. 参考资料 1. 横向静稳定性 2. 横向静稳定准则 对于横向静稳定性飞机&#xff0c;右滚转扰动会产生正侧滑&#xff0c;飞机产生左滚恢复力矩(负)&#xff0c;即 Δ …

java 身份证号码验证

需要编号文件 编号文件部分内容如下 11:北京市 1101:市辖区 110101:东城区 110102:西城区 110105:朝阳区 110106:丰台区 110107:石景山区 110108:海淀区 ...... 编号文件内容比较多 csdn点击 下载地址 java代码如下 import java.io.*; import java.text.ParseException; im…

github 创建自己的分支 并下载代码

github创建自己的分支 并下载代码 目录概述需求&#xff1a; 设计思路实现思路分析1.进入到master分支&#xff0c;git checkout master;2.master-slave的个人远程仓库3.爬虫调度器4.建立本地分支与个人远程分支之间的联系5.master 拓展实现 参考资料和推荐阅读 Survive by day…

golang面试题:reflect(反射包)如何获取字段tag​?为什么json包不能导出私有变量的tag?

问题 json包里使用的时候&#xff0c;会结构体里的字段边上加tag&#xff0c;有没有什么办法可以获取到这个tag的内容呢&#xff1f; 举例 tag信息可以通过反射&#xff08;reflect包&#xff09;内的方法获取&#xff0c;通过一个例子加深理解。 package mainimport (&quo…

Linux 6.6 初步支持AMD 新一代 Zen 5 处理器

AMD 下一代 Zen 5 CPU 现已开始为 Linux 6.6 支持提交相关代码&#xff0c;最新补丁包括提供温度监控和 EDAC 报告等。 最新的 Linux 6.6 代码中已经加入了包括支持硬件监视器温度监控和 EDAC 报告的补丁。此外&#xff0c;新版本还加入了 x86 / misc 补丁&#xff0c;Phoronix…

初出茅庐的小李博客之根据编译时间生成软件版本号

为什么要软件版本号呢&#xff1f; 生成软件版本号是在软件开发和维护过程中非常重要的一项任务&#xff0c;它有很多意义和好处&#xff0c;同时也有多种常见的方法。 标识和追踪&#xff1a;软件版本号是唯一的标识符&#xff0c;用于区分不同版本的软件。这有助于开发人员和…

华为云云服务器云耀L实例评测 | 华为云云服务器实例新品全面解析

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

CATIA Composer软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 CATIA&#xff08;Computer-Aided Three-dimensional Interactive Application&#xff09;是一款由法国达索系统公司开发的三维计算机辅助设计&#xff08;CAD&#xff09;软件。它是一种全面的产品开发解决方案&#xff0c;广泛…