【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.mzph.cn/news/76416.shtml

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

相关文章

Vector底层原理——面试之我答

Vector概述 vector是STL中最常用的容器&#xff0c;vector主要功能是作动态数组来弥补传统数组的缺点&#xff0c;如&#xff1a;不灵活&#xff0c;不方便插入等等。 Vector支持随机访问&#xff0c;因此访问某一个元素的时间复杂度是O(1)。 vector中存储着许多易用的函数方法…

C++与OPENCV实战学习

基于双目视觉的三维重建C实战_基于视觉sfmmvs的三维重建程序c源码(带gui界面)_新缸中之脑的博客-CSDN博客

逆市而行:如何在市场恐慌时保持冷静并抓住机会?

市场中的恐慌和波动是投资者所不可避免的。当市场出现恐慌情绪时&#xff0c;很多投资者会盲目跟从大众&#xff0c;导致决策出现错误。然而&#xff0c;聪明的投资者懂得在恐慌中保持冷静&#xff0c;并将其视为抓住机会的时机。本文将分享一些在市场恐慌时保持冷静并抓住机会…

Azure + React + ASP.NET Core 项目笔记一:项目环境搭建(二)

有意义的标题 pnpm 安装umi4 脚手架搭建打包语句变更Visual Studio调试Azure 设置变更发布 pnpm 安装 参考官网&#xff0c;或者直接使用npm安装 npm install -g pnpmumi4 脚手架搭建 我这里用的umi4&#xff0c;官网已附上 这里需要把clientapp清空&#xff0c;之后 cd Cl…

游戏性能优化

Unity性能优化主要包括以下方面&#xff1a; 1.渲染性能 。包括减少Draw Calls、减少三角面数、使用LOD、使用批处理技术、减少实时光源等&#xff0c;以提高游戏的帧率和渲染效率。 2.内存性能 。包括使用对象池、使用合适的纹理、使用异步加载资源等&#xff0c;以减少内存占…

【JAVA】String类

作者主页&#xff1a;paper jie_的博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JAVASE语法系列》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和…

基于SSM的高校共享单车管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

Redis原理:IntSet

&#xff08;笔记总结自b站黑马程序员课程&#xff09; 一、结构 IntSet是Redis中set集合的一种实现方式&#xff0c;基于整数数组来实现&#xff0c;并且具备长度可变、有序等特征。 结构如下&#xff1a; typedef struct intset {uint32_t encoding; //编码方式uint32_t l…

读取XML的几种方式

一、为什么使用XML 1、便于不同应用程序之间通信。 2、便于不同平台之间通信。 3、便于不同平台之间数据共享。 二、Dom读取 xml文件内容 <?xml version"1.0" encoding"UTF-8"?> <bookstore><book id"1"><name>冰…

归并排序-面试例子

小数和问题 描述 在一个数组中&#xff0c;一个数左边比它小的数的总和&#xff0c;叫数的小和&#xff0c;所有数的小和累加起来&#xff0c;叫数组小和。求数组小和。 例子 5 2 6 1 7 小和原始的求法是&#xff1a;任何一个数左边比它小的数累加起来。 5左边比它小数累加…

面试系列 - Redis持久化机制详解

目录 一、Redis 持久化机制 二、混合使用 RDB 和 AOF 三、 RDB(Redis DataBase)详解 四、AOF&#xff08;Append-Only File&#xff09;详解 Redis 是一个内存数据库&#xff0c;为了持久化数据以确保数据不会在服务器重启时丢失&#xff0c;Redis 提供了两种主要的持久化机…

执行 JUnit 单元测试前,修改环境变量

同一份代码&#xff0c;在不改变配置文件的情况下&#xff0c;可以连接不同的数据库&#xff0c;进行JUnit测试。 非开发、测试、生产环境的区别。而是 我就站在这里&#xff0c;指哪打哪&#xff01; 避免重复造轮子&#xff0c;参考博文&#xff1a; 使用junit&spri…

python main 函数-启动-传递参数 python 打包 exe C# 进程传参

Part1:Python main 传递参数 在Python编程中&#xff0c;我们经常需要从命令行或其他外部环境中获取参数。Python提供了一种简单而灵活的方式来处理这些参数&#xff0c;即通过main函数传参 1.python main 函数-启动-传递参数 test.py import sysdef main():# 获取命令行参…

QT 自定义信号

自定义信号&#xff0c;需要 1.在singnals:区域下写信号函数&#xff0c;以及函数对应的参数 2. 需要emit关键字进行发射信号 3. 在需要处理该信号的其他类中&#xff0c;建立信号和其信号槽函数connect() 4. 在其他类中创建信号处理槽函数 #include "mythread.h"my…

Linux 中的 col 命令及示例

Linux 系统中的col命令用于过滤掉反向换行,使输出看起来更加正确,只有正向和半正向换行,并尽可能用制表符替换空白字符。这在处理 nroff 和 tbl 的输出时被证明是有用的。col 实用程序只是从标准输入读取并写入标准输出。 句法: 列 [-bfhpx] [-l num] col [-bfhpx] [-l nu…

C盘清理教程

C盘清理教程 首先使用space Sniffer 扫一下c盘&#xff0c;然后看一下到底是哪个文件这么大 第二步&#xff0c;创建软链接。 首先将我们需要移动的文件的当前路径拷贝下来&#xff1a;C:\Users\Tom\Desktop\test-link\abc\ghi.txt 然后假设剪切到D盘下&#xff1a;D:\ghi.…

Linux 中的 cmp 命令及示例

Linux/UNIX 中的cmp命令用于逐字节比较两个文件,帮助您找出两个文件是否相同。 当 cmp 用于比较两个文件时,如果发现差异,则将第一个不匹配的位置报告到屏幕,如果没有发现差异,即比较的文件相同。cmp 不显示任何消息,如果比较的文件相同,则仅返回提示。语法: cmp [选项…

基于改进人工蜂群算法的 K 均值聚类算法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

RBTree模拟实现

一、概念 概念&#xff1a;红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出俩倍&a…

pdf拆分成多个文件的方法以及注意事项

PDF是一种非常流行的文件格式&#xff0c;但有时候我们需要将一个大的PDF文件拆分成多个小的文件&#xff0c;以便于管理和分享。本文将介绍一些拆分PDF文件的方法以及需要注意的事项。 AdobeAcrobat是一款专业的PDF编辑软件&#xff0c;可以轻松地拆分PDF文件。以下是使用Adob…