【C++庖丁解牛】二叉搜索树(Binary Search Tree,BST)

🍁你好,我是 RO-BERRY
📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识
🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油

在这里插入图片描述


目录

  • 1. 二叉搜索树概念
  • 2. 二叉搜索树操作
  • 3. 二叉搜索树的实现
    • 3.1 准备工作
      • 结点结构体BSTreeNode
      • 二叉搜索树BinarySearchTree
    • 3.2 函数体实现
      • 📖bool Insert(const K& key)
      • 📖bool Find(const K& key)
      • 📖打印二叉搜索树void InOrder()
      • 📖删除 Erase
      • 📖构造函数、析构函数、拷贝构造以及复制拷贝
    • 3.3 函数体实现(优化版)
      • 📖优化版insert函数
      • 📖优化版find函数
      • 📖优化版Erase函数
    • 3.4 源代码BinarySearchTree.h
  • 4. 二叉搜索树的应用
    • kv二叉树实现
  • 5. 二叉搜索树的性能分析


1. 二叉搜索树概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

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

二叉搜索树(Binary Search Tree,简称BST)是种常用的数据结构,它是一棵二叉树,并且满足以下性质:

对任意节点,其左子树中的节点的值都小于该节点的值。
对于任意节点,其右子树中的所有节点的值都大于该节点的值。
左子树和右子树也都是二叉搜索树。
二叉搜索树的这种特性使得它在查找、插入和删除操作上具有高效性能。通过比较节点的值,可以快速定位到目标节点或者确定插入位置。

二叉搜索树的操作包括:

查找:从根节点开始,递归地比较目标值与当前节点的值,直到找到目标节点或者遍历到叶子节点。
插入:从根节点开始,递归地比较目标值与当前节点的值,根据大小关系选择左子树或右子树进行插入,直到找到合适的插入位置。
删除:删除操作比较复杂,需要考虑被删除节点的子节点情况。可以分为三种情况:被删除节点没有子节点、被删除节点只有一个子节点、被删除节点有两个子节点。
二叉搜索树的时间复杂度取决于树的高度,平均情况下为O(log n),最坏情况下为O(n)。为了保持树的平衡,可以使用平衡二叉搜索树(如AVL树、红黑树)来优化性能。

在这里插入图片描述

2. 二叉搜索树操作

在这里插入图片描述

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
  1. 二叉搜索树的查找
    a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
    b、最多查找高度次,走到到空,还没找到,这个值不存在。
  2. 二叉搜索树的插入
    插入的具体过程如下:
    a. 树为空,则直接新增节点,赋值给root指针
    b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

在这里插入图片描述

  1. 二叉搜索
    首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:

情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除

在这里插入图片描述

3. 二叉搜索树的实现

3.1 准备工作

结点结构体BSTreeNode

我们实现二叉搜索树需要定义其结点,定义其左右节点以及value,方便后续操作进行

template<class K>
struct BSTreeNode      //二叉树结点
{typedef BSTreeNode<K> Node;     //简化名称Node* _left;        //左节点Node* _right;       //右节点K _key;             //结点valueBSTreeNode(const K& key)    //构造函数:_left(nullptr),_right(nullptr),_key(key){}
};

二叉搜索树BinarySearchTree

template<class K>
class BinarySearchTree
{typedef BSTreeNode<K> Node;    //节点名称简化
public:.....         //二叉搜索树函数接口
private:Node* _root = nullptr;        //省略了初始化在定义的时候就进行初始化
};

3.2 函数体实现

📖bool Insert(const K& key)

实现思路:

二叉搜索树的定义是左边的子结点必定小于父节点,右边的子结点必定大于父节点,所以我们采取key值与节点value比较,如果key大于节点value,则继续与节点右子节点比较;如果key小于节点value,则继续与节点左子节点比较,最终如果走到空的位置,则进行插入。

这里我们需要定义一个parent节点帮助我们定位最终插入后其父节点的位置,然后将父节点与新插入的结点进行连接,将父节点的value与key值比较,若key大于value,那么肯定是被插入到父节点的右子树里了;若key小于value,则反之。

另:我们要独自判断空树的情况,不然会出现程序崩溃;搜索二叉树的每一个节点数值不能一样,那么我们如果插入的key与某个value相等,也会返回false

实现代码:

	bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;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;}}cur = new Node(key);if (parent->_key < key)   //连接父节点{parent->_right = cur;}else{parent->_left = cur;}return true;}

📖bool Find(const K& key)

实现思路:

左边的子结点必定小于父节点,右边的子结点必定大于父节点,所以我们采取key值与节点value比较,如果key大于节点value,则继续与节点右子节点比较;如果key小于节点value,则继续与节点左子节点比较,最终如果走到空的位置,则返回false,如果找到与key相等的value,则返回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;}

📖打印二叉搜索树void InOrder()

实现思路:

使用递归实现中序遍历,二叉搜索树的中序遍历其实输出出来是一个递增排序的数组,从根节点开始,访问其左子树,并进行打印,然后访问右子树进行打印。
这里用了一个巧妙的方法: 我们对二叉搜索树打印需要传入根节点位置,才能进行递归,但是根节点为私有成员,我们要么写一个GetRoot()函数获取到根节点位置,才能传入,我们在这里可以对其进行封装,在上面重新定义一个打印函数,然后在这个函数里面调用刚刚的打印函数,并传入root,这样就不用在调用的时候传入root了。

实现代码:

	void InOrder()       //巧妙调用输出函数{_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}

📖删除 Erase

实现思路:
二叉搜索树(Binary Search Tree,BST)的删除操作是指在BST中删除一个指定的节点。删除操作的模拟实现可以按照以下步骤进行:

首先,需要找到要删除的节点。从根节点开始,比较要删除的节点值与当前节点值的大小关系,如果相等则找到了要删除的节点,如果小于当前节点值,则继续在左子树中查找,如果大于当前节点值,则继续在右子树中查找。重复这个过程直到找到要删除的节点或者遍历到叶子节点为止。

如果找到了要删除的节点,需要考虑以下几种情况:

  1. 如果要删除的节点是叶子节点(没有子节点),直接删除即可。
  2. 如果要删除的节点只有一个子节点,将其子节点替代要删除的节点即可。
  3. 如果要删除的节点有两个子节点,需要找到其右子树中的最小节点(或者左子树中的最大节点),将其值替换到要删除的节点上,并将最小(或最大)节点删除。

实现删除操作时,需要注意维护BST的性质,即左子树中的所有节点值都小于当前节点值,右子树中的所有节点值都大于当前节点值。

实现代码:

	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 (cur == parent->_right){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;return true;}else if (cur->_right == nullptr)  //右子树为空{if (cur == _root){_root = cur->_left;}else{if (cur == parent->_right){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;return true;}else                          //左右子树都不为空{// 替换法Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMin == rightMinParent->_left)     //根节点rightMinParent->_left = rightMin->_right;    elserightMinParent->_right = rightMin->_right;delete rightMin;return true;}}}return false;}

📖构造函数、析构函数、拷贝构造以及复制拷贝

这里直接实现,不再讲述原理,因为这一块比较基础了

	//强制生成默认构造BinarySearchTree() = default;BinarySearchTree(const BinarySearchTree<K>& t)   //拷贝构造函数{_root = Copy(t._root);}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return new Root;}BinarySearchTree<K>& operator=(const BinarySearchTree<K>& t)    //赋值拷贝{swap(_root, t._root);return *this;}~BinarySearchTree()   //析构函数{Destroy(_root);}

3.3 函数体实现(优化版)

我们的优化主要用了封装的艺术

📖优化版insert函数

我们在上面的函数实现时需要判断最终在左子树还是在右子树,并且还要定义一个parent来存储其父节点,而我们对其进行优化,可以看到我们在函数参数里加了一个Node*& root,正是这个参数,我们使用了引用,就不需要再判断其是左子树还是右子树,也不用记录其父节点,因为引用就是别名,我们对其进行修改就是对这个指针进行修改,这里还用了递归,让我们的代码量大大减小

public:bool InsertR(const K& key)      //函数放在public便于访问{return _InsertR(_root, key);}
private:                              //函数的具体实现放在privatebool _InsertR(Node*& root,const K& key)   //这里使用别名,修改的就是其本身,就不用判断左右节点了{if (root == nullptr){root=new Node(key);return true;}if (root->_key < key)                 //小于key就遍历右子树{return _InsertR(root->_right, key);}else if (root->_key > key)            //大于key就遍历左子树{return _InsertR(root->_left, key);  }else{return false;}}

📖优化版find函数

public:bool FindR(const K& key){return _FindR(_root, key);} 
private:bool _FindR(Node* root,const K& key){if (root == nullptr)return false;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}}

📖优化版Erase函数

public:bool EraseR(const K& key){return _EraseR(_root, key);}
private:bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;     //保存该节点便于删除if (root->_right == nullptr){root = root->_left;}else if (root->_left == nullptr){root = root->_right;}else{Node* rightMin = root->_right;while (rightMin->_left){rightMin = rightMin->_left;}swap(root->_key, rightMin->_key);return _EraseR(root->_right, key);}delete del;return true;}}

3.4 源代码BinarySearchTree.h

#pragma once#include<iostream>
using namespace std;
template<class K>
struct BSTreeNode
{typedef BSTreeNode<K> Node;Node* _left;Node* _right;K _key;BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}
};template<class K>
class BinarySearchTree
{typedef BSTreeNode<K> Node;
public://强制生成默认构造BinarySearchTree() = default;BinarySearchTree(const BinarySearchTree<K>& t)   //拷贝构造函数{_root = Copy(t._root);}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return new Root;}BinarySearchTree<K>& operator=(const BinarySearchTree<K>& t)    //赋值拷贝{swap(_root, t._root);return *this;}~BinarySearchTree()   //析构函数{Destroy(_root);}bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;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;}}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 (cur == parent->_right){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;return true;}else if (cur->_right == nullptr)  //右子树为空{if (cur == _root){_root = cur->_left;}else{if (cur == parent->_right){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;return true;}else                          //左右子树都不为空{// 替换法Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMin == rightMinParent->_left)     //根节点rightMinParent->_left = rightMin->_right;    elserightMinParent->_right = rightMin->_right;delete rightMin;return true;}}}return false;}void InOrder()       //巧妙调用输出函数{_InOrder(_root);cout << endl;}///bool FindR(const K& key){return _FindR(_root, key);} bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}
private:void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;     //保存该节点便于删除if (root->_right == nullptr){root = root->_left;}else if (root->_left == nullptr){root = root->_right;}else{Node* rightMin = root->_right;while (rightMin->_left){rightMin = rightMin->_left;}swap(root->_key, rightMin->_key);return _EraseR(root->_right, key);}delete del;return true;}}bool _InsertR(Node*& root,const K& key)   //这里使用别名,修改的就是其本身,就不用判断左右节点了{if (root == nullptr){root=new Node(key);return true;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{return false;}}bool _FindR(Node* root,const K& key){if (root == nullptr)return false;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}Node* _root = nullptr;
};

4. 二叉搜索树的应用

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

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

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

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

kv二叉树实现

namespace key_value
{template<class K, class V>struct BSTreeNode{typedef BSTreeNode<K, V> Node;Node* _left;Node* _right;K _key;V _value;BSTreeNode(const K& key, const V& value):_left(nullptr),_right(nullptr),_key(key),_value(value){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:bool Insert(const K& key, const V& value){if (_root == nullptr){_root = new Node(key, value);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, value);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* 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 cur;}}return nullptr;}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 (cur == parent->_right){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;return true;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (cur == parent->_right){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;return true;}else{// 替换法Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMin == rightMinParent->_left)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;return true;}}}return false;}void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}private:Node* _root = nullptr;};
}

5. 二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述

  1. 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
  2. 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插
入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上
场了。

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

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

相关文章

Etcd Raft 协议(进阶篇)

前言 在正式开始介绍 Raft 协议之间&#xff0c;我们有必要简单介绍一下其相关概念。在分布式系统中&#xff0c;一致性是比较常见的概念&#xff0c;所谓一致性指的是集群中的多个节点在状态上达成一致。在程序和操作系统不会崩溃、硬件不会损坏、服务器不会掉电、网络绝对可靠…

建设工程(2)

建设工程(2) 单选题&#xff08;共10题&#xff0c;每题3分&#xff09; 1、《安全韧性城市评价指南》&#xff08;GB/T 40947-2021&#xff09;指出&#xff0c;&#xff08;&#xff09;城市是在灾害环境中能够承受、适应和恢复的城市。 正确答案&#xff1a;B、安全韧性 2、…

JavaScript之Promise补充与Dom操作

Promise过程分析 //按照顺序依次引入a-d.js&#xff0c;最后打印加载完毕load(a.js).then(()>{return load(b.js)//load方法返回Pomise对象//但是没有把这个对象返回//所以这个函数没有返回值//then方法会提供一个空对象作为返回值},()>{console.log(有错误)}).then(()&g…

【Linux】环境变量常见指令操作&基本实验(入门必看!)

前言 大家好吖&#xff0c;欢迎来到 YY 滴Linux系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《…

安卓实现翻转时间显示效果

效果 废话不多说上代码 自定义组件 import android.content.Context; import android.content.res.TypedArray; import android.graphics.Camera; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.…

BM83 字符串变形

import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** * param s string字符串 * param n int整型 * return string字符串*/public String trans (String s, int n) {// write co…

RK3568驱动指南|第十三篇 输入子系统-第143章 多对多的匹配关系分析

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

一文全面了解 wxWidgets 的安装

目录 使用二进制文件 从源代码构建 获取源代码 选择构建系统 构建您的应用程序 Unix&#xff0c;命令行 Unix&#xff0c;使用GNU Make MSW&#xff0c;使用Microsoft Visual Studio Mac&#xff0c;使用Xcode 其他IDE wxWidgets的头文件和库必须可用&#xff0c;以便…

ubuntu20.04_PX4_1.13

说在前面&#xff1a;&#xff08;最好找一个干净的Ubuntu系统&#xff09;如果配置环境的过程中出现很多编译的错误或者依赖冲突&#xff0c;还是建议新建一个虚拟机&#xff0c;或者重装Ubuntu系统&#xff0c;这样会避免很多麻烦&#x1f490; &#xff0c; 安装PX4 1.13.2 …

小红书扫码登录分析与python实现

文章目录 1. 写在前面2. 接口分析3. 代码实现 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python…

变量的数据类型:基本数据类型和引用数据类型

数据分类 数据分为&#xff1a;静态数据、动态数据。 #静态数据 静态数据是指一些永久性的数据。一般是以文件的形式存储在硬盘上&#xff0c;比如文档、照片、视频等文件。 电脑关闭后&#xff0c;静态数据仍然还在。只要不主动删掉数据或者硬盘没损坏&#xff0c;这些数据…

SQL管理员高频面试问题

1.请你谈谈 MySQL 事务隔离级别&#xff0c;MySQL 的默认隔离级别是什么&#xff1f;为了达到事务的四大特性&#xff0c;数据库定义了 4 种不同的事务隔离级别&#xff1a; READ-UNCOMMITTED&#xff08;读取未提交&#xff09;&#xff1a;最低的隔离级别&#xff0c;允许脏…

代码随想录算法训练营第十七天(二叉树IV)| 110. 平衡二叉树、257. 二叉树的所有路径、404.左叶子之和(JAVA)

文章目录 110. 平衡二叉树解题思路源码 257. 二叉树的所有路径解题思路源码 404.左叶子之和解题思路源码 110. 平衡二叉树 给定一个二叉树&#xff0c;判断它是否是平衡二叉树 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7]输出&#xff1a;true 示例…

伊理威科技:抖音开网店新手刚做选啥品

在数字浪潮中&#xff0c;抖音不仅是展示才艺的舞台&#xff0c;更是创业者的新天地。新手若想在这片热土上开垦网店&#xff0c;选品便是首要课题。选择产品如同种下希望的种子&#xff0c;既要考量土壤肥沃度&#xff0c;也得预测风雨适宜期。 兴趣与专长是选品的罗盘。热爱所…

鸿蒙Harmony应用开发—ArkTS(@Extend装饰器:定义扩展组件样式)

在前文的示例中&#xff0c;可以使用Styles用于样式的扩展&#xff0c;在Styles的基础上&#xff0c;我们提供了Extend&#xff0c;用于扩展原生组件样式。 说明&#xff1a; 从API version 9开始&#xff0c;该装饰器支持在ArkTS卡片中使用。 装饰器使用说明 语法 Extend(UI…

光速论文能用吗 #媒体#知识分享#学习方法

光速论文是一个非常有效的论文写作、查重降重工具&#xff0c;它的使用非常简单方便&#xff0c;而且功能强大&#xff0c;是每个写作者必备的利器。 首先&#xff0c;光速论文具有强大的查重降重功能&#xff0c;能够快速检测论文中的抄袭部分&#xff0c;帮助作者避免不必要的…

面试算法-80-字符串相乘

题目 给定两个以字符串形式表示的非负整数 num1 和 num2&#xff0c;返回 num1 和 num2 的乘积&#xff0c;它们的乘积也表示为字符串形式。 注意&#xff1a;不能使用任何内置的 BigInteger 库或直接将输入转换为整数。 示例 1: 输入: num1 “2”, num2 “3” 输出: “6…

集合(set)篇(一)

文章目录 5.1 集合的创建5.2 交集5.3 ^ 对称差集5.4 - 差集5.5 并集5.6 < 判断当前集合是否为另一个集合的子集5.7 5.8 > 判断当前集合是否为另一个集合的超集 5.1 集合的创建 在Python中&#xff0c;集合&#xff08;set&#xff09;是一个无序的不重复元素序列。你可以…

用BSP优化3D渲染

3D渲染引擎设计者面临的最大问题之一是可见性计算&#xff1a;只必须绘制可见的墙壁和物体&#xff0c;并且必须以正确的顺序绘制它们&#xff08;应该在远处的墙壁前面绘制近墙&#xff09; 。 更重要的是&#xff0c;对于游戏等应用程序来说&#xff0c;开发能够快速渲染场景…

Gin简介(Go web基础知识)

Gin简介 https://geektutu.com/post/quick-go-gin.html我是从这个网站上面摘录的&#xff0c;就是做个笔记&#xff0c;仅分享。膜拜极客兔兔大佬 Go特性&#xff1a; 快速&#xff1a;路由不使用反射&#xff0c;基于Radix树&#xff0c;内存占用少。 中间件&#xff1a;HT…