二叉树进阶 --- 中
目录
1. find 的递归实现
2. insert 的递归实现
3. erase 的递归实现
3.1. 被删除的节点右孩子为空
3.2. 被删除的节点左孩子为空
3.3. 被删除的节点左右孩子都不为空
4. 析构函数的实现
5. copy constructor的实现
6. 赋值运算符重载
7. 搜索二叉树的完整实现
1. find 的递归实现
find的递归实现较为简单,思路是:根据当前节点的 key 与传入的 key 作比较:
- 如果前者大于后者,那么当前节点往左子树走;
- 如果前者小于后者,那么当前节点往右子树走;
- 如果两者相等,返回true;
- 走到空,返回false。
代码实现:
// 对外提供的
bool find_recursion(const T& key)
{return _find_recursion(_root, key);
}
// 类中私有的
bool _find_recursion(Node* root, const T& key)
{if (root == nullptr)return false;else{if (root->_key < key)return _find_recursion(root->_right, key);else if (root->_key > key)return _find_recursion(root->_left, key);elsereturn true;}
}
2. insert 的递归实现
insert分为两个过程
- 第一个过程:找到合适位置;
- 第二个过程:构建新节点,并完成连接关系。
假如现在我们已经得到了合适的插入位置,那么如何连接呢?
例如,如下图所示:我们要插入13这个数据,现在的关键问题是,如何将15和13这两个节点连接起来呢? 具体如下:

第一种方法:调用函数时,将父亲节点即这里的15也传进来。找到合适位置,创建节点并连接。
但是我们在这里提出一个较好玩的玩法,利用引用传参,如下所示:
// 对外提供的
bool insert_recursion(const T& key)
{ return _insert_recursion(_root, key);
}
// 类中私有的
bool _insert_recursion(Node*& root, const T& key)
{if (root == nullptr){// 走到空, 说明找到了目标位置, 需要构建新节点, 并完成连接关系// 在这里, 我们用上图解释:// root就是15这个节点的左孩子的引用,即root就是15的左孩子// 给root new了一个node(key),等价于插入了这个节点,并连接了起来.root = new Node(key);return true;}else{if (root->_key < key)return _insert_recursion(root->_right, key);else if (root->_key > key)return _insert_recursion(root->_left, key);elsereturn false;}
}
3. erase 的递归实现
对于erase的递归实现,其实也可以分为两个过程:
- 第一个过程:找到这个要删除的特殊节点;
- 第二个过程:可以分为三种情况(左孩子为空、右孩子为空、左右孩子都不为空),根据不同情况进行删除。
假设我们现在已经得到了要删除节点的位置,该如何删除呢?
3.1. 被删除的节点右孩子为空
如图所示:我们要删除6号节点(其右孩子为空),该如何删除:

由于 root 是4的右孩子的引用,且 root 的右孩子为空,那么root = root->_left,就可以将4的右孩子由6变更为5,我们在删除6即可,因此我们需要提前保存6节点,当指向变更之后,delete 6。
3.2. 被删除的节点左孩子为空
如图所示:我们要删除15号节点(其左孩子为空),该如何删除:

由于 root 是8的右孩子的引用,且 root 没有左孩子,那么我们此时只需要更改 root 即可,让 root 到它的右孩子 (root = root->_right),等价于将8连接了19,当然我们也需要提前将 root 节点进行保存,更改指向后,在释放 root 节点即可。
3.3. 被删除的节点左右孩子都不为空
较为复杂的就是第三种情况了,由于此时被删除节点有两个孩子,因此无法像上面两种情况进行处理。此时我们还是要利用循环实现的思路:
- (1):从被删除的节点开始,先找到左子树的最大节点or右子树的最小节点(我在这里称之为"合适节点");
- (2):交换这个"合适结点"和被删除节点的key;
- (3):将删除原节点转化为删除我们后找的这个"合适节点"。
在这里我们用实例说明,如下图所示:如果我要删除下图中的4,该如何删除?
我在这里实现的"合适节点"是: 左子树的最大(右)节点

相信前两个过程是没有困难的,最后一步可能不好实现,但是当我们经过了前两个过程,我们发现被删除节点变成了我们找到的"合适节点",而且这个"合适节点"很有特征,如果它是左子树的最大值,那么它一定不会有右子树,反之,如果他是右子树的最小节点,那么它一定不会有左子树。因此我们可以在递归一次,如果"合适节点"是左子树的最大节点,那么我们递归树的左子树即可,反之如果是右子树的最小节点,那么我们递归树的右子树即可。
代码如下:
// 对外提供的
bool erase_recursion(const T& key)
{return _erase_recursion(_root, key);
}
// 类中私有的
bool _erase_recursion(Node*& root, const T& key)
{if (!root)return false;else{// 如果当前节点的key > 目标key,那么递归它的左子树即可if (root->_key > key)return _erase_recursion(root->_left, key);// 如果当前节点的key < 目标key,那么递归它的右子树即可else if (root->_key < key)return _erase_recursion(root->_right, key);// 如果找到了,进行删除else{// 此时的root就是要删除的节点Node* del = root;// a. 左子树为空if (root->_left == nullptr)root = root->_right;// b. 右子树为空else if (root->_right == nullptr)root = root->_left;// c. 左右子树都不为空else{// 左子树的最右节点Node* left_max = root->_left;while (left_max->_right)left_max = left_max->_right;// 交换"合适节点"和"被删除节点"的keystd::swap(left_max->_key, root->_key);// 在这里递归左子树即可return _erase_recursion(root->_left, key);}delete del;del = nullptr;return true;}}
}
4. 析构函数的实现
析构函数的实现我们依据的是后序的思想(LRN),先析构左子树、然后是右子树、最后才是根。这种实现的原因是是少了许多的记录信息,例如在这里我们就不用记录下一个节点。因为我们释放的就是当前的叶子节点。
具体实现如下:
~BinarySearchTree()
{_BSTDestroy(_root);
}
// 注意我们这里传递的是根的引用
void _BSTDestroy(Node*& root)
{if (root == nullptr)return;else{// 依据后序的思想_BSTDestroy(root->_left);_BSTDestroy(root->_right);delete root;root = nullptr;}
}
5. copy constructor的实现
老生常谈的问题,如果我们没有显示实现拷贝构造函数,那么编译器默认生成的拷贝构造会对内置类型按照字节序的方式进行拷贝,对自定义类型成员属性会去调用它的拷贝构造函数。而字节序的方式进行拷贝会带来两个问题:
- 其一,其中一个对象的修改会影响另一个对象;
- 其二,同一空间会被析构两次,进程crash。
因此,我们在这里必须要实现深拷贝,那如何实现呢?我们可以借助前序的思想(NLR)。从根节点开始进行构造节点,然后递归构造它的左子树和右子树。注意构造的时候需要它们的连接关系。
代码如下:
BinarySearchTree(const BinarySearchTree<T>& copy)
{_root = _creat_new_root(copy._root);
}
Node* _creat_new_root(Node* root)
{// 如果遇到空了,就不用构造了if (root == nullptr)return nullptr;else{// 根据前序的思想(NLR),依次构造它的根、左子树、右子树 // 同时将它们连接起来Node* new_root = new Node(root->_key);new_root->_left = _creat_new_root(root->_left);new_root->_right = _creat_new_root(root->_right);return new_root;}
}
6. 赋值运算符重载
赋值运算符重载就比较简单了,因为我们已经实现了copy constructor,在这里利用传值传参会进行拷贝构造的特性实现我们的赋值
代码如下:
// 传值传参会进行拷贝构造
BinarySearchTree<T>& operator=(BinarySearchTree<T> copy)
{std::swap(_root, copy._root);return *this;
}
7. 搜索二叉树的完整实现
代码如下:
#ifndef _BINARY_SEARCH_TREE_HPP_
#define _BINARY_SEARCH_TREE_HPP_#include <iostream>namespace Xq
{template<class T>struct BinarySearchTreeNode{BinarySearchTreeNode<T>* _left;BinarySearchTreeNode<T>* _right;T _key;BinarySearchTreeNode(const T& key) :_key(key), _left(nullptr), _right(nullptr) {}};template<class T>class BinarySearchTree{private:typedef BinarySearchTreeNode<T> Node;public:BinarySearchTree(Node* root = nullptr) :_root(root) {}bool insert(const T& key){// 1. 如果是空树,直接对_root赋值即可,插入成功并返回trueif (_root == nullptr){_root = new Node(key);return true;}else{// step 1: 先找目标位置Node* cur = _root;// 为了更好的连接新节点, 因此记录父节点Node* parent = nullptr;while (cur){// 如果当前节点的Key大于目标Key// 当前节点应该向左子树走if (cur->_key > key){parent = cur;cur = cur->_left;}// 如果当前节点的Key小于目标Key// 当前节点应该向右子树走else if (cur->_key < key){parent = cur;cur = cur->_right;}else{// 找到了相同的 key, 在这里不插入return false;}}// cur 走到了空, 即 cur 就是合适的位置cur = new Node(key);// 我们需要判断cur是parent的左节点还是右节点// 如果key小于parent的key,那么插入左节点if (key < parent->_key)parent->_left = cur;// 反之连接到右节点elseparent->_right = cur;return true;}}bool find(const T& key){// 1. 从根节点开始Node* cur = _root;while (cur){// 2. 如果当前关键字大于目标关键字,那么向左子树走if (cur->_key > key)cur = cur->_left;// 3. 如果小于目标关键字,那么向右子树走else if (cur->_key < key)cur = cur->_right;// 4. 相等,就返回trueelsereturn true;}// 5. 循环结束,说明没找到, 返回falsereturn false;}bool erase(const T& key){// 先找要删除的节点Node* del = _root;Node* del_parent = nullptr;while (del){if (del->_key < key){del_parent = del;del = del->_right;}else if (del->_key > key){del_parent = del;del = del->_left;}else{// 锁定了要删除的节点// 分三种情况:// case 1: 左子树为空if (del->_left == nullptr){// 如果要删除的节点是根if (del == _root){Node* newroot = del->_right;delete _root;_root = newroot;}else{// 托孤法删除if (del_parent->_left == del)del_parent->_left = del->_right;elsedel_parent->_right = del->_right;delete del;del = nullptr;}}// case 2: 右子树为空else if (del->_right == nullptr){if (_root == del){Node* newroot = del->_left;delete _root;_root = newroot;}else{if (del_parent->_left == del)del_parent->_left = del->_left;elsedel_parent->_right = del->_left;delete del;del = nullptr;}}// case 3: 左右子树都不为空else{// 从被删除节点开始, 找右子树的最小(左)节点 || 找左子树的最大(右)节点if (del->_right)_erase_right_min_node(del);else_erase_left_max_node(del);}return true;}}return false;}bool find_recursion(const T& key){return _find_recursion(_root, key);}bool insert_recursion(const T& key){return _insert_recursion(_root, key);}bool erase_recursion(const T& key){return _erase_recursion(_root, key);}~BinarySearchTree(){_BSTDestroy(_root);}BinarySearchTree(const BinarySearchTree<T>& copy){_root = _creat_new_root(copy._root);}// 传值传参会进行拷贝构造BinarySearchTree<T>& operator=(BinarySearchTree<T> copy){std::swap(_root, copy._root);return *this;}void InOrder(){_InOrder(_root);std::cout << std::endl;}private:void _InOrder(Node* root){if (root){_InOrder(root->_left);std::cout << root->_key << " ";_InOrder(root->_right);}}bool _find_recursion(Node* root, const T& key){if (root == nullptr)return false;else{if (root->_key < key)return _find_recursion(root->_right, key);else if (root->_key > key)_find_recursion(root->_left, key);elsereturn true;}}bool _insert_recursion(Node*& root, const T& key){if (root == nullptr){root = new Node(key);return true;}else{if (root->_key < key)return _insert_recursion(root->_right, key);else if (root->_key > key)return _insert_recursion(root->_left, key);elsereturn false;}}void _erase_right_min_node(Node* del){// 从被删除结点开始, 找右子树的最小(左)节点Node* right_min = del->_right;// 并记录这个节点的父亲节点, 让其从del开始Node* right_min_parent = del;while (right_min->_left){right_min_parent = right_min;right_min = right_min->_left;}// 交换这个节点和要删除节点的 keystd::swap(del->_key, right_min->_key);// 将删除 del 转化为删除 right_min (托孤法删除)if (right_min_parent->_left == right_min)right_min_parent->_left = right_min->_right;elseright_min_parent->_right = right_min->_right;delete right_min;right_min = nullptr;}void _erase_left_max_node(Node* del){// 从被删除节点开始, 找左子树的最大(右)节点Node* left_max = del->_left;// 并记录这个节点的父亲节点, 让其从del开始Node* left_max_parent = del;while (left_max->_right){left_max_parent = left_max;left_max = left_max->_right;}// 交换这个节点和要删除节点的 keystd::swap(del->_key, left_max->_key);// 将删除 del 转化为删除 left_max (托孤法删除)if (left_max_parent->_left == left_max)left_max_parent->_left = left_max->_left;elseleft_max_parent->_right = left_max->_left;delete left_max;left_max = nullptr;}bool _erase_recursion(Node*& root, const T& key){if (!root)return false;else{// 如果当前节点的key > 目标key,那么递归它的左子树即可if (root->_key > key)return _erase_recursion(root->_left, key);// 如果当前节点的key < 目标key,那么递归它的右子树即可else if (root->_key < key)return _erase_recursion(root->_right, key);// 如果找到了,进行删除else{// 此时的root就是要删除的节点Node* del = root;// a. 左子树为空if (root->_left == nullptr)root = root->_right;// b. 右子树为空else if (root->_right == nullptr)root = root->_left;// c. 左右子树都不为空else{// 左子树的最右节点Node* left_max = root->_left;while (left_max->_right)left_max = left_max->_right;// 交换"合适节点"和"被删除节点"的keystd::swap(left_max->_key, root->_key);// 在这里递归左子树即可return _erase_recursion(root->_left, key);}delete del;del = nullptr;return true;}}}Node* _creat_new_root(Node* root){// 如果遇到空了,就不用构造了if (root == nullptr)return nullptr;else{// 根据前序的思想(NLR),依次构造它的根、左子树、右子树 // 同时将它们连接起来Node* new_root = new Node(root->_key);new_root->_left = _creat_new_root(root->_left);new_root->_right = _creat_new_root(root->_right);return new_root;}}// 注意我们这里传递的是根的引用void _BSTDestroy(Node*& root){if (root == nullptr)return;else{// 依据后序的思想_BSTDestroy(root->_left);_BSTDestroy(root->_right);delete root;root = nullptr;}}private:Node* _root;};
}#endif
二叉树进阶 --- 中,结束。
相关文章:
二叉树进阶 --- 中
目录 1. find 的递归实现 2. insert 的递归实现 3. erase 的递归实现 3.1. 被删除的节点右孩子为空 3.2. 被删除的节点左孩子为空 3.3. 被删除的节点左右孩子都不为空 4. 析构函数的实现 5. copy constructor的实现 6. 赋值运算符重载 7. 搜索二叉树的完整实现 1. fi…...
ChatGPT DALL-E绘图,制作各种表情包,实现穿衣风格的自由切换
DALL-E绘图功能探索: 1、保持人物形象一致,适配更多的表情、动作 2、改变穿衣风格 3、小女孩的不同年龄段展示 4、不同社交平台的个性头像创作 如果不会写代码,可以问GPT。使用地址:我的GPT4 视频,B站会发&#…...
程序环境和预处理、编译链接过程、编译的几个阶段、运行环境、预定义符号等的介绍
文章目录 前言一、程序的翻译环境和执行环境二、编译链接过程三、编译的几个阶段四、运行环境五、预定义符号总结 前言 程序环境和预处理、编译链接过程、编译的几个阶段、运行环境、预定义符号的介绍。 一、程序的翻译环境和执行环境 在 ANSI C 的任何一种实现中,…...
MySQL导入导出详细教程
导出 语法 mysqldump [OPTIONS] database [tables] mysqldump [OPTIONS] --databases [OPTIONS] DB1 [DB2 DB3...] mysqldump [OPTIONS] --all-databases [OPTIONS]导出所有数据库 mysqldump -uroot -proot --all-databases >/tmp/all.sql导出db1、db2两个数据库的所有数…...
STM32F103学习笔记 | 8. 二,八,十,十六进制表示方式
文章目录 进制基本信息参考文献 进制基本信息 C语言中的表示,前缀加0表示八进制数,前缀加0x表示十六进制数 基数数码名称描述代码和书本中的表示举例20 和 1二进制逢二进一,几乎所有的电子计算机内部都使用二进位制,分别为“0”…...
ROS2 工作空间
文章目录 ROS2 工作空间创建工作空间自动安装依赖编译工作空间设置环境变量参考链接 ROS2 工作空间 工作空间可以简单理解为工程目录。 ROS 系统中一个典型的工作空间结构如图所示: dev_ws: 根目录,里面会有四个子目录(子空间&a…...
基于CCS5.5的双音多频(DTMF)信号检测仿真实验(①检测型音频文件②输入生成音频并检测)
DTMF的优点 我们知道,DTMF根本上仍然是频谱分析,基础还是DFT,但DFT通常需要对一整段数据做变换,而DTMF不同,每输入一个采样点就计算一次,更有利于硬件实现。 基于CCS的双音多频(DTMF)信号检测原理 公式详细推导 详细的公式推导在下面这篇博客中已经进行了详细的描述,…...
Postgresql中JIT函数能否inline的依据function_inlinable
相关 《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》 《LLVM的ThinLTO编译优化技术在Postgresql中的应用》 前置阅读:《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》 在JIT inline函数的过…...
存储过程作为初始化数据例子
查询出每个人员,为每个人员插入11条数据,作为初始化数据 oracle存储过程 CREATE OR REPLACE PROCEDURE initialize_order_warn_config ISv_id NUMBER; BEGINSELECT COALESCE(MAX(id), 0) INTO v_id FROM order_warn_config;FOR rec IN (SELECT DISTINCT…...
【数据分析】 JupyterNotebook安装及使用简介
各位大佬好 ,这里是阿川的博客 , 祝您变得更强 个人主页:在线OJ的阿川 大佬的支持和鼓励,将是我成长路上最大的动力 阿川水平有限,如有错误,欢迎大佬指正 在数据分析中,一般用Pycharm编辑代…...
nginx命令大全
以下是Nginx的一些常用命令,适用于Linux环境,部分命令在Windows系统下也适用,但命令形式可能有所不同: 查看Nginx版本号: nginx -v:简短显示版本号。nginx -V:详细显示版本号及编译配置信息。 启动Nginx…...
【数据结构】顺序表与链表的差异
顺序表和链表都是线性表,它们有着相似的部分,但是同时也有着很大的差异。 存储空间上的差异: 对于插入上的不同点,顺序表在空间不够时需要扩容,而如果在使用realloc函数去扩容,会有原地扩容和异地扩容两种情…...
小程序如何进行评分评价
小程序以其便捷、快速、无需安装的特点,成为了众多企业、品牌与消费者之间的重要连接桥梁。而评价评分机制,作为小程序中不可或缺的一环,对于提升用户体验、建立用户信任、促进商家与用户的互动等方面,都具有至关重要的意义。本文…...
【MATLAB源码-第206期】基于matlab的差分进化算法(DE)机器人栅格路径规划,输出做短路径图和适应度曲线。
操作环境: MATLAB 2022a 1、算法描述 差分进化算法(Differential Evolution, DE)是一种有效的实数编码的进化算法,主要用于解决实值函数的全局优化问题。本文将详细介绍差分进化算法的背景、原理、操作步骤、参数选择以及实际应…...
Python图形界面(GUI)Tkinter笔记(三):控件的定位(1)
Tkinter(GUI)设计图形界面时有三种控件的包装方法去定位各控件在窗口(父容器、根窗口)上的位置。 【1】pack()方法:用方位来定位位置,类似于Word文档中的文字对齐方式。 【2】grid()方法:用二维表格式的坐标值定位,类似于EXCEL单位元。 【3】place()方法:用窗口的像…...
数据结构--单链表 详解(附代码
目录: 1:链表的概念及结构 2:实现单链表 3:常见疑问 解答 (看到最后!!) 一:链表的概念及结构 1.1 概念: 链表是⼀种 物理存储结构上非连续、非顺序的 存储结…...
leetcode 1749.任意子数组和的绝对值的最大值
思路:dp 说到绝对值,大家肯定不陌生,但是用在dp上就会使问题变得稍微复杂一些了。 我们在最大子数组和的那道题中知道,在状态转移的时候,我们会舍弃掉为负数的连续部分,重新构建连续的子串。但是…...
Linux进程——进程地址空间
前言:在讲完环境变量后,相信大家对Linux有更进一步的认识,而Linux进程概念到这也快接近尾声了,现在我们了解Linux进程中的地址空间! 本篇主要内容: 了解程序地址空间 理解进程地址空间 探究页表和虚拟地址空…...
基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (三)
基于 LlaMA 3 LangGraph 在windows本地部署大模型 (三) 大家继续看 https://lilianweng.github.io/posts/2023-06-23-agent/的文档内容 第二部分:内存 记忆的类型 记忆可以定义为用于获取、存储、保留以及随后检索信息的过程。人脑中有多…...
python3如何安装bs4
在python官网找到beautifulsoup模块的下载页面,点击"downloap"将该模块的安装包下载到本地。 将该安装包解压,然后在打开cmd,并通过cmd进入到该安装包解压后的文件夹目录下。 在该文件目录下输入"python install setup.py&quo…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...
