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

【C++要笑着学】搜索二叉树 (SBTree) | K 模型 | KV 模型

   C++ 表情包趣味教程 👉 《C++要笑着学》

💭 写在前面半年没更 C++ 专栏了,上一次更新还是去年九月份,被朋友催更很久了hhh

本章倒回数据结构专栏去讲解搜索二叉树,主要原因是讲解 map 和 set 的特性需要二叉搜索树做铺垫,理解搜索二叉树有助于更好地理解 map 和 set 的特性。第二个原因是为了后期讲解查找效率极高的平衡搜索二叉树,随后再讲完红黑树,我们就可以模拟实现 map 和 set 了。二叉树的基础知识讲解在我的《树锯结构》专栏中有写,这里放上链接方便复习。

🔗 复习链接:【数据结构】二叉树的遍历


Ⅰ. 搜索二叉树(SearchBinaryTree)

0x00 搜索二叉树的概念

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

  • 若其左子树不是空,则左子树上所有节点的值都小于根结点的值
  • 若其右子树不是空,则右子树上所有结点的值都大于根结点的值
  • 其左右子树必须都是二叉搜索树

  

至于叫它 "搜索二叉树",还是 "二叉搜索树",这个似乎也没有特别的规定,应该都是可以的。

但我更喜欢叫它 "搜索二叉树",因为这样翻译过来是 "Search Binary Tree",

 但是我并不是因为这样可以叫它 SB 树才喜欢的。

(而且这样也不文明,而且已经有 SB 树了,Size Balanced Tree)

而是因为  叫 "搜索二叉树 Search Binary Tree" 可以顺便记住它的性质:

Search Binary TreeS 也可以表示 SmallB 也可以表示 Big,SB 即左边小右边大!

(一般而言,搜索二叉树都是左边小右边大的)

 这样可以正好能对应它的性质:左子树的值 < 根 < 右子树的值

🔺 结论:任意一个子树都需要满足,左子树的值 < 根 < 右子树的值,才能构成二叉搜索树。 

0x01 搜索二叉树的优势

既然叫搜索二叉树,它肯定是用来搜索的,当满足搜索二叉树时你将可以快速地查找任意的值。

💭 举个例子: 查找 7

放到以前我们如果不用二分查找,可能会选择用暴力的方式去从头到尾遍历一遍。

但现在学了搜索二叉树,我们就可以轻松找到这个 7 了,不信你看:

STEP1:7 比 8 (根节点) 小,根据搜索二叉树的性质,它必然不会出现在右子树 (右边大) !

 所以,直接 🔒 锁定 左子树开始找:

STEP2: 7 比 3 大,根据性质它肯定不会出现在左子树 (左边小) !

 这次,直接 🔒锁定 右子树继续找: 

STEP3: 我们继续对比,7 比 6 大,所以在右边,就这么轻轻松松的找到了:

 搜索二叉树查找一个值的最坏情况,也只是查找高度次。 你就说爽不爽吧。

这就是搜索二叉树,它的搜索功能是真的名副其实的厉害!

0x02 二叉搜索树的时间复杂度:O(N)

上面的例子举得太丝滑了,会让人误以为搜索二叉树的时间复杂度是 O(logN) ……

但实际上是 O(N)  !!!

因为这棵树是有可能会 蜕化 的,极端情况下会蜕化成一个 "单边树" :

  

😢 最差情况:二叉搜索树退化为单边树(或类似单边),其平均比较次数为:

O(\frac{N}{2})\Rightarrow O(N)

 但是在好的情况下,其搜索效率也是非常可观的:

😍 最优情况:二叉搜索树为完全二叉树(或接近完全二叉树),其平均比较次数为:

O(log_2N)\Rightarrow O(logN)

  对于时间复杂度的分析我们要做一个悲观主义者,根据最差情况去定时间复杂度。

🔺 总结:搜索二叉树的时间复杂度为 O(N)

0x03 搜索二叉树的改良方案

 如果搜索二叉树蜕化成了单边树,其性能也就失去了,能否进行改进让它保持性能?

如何做到不论按照上面次序插入关键码,二叉搜索树的性能均能达到最优?

搜索二叉树由于控制不了极端情况,与 O(logN) 失之交臂,但平衡二叉搜索树做到了。

"平衡二叉树的搜索效率极高"

严格意义上来说满二叉树才是 O(logN),完全二叉树是接近 O(logN) 。

而平衡搜索二叉树维持左右两边均匀程度,让它接近完全二叉树,从而让效率趋近 O(logN)

Ⅱ. 搜索二叉树的实现

0x00 搜索二叉树的定义

 搜索二叉树,SearchBinaryTree 名称实在是又臭又长!

我们不如取名为 SBTree,但是 SBTree 听起来好像有点不文明,我们还是叫 BSTree 吧。

这里我们用模板,模板参数我们给了一个 K,表示 key 的意思(模板参数并非一定要用 T)

template<class K> 
struct BSTreeNode {BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key): _left(nullptr), _right(nullptr), _key(key) {}
};

下面我们来定义整个树,BSTreeNode 也有些长了,我们不如将其 typedef Node 

这里我们构造函数都没必要写,它自己生成的就够用了:

template<class K> 
class BSTree {typedef BSTreeNode<K> Node;
private:Node* _root = nullptr;    // 懒得写构造函数了
};

0x01 搜索二叉树的插入

 我们先来实现最简单的插入操作:

  • 如果树为空,则直接新增结点,赋值给 root 指针。
  • 如果树不为空,按二叉搜索树性质查找插入位置,插入新节点。
bool Insert(const K& key);

Insert 的实现我们可以用递归,也可以用非递归,这一块递归比非递归更难理解。

秉着先难后易的态度,我们先讲比较难理解的非递归版本!

💡 分析

Step1:首先检查是否有根结点 _root,如果没有我们就 new 一个结点出来作为根结点。
此时插入成功,返回 true

Step2:插入就需要找到插入位置,我们定义一个 cur 变量,从根节点开始,
根据搜索二叉树 "SB" 性质,将 cur 结点的值与插入的值 x 进行大小比较。

  • 如果插入的值大于当前结点值,则将 cur 结点向右移动 cur=cur->_right ;
  • 如果插入的值小于当前节点值,就将 cur 结点向左移动 cur=cur->_left

值得注意的是,我们还需要额外记录一下 cur 的父结点,因为你不知道什么时候会碰 \textrm{null} 结束。
并且当我们找到插入位置后,仅仅 new 上一个新结点给 cur 是完成不了插入操作的!
因为直接这么做 cur 也只是一个局部变量而已,你需要 cur 跟上一层(cur 的父亲)相链接才行!
为了能找到上一层,所以我们还需要额外定义一个 prev 变量来记录 cur 的父结点,
在我们更换 cur 结点时记录父结点的位置 prev=cur 即可。
当然了,还有一种插入失败的情况,就是判断大小时出现等于的情况,返回 false 即可。
(重复的值是不允许插入的,默认情况是不允许冗余的!但是也有针对这个的变形,后续再说)

Step3:插入!new 一个新结点给 cur,此时 cur 只是一个局部变量,必须要和父亲链接,
此时应该链接父亲的左边,还是链接父亲的右边?我们不知道,所以我们需要再做一个比较!

  • 如果父节点的值大于插入的值,则将 cur 链接到父亲左边 prev->_left=cur
  • 反之将 cur 链接到父亲右边  prev->_right=cur

最后,插入成功返回 true,我们的插入操作就大功告成了。

💬 代码演示:Insert 接口的实现

/* 插入 */
bool Insert(const K& x) {/* 检查是否由根节点 */if (_root == nullptr) {    // 如果根节点为空指针_root = new Node(x);   // 创建一个新结点作为根结点return true;           // 插入成功,返回真}Node* prev = nullptr;      // 用于记录cur的父亲Node* cur = _root;         // 从根节点开始/* 找到插入位置 */while (cur != nullptr) {if (x > cur->_key) {          // 如果插入的值大于当前结点值,则向右移动prev = cur;               // 保存父节点cur = cur->_right;        // 令cur右滑}else if (x < cur->_key) {     // 如果插入的值小于当前结点值,则向左移动prev = cur;      cur = cur->_left;         // 令cur左滑}else {                        // 相等的情况,禁插return false;             // 插入失败,返回假}}/* 插入位置已找到,准备进行链接操作 */cur = new Node(x);      // 创建一个新结点,赋给cur,此时cur为局部,需与父结点链接if (prev->_key > x) {   // 如果父结点的值大于插入的值,则将cur链接到左边prev->_left = cur;} else {                  // 如果父节点的值小于插入的值,则将cur链接到右边prev->_right = cur;}return true;   // 插入成功,返回真
}

再写一个中序遍历来测试一下插入的效果:

void InOrder(Node* root) {if (root == nullptr) {return;}InOrder(root->_left);            // 左cout << root->_key << " ";       // 值InOrder(root->_right);           // 右
}

模拟出一个测试用例:

void TestBSTree() {BSTree<int> t;int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };for (auto e : a) {t.Insert(e);}t.InOrder();  ❌ 没法传根
}

此时会出现一个问题,因为根是私有的,我们没办法把根传过去。

此时我们可以选择在类内部写一个成员函数 GetRoot 去取根,但是这里我们可以选择这么做:

	void InOrder() {_InOrder(_root);}private:// 改为内部函数void _InOrder(Node* root) {if (root == nullptr) {return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}Node* _root = nullptr;
};

干脆将刚才我们实现的中序设为 private 私有,然后再写一个 InOrder 放在公有的区域。

这就是在类内访问 _root 了,没有什么问题。

如此一来我们在类外就可以直接调用 InOrder,并且也不需要传递参数了。

int main(void)
{TestBSTree();return 0;
}

🚩 运行结果:

0x02 搜索二叉树的查找

 find 实现很容易,用和刚才一样的思路,从根结点开始查找。 

从根开始,如果要查找的值大于 cur 目前的值,则让 cur 往右走,反之往左走。

当查找得值与 cur 的值相等时则说明找到了,返回 true

cur 触及到空(while 循环结束)则说明找不到,返回 false

💬 代码实现:搜索二叉树的查找

/* 查找 */
bool Find(const K& target) {Node* cur = _root;                    // 从根结点开始查找while (cur != nullptr) {      if (target > cur->_key) {         // 如果目标值比当前结点值大,cur↘cur = cur->_right;       }else if (target < cur->_key) {    // 如果目标值比当前结点值小,cur↙cur = cur->_left;}else {                            // 如果目标值等于结点值,说明找到了/* 找到了,返回真 */return true;}}/* 没找到,返回假 */return false;
}

0x03 搜索二叉树删除

搜索二叉树真正困难的是删除,搜索二叉树删除的实现是有很有难度的。

删除的实现就需要一些 "奇技淫巧" 了,断然删除会毁树。

没有孩子或者只有一个孩子,可以直接删除,孩子托管给父亲。

两个还是没办法给父亲,父亲养不了这么多孩子,但是可以找个人替代父亲养孩子。

当然,也不能随便找,找的人必须仍然维持搜索二叉树的性质,这是原则。

"你不能说搞得我都不是搜索二叉树了,那还玩个锤子"

必须比左边的大,比右边的小。所以在家族中找是最合适的。

找左子树的最大值结点,或者右子树的最小值结点。

首先要查找元素是否在二叉搜索树中,如果不存在,则返回。

如果存在,那么删除的结点可能分为下面四种情况:

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

看起来有待删除节点有 4 种情况,但实际上 a 和 b,或 a 和 c 可以合并。

📌 因此,真正的删除过程如下:

  • 情况B:删除该结点且使被删除结点的父结点指向被删除节点的左孩子结点 —— 直接删除。
  • 情况C:删除该节点且使被删除结点的父节点指向被删除节点的右孩子结点 —— 直接删除。
  • 情况D:在它的右子树中寻找中序下的第一个结点(值最小),用它的值填补到被删除结点中,再来处理该结点的删除问题 —— 替换法删除。

① 该结点无左孩子

如果要删除下面这颗二叉树的 10 节点和 4 节点:

我们还是定义一个 cur 变量,当 cur 找到 10 结点后,如果左侧为空情况如下:

  1. 若该结点为 root,直接让 root 等于它的右孩子结点。
  2. 对于删除 10 结点:若 cur==father->right,则令 parent->right = cur->right (如图所示)
  3. 对于删除 4 结点:若 cur==father->left,则令 parent->left=cur->right (如图所示)
  4. 最后删除 cur 结点

💬 代码演示:

if (cur->_left == nullptr) {/* 判断要删除的结点是否为根结点 */if (cur == _root) {_root = cur->_right;}else {if (cur == father->_right) {/* 如果 cur 比 father 大 */father->_right = cur->_right;}else {father->_left = cur->_right;}}delete cur;cur = nullptr;
}

② 该结点无右孩子

 如果要删除 14 结点,删除逻辑和删除左孩子是类似的:

💬 代码演示:

else if (cur->_right == nullptr) {/* 判断是否为根结点 */if (cur == _root) {_root = cur->_left;}else {if (cur == father->_right) {/* cur 比父结点大 */father->_right = cur->_left;}else {father->_left = cur->_left;}}delete cur;cur = nullptr;
}

③ 该结点有左右两个孩子

如果删除的结点有左右两个孩子,我们就在它的右子树中寻找中序的第一个结点。

与右子树的最小值进行替换,当然也可以选择左子树的最大值进行替换。

💭 例子:比如下面这颗子树,我们要删除 3 结点:

如果该结点有两个孩子,则采用如下替换法:

该结点和右子树的最小值或左子树的最大值进行值的替换,然后删除替换后的结点。

这里我们采用与右子树的最小值进行替换。

💬 代码演示:非递归版本的 Erase

bool Erase(const K& key) {Node* father = nullptr;Node* cur = _root;while (cur != nullptr) {if (cur->_key < key) {father = cur;cur = cur->_right;}else if (cur->_key > key){father = cur;cur = cur->_left;}else {/* 找到了! 情况一:该节点没有左孩子   情况二:该节点没有右孩子 */if (cur->_left == nullptr) {/* 判断是否为根结点 */if (cur == _root) {_root = cur->_right;}else {if (cur == father->_right) {//cur 比 father大father->_right = cur->_right;}else {father->_left = cur->_right;}}delete cur;cur = nullptr;}else if (cur->_right == nullptr) {/* 判断是否为根结点 */if (cur == _root) {_root = cur->_left;}else {if (cur == father->_right) {/* 如果 cur 比父结点大 */father->_right = cur->_left;}else {father->_left = cur->_left;}}delete cur;cur = nullptr;}else {/* 有两个节点,替换 */Node* MinParNode = cur;Node* MinNode = cur->_right;while (MinNode->_left) {MinParNode = MinNode;MinNode = MinNode->_left;}swap(cur->_key, MinNode->_key);if(MinParNode->_left == MinNode) {MinParNode->_left = MinNode->_right;}else {MinParNode->_right = MinNode->_right;}delete MinNode;MinNode = nullptr;}return true;}}return false;
}

💡 解读:找到 3 结点中右子树的最小结点,替换它们的值。定义 MinParNode 为 cur,MinNode 为 cur 的右节点。首先让 MinNode 指向 3 的右孩子(1),然后一直向左边找知道找到 nullptr 为止,此时 MinNode 指向的就是最小的结点了,此时让 3 与 MinNode 的值交换即可。

交换后,删除 3 就变成了删除 MinNode,我们需要弄清 MinNode  和 MinParNode 的指向关系:

  • 如果 MinParNode 的左孩子是 MinNode,则让 MinParNode 的左指向 MinNode 的右。
  • 如果 MinParNode 的右孩子是 MinNode,则让 MinParNode 的右指向 MinNode 的右。(这里让 MinParNode 指向 MinNode 的右的原因是 MinNode 已是最小结点,不可能有左孩子了)

Ⅲ. 搜索二叉树的应用

0x00 K 模型

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

💭 举个例子:对于单词 word,我们需要判断该单词是否拼写正确

  • 以单词集合中的每个单词作为 key,构建一个搜索二叉树。
  • 在二叉树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

0x01 KV 模型

KV 模型,每一个关键码 key,都有与之对应的值 Value,即 <Key, Value> 的键值对。

这就像 Python 中的 dict 字典类型一样,key 和 value 对应。

这在生活中也是非常常见的,比如英汉词典就是英文与中文的对应关系,通过英文可以快读检索到对应的中文,英文单词也可以与其对应的中文构建出一种键值对:

<word, chinese>

再比如统计单词次数,统计成功后,给定的单词就课快速找到其出现的次数,单词与其出现的次数就构建出了一种键值对:

<word, count>

💬 代码演示:我们实现一个简单的英汉词典 dict,可以通过英文找到对应的中文。

具体实现方式如下:

  • <单词, 中文含义>  以键值对构造搜索二叉树,值得注意的是,搜索二叉树需要比较,键值对比较时只比较 Key。
  • 查询英文单词时,只需给出英文单词,就可以快速检索到对应的 Key
namespace KV
{template<class K, class V>struct BSTreeNode{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;V _value;//pair<K, V> _kv;BSTreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key), _value(value){}};template<class K, class V>struct BSTree{typedef BSTreeNode<K, V> Node;public:BSTree():_root(nullptr){}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 (parent == nullptr){_root = cur->_right;}else{if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;}else if (cur->_right == nullptr){if (parent == nullptr){_root = cur->_left;}else{if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;}else{Node* minParent = cur;Node* min = cur->_right;while (min->_left){minParent = min;min = min->_left;}cur->_key = min->_key;cur->_value = min->_value;if (minParent->_left == min)minParent->_left = min->_right;elseminParent->_right = min->_right;delete min;}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 << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root;};void TestBSTree1(){// 字典KV模型BSTree<string, string> dict;dict.Insert("sort", "排序");dict.Insert("left", "左边");dict.Insert("right", "右边");dict.Insert("map", "地图、映射");//...string str;while (cin>>str){BSTreeNode<string, string>* ret = dict.Find(str);if (ret){cout << "对应中文解释:" << ret->_value << endl;}else{cout << "无此单词" << endl;}}}void TestBSTree2(){// 统计水果出现次数string arr[] = { "苹果", "西瓜","草莓", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };BSTree<string, int> countTree;for (auto& str : arr){//BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret != nullptr){ret->_value++;}else{countTree.Insert(str, 1);}}countTree.InOrder();}
}


📌 [ 笔者 ]   王亦优
📃 [ 更新 ]   2023.4.8
❌ [ 勘误 ]   /* 暂无 */
📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!

📜 参考资料 

C++reference[EB/OL]. []. http://www.cplusplus.com/reference/.

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

百度百科[EB/OL]. []. https://baike.baidu.com/.

比特科技. C++[EB/OL]. 2021[2021.8.31]. 

相关文章:

【C++要笑着学】搜索二叉树 (SBTree) | K 模型 | KV 模型

C 表情包趣味教程 &#x1f449; 《C要笑着学》 &#x1f4ad; 写在前面&#xff1a;半年没更 C 专栏了&#xff0c;上一次更新还是去年九月份&#xff0c;被朋友催更很久了hhh 本章倒回数据结构专栏去讲解搜索二叉树&#xff0c;主要原因是讲解 map 和 set 的特性需要二叉搜索…...

微信小程序开发 | 小程序开发框架

小程序开发框架7.1 小程序模块化开发7.1.1 模块7.1.2 模板7.1.3 自定义组件7.1.4插件7.2 小程序基础样式库—WeUI7.2.1 初识WeUI7.2.2【案例】电影信息展示7.3 使用vue.js开发小程序7.3.1 初识mpvue7.3.2 开发工具7.3.3 项目结构7.3.4【案例】计数器7.4 小程序组件化开发框架7.…...

气候系统设计

基础概念 一个星球&#xff08;例如地球&#xff09;的气候系统主要是一些基本参数基于公转周期&#xff08;年&#xff09;和自转周期&#xff08;日&#xff09;的变化&#xff0c;这其中会有两个变化因素&#xff1a;地理位置&#xff08;经纬度&#xff09;和天气变化&…...

如何使用Thymeleaf给web项目中的网页渲染显示动态数据?

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 服务器软件&#xff1a;apache-tomcat-8.5.27 目录一. 什么是Thymeleaf&#xff1f;二. MVC2.1 为什么需要MVC&#xff1f;2.2 MVC是什么&#xff1f;2.3 MVC和三层架构之间的关系及工…...

01 | 电机常用语

1 电机常用术语 1.1 原点 原点是指步进电机在驱动直线运动机构时的起始点。 1.2 点动 点动是电动机控制方式中的一种。 点动由于在这一控制回路中没有自保,也没有并接其它的自动装置,只是按下控制回路的启动按钮,主回路才通电,松开启动按钮,主回路就没电了。最典型的是…...

Leetcode.2601 质数减法运算

题目链接 Leetcode.2601 质数减法运算 Rating &#xff1a; 1779 题目描述 给你一个下标从 0 开始的整数数组 nums&#xff0c;数组长度为 n 。 你可以执行无限次下述运算&#xff1a; 选择一个之前未选过的下标 i &#xff0c;并选择一个 严格小于 nums[i]的质数 ppp &…...

DP7416国产192K数字音频接收芯片兼容替代CS8416

目录192K 数字音频应用DP7416简介芯片特性192K 数字音频应用 采样率192khz&#xff0c;能将192,000hz以下的频率都录下来&#xff0c;而且对声波每秒连续采样192,000次。在回放的时候&#xff0c;这192,000个采样点按顺序播放&#xff0c;从而还原原来的声音。   过采样技术除…...

全球土壤湿度数据获取方法

土壤湿度亦称土壤含水率&#xff0c;表示土壤干湿程度的物理量。是土壤含水量的一种相对变量。通常用土壤含水量占干土重的百分数是示&#xff0c;亦称土壤质量湿度&#xff0c;如用土壤水分容积占土壤总容积的百分数表示&#xff0c;则称土壤容积湿度。通常说的土壤湿度&#…...

在proteus中仿真arduino实现矩阵键盘程序

矩阵键盘是可以解决我们端口缺乏的问题&#xff0c;当然&#xff0c;如果我们使用芯片来实现矩阵键盘的输入端口缺乏的问题将更加划算了&#xff0c;本文暂时不使用芯片来解决问题&#xff0c;而使用纯朴的8根线来实现矩阵键盘&#xff0c;目的是使初学者掌握原理。想了解使用芯…...

【ROS2指南-5】理解ROS2服务

目标&#xff1a;使用命令行工具了解 ROS 2 中的服务。 教程级别&#xff1a;初学者 时间&#xff1a; 10分钟 内容 背景 先决条件 任务 1 设置 2 ros2服务列表 3 ros2服务类型 4 ros2 服务查找 5 ros2界面展示 6 ros2 服务调用 概括 下一步 相关内容 背景 服务是 …...

探索Apache Hudi核心概念 (3) - Compaction

Compaction是MOR表的一项核心机制&#xff0c;Hudi利用Compaction将MOR表产生的Log File合并到新的Base File中。本文我们会通过Notebook介绍并演示Compaction的运行机制&#xff0c;帮助您理解其工作原理和相关配置。 1. 运行 Notebook 本文使用的Notebook是&#xff1a;《A…...

100Wqps异地多活,得物是怎么架构的?

说在前面 在40岁老架构师尼恩的数千读者群中&#xff0c;一直在指导大家简历和职业升级&#xff0c;前几天&#xff0c;指导了一个华为老伙伴的简历&#xff0c;小伙伴的优势在异地多活&#xff0c;但是在简历指导的过程中&#xff0c;尼恩发现&#xff1a; 异地多活的概念、异…...

35岁的测试工程师被公司强行辞退,感叹道:我以前就该好好努力了

曾经的高薪软件测试工程师&#xff0c;今年35岁了&#xff0c;被公司劝退了&#xff0c;外卖跑到凌晨&#xff0c;很累&#xff0c;但还是有一种想诉说的冲动。哪怕让大家觉得已经说得太多了&#xff0c;烦了&#xff0c;都成祥林嫂了&#xff0c;但是&#xff0c;我是真的想说…...

ASP.NET动态Web开发技术第5章

第5章数据验证一.预习笔记 1.验证控件概述&#xff1a; 2.RequiredFieldValidator&#xff08;必填验证&#xff09; 常用属性1&#xff1a;ControlToValidator:被验证的输入控件的ID 常用属性2&#xff1a;Text&#xff1a;验证失败时&#xff0c;验证控件显示的文本 常用…...

【数据结构与算法篇】时间复杂度与空间复杂度

目录 一、数据结构和算法 1.什么是数据结构&#xff1f; 2.什么是算法&#xff1f; 3.数据结构和算法的重要性 二、算法的时间复杂度和空间复杂度 1.算法效率 2.算法的复杂度 3.复杂度在校招中的考察 4.时间复杂度 5.空间复杂度 6.常见复杂度对比 7.复杂度的OJ练…...

HTTP API接口设计规范

1. 所有请求使用POST方法 使用post&#xff0c;相对于get的query string&#xff0c;可以支持复杂类型的请求参数。例如日常项目中碰到get请求参数为数组类型的情况。 便于对请求和响应统一做签名、加密、日志等处理 2. URL规则 URL中只能含有英文&#xff0c;使用英文单词或…...

数据一致性校验(pt-table-checksum)

介绍 pt-table-checksum 和 pt-table-sync 是 percona 公司发布的、检查 MySQL 主从数据库数据一致性校验的工具。pt-table-checksum 利用 MySQL 复制原理&#xff0c;在主库执行校验和计算&#xff0c;并对比主从库校验和&#xff0c;由此判断主从库数据是否一致。如果发现数…...

Talk预告 | 新加坡国立大学郑奘巍 AAAI‘23 杰出论文:大批量学习算法加速推荐系统训练

本期为TechBeat人工智能社区第486期线上Talk&#xff01; 北京时间3月30日(周四)20:00&#xff0c;新加坡国立大学二年级博士生——郑奘巍的Talk将准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “大批量学习算法加速推荐系统训练”&#xff0c;届时将分…...

肖 sir_就业课__004项目流程(H模型)

项目流程&#xff1a; 一、面试提问&#xff08;h模型&#xff09; 1、你说下你们公司测试流程&#xff1f; 2、给你一个需求你会怎么做? 3、你讲下你的工作&#xff1f; 4、谈谈你是如何去测试&#xff1f; 答案&#xff1a;h模型 要求第一人称来写 讲解简化文字流程&#x…...

snipaste 截图工具——可以使图片悬浮在任何软件上,方便对比

一、下载 官网下载地址&#xff1a;Snipaste Downloads &#xff08;需要梯子&#xff09; CSDN下载地址&#xff1a;https://download.csdn.net/download/weixin_43042683/87671809 1. 下载 压缩包后&#xff0c;免安装&#xff0c;直接解压后既可以使用。 2. 点击Snipaste.…...

Docker 快速部署Springboot项目

编写Dockerfile文件 # Docker image for springboot file run # VERSION 0.0.1 # Author: # 基础镜像使用java FROM openjdk:8 # 作者 MAINTAINER laihx # VOLUME 指定了临时文件目录为/tmp。 # 其效果是在主机 /var/lib/docker 目录下创建了一个临时文件&#xff0c;并链接到…...

【LeetCode: 剑指 Offer II 112. 最长递增路径 | 递归 | DFS | 深度优先遍历 | 记忆化缓存表】

&#x1f34e;作者简介&#xff1a;硕风和炜&#xff0c;CSDN-Java领域新星创作者&#x1f3c6;&#xff0c;保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文|经验分享|好用的网站工具分享&#x1f48e;&#x1f48e;&#x1f48e; &#x1f34e;座右…...

hive 入门 一般用于正式环境 修改元数据(二)

安装配置可参考 https://blog.csdn.net/weixin_43205308/article/details/130020674 1、如果启动过derby&#xff0c;最小初始化过 在安装路径下删除 derby.log metastore_db rm -rf derby.log metastore_db此处省略安装mysql数据库 2、配置MySQL 登录mysql mysql -uroot …...

在RedHat系统上使用firewall-cmd命令可以将端口打开

在RedHat系统上使用firewall-cmd命令可以将端口打开&#xff0c;具体操作如下&#xff1a; 首先&#xff0c;检查当前系统使用的防火墙服务&#xff0c;比如firewalld或iptables&#xff0c;使用以下命令&#xff1a; systemctl status firewalld # 检查firewalld服务 system…...

分享(五):免费可用的多种类 API 大全集合整理

前言 搜罗了各大平台整理了一波免费可以用的 API &#xff0c;有需要的收藏起来啦。 实名认证 运营商二要素 API &#xff1a;运营商校验此姓名、手机号码是否一致。 运营商三要素 API&#xff1a;运营商验证姓名、身份证号码、手机号码是否一致&#xff0c;返回验证结果称…...

8.1 假设验证的基本概念

学习目标&#xff1a; 要学习假设检验的基本概念&#xff0c;我会按照以下步骤进行&#xff1a; 了解假设检验的基本概念&#xff1a;假设检验是一种统计推断方法&#xff0c;用于判断某个假设是否成立。一般来说&#xff0c;假设检验包括原假设和备择假设两个假设&#xff0c…...

C语言基础

为了学习数据结构&#xff0c;整理一篇基础的C语言入门知识&#xff08;仅供自身学习用&#xff09; 条件运算符 语法&#xff1a;exp1 ? exp2 : exp3; exp1是条件表达式&#xff0c;如果结果为真&#xff0c;返回exp2 如果结果为假&#xff0c;返回exp3 if (a > b)max …...

Docker教程:如何将Helix QAC创建为一个容器并运行?

在这个Docker教程中&#xff0c;你将了解到如何将Helix QAC创建为一个容器化的镜像并运行。 Docker的基本定义是一个开源且流行的操作系统级虚拟化&#xff08;通常称为“容器化”&#xff09;技术&#xff0c;它是轻量级且可移植的&#xff0c;主要在Linux和Windows上运行。D…...

1676_MIT 6.828 xv6中的CPU alarm_资料翻译整理

全部学习汇总&#xff1a; GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 我觉得看了几个MIT的课程之后让我觉得我的大学四年有点浪费时光&#xff0c;看起来MIT的课程的确是很有饱满度。 这里&#xff0c;再整理一份课程中的作业要求。 …...

记一次内存泄漏问题的排查

阶段一&#xff1a; 前段时间&#xff0c;突然发现服务在毫无征兆的情况下发生了重启。去看了一下容器退出的日志&#xff0c;发现内存利用率超过了100%&#xff0c;导致容器重启&#xff0c;进一步看了skyWalking&#xff0c;发现heap内存超了&#xff0c;当时只是简单的以为是…...