二叉树进阶--二叉搜索树

news/2024/5/6 14:42:26/文章来源:https://blog.csdn.net/weixin_72068014/article/details/129002958

目录

1.二叉搜索树

1.1 二叉搜索树概念

1.2 二叉搜索树操作

1.3 二叉搜索树的实现

1.4 二叉搜索树的应用

 1.5 二叉搜索树的性能分析

2.二叉树进阶经典题:


1.二叉搜索树

1.1 二叉搜索树概念

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

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
二叉搜索树又称二叉排序树,因为根据其性质我们可以知道其中序遍历是有序的

1.2 二叉搜索树操作

1. 二叉搜索树的查找
a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b、最多查找高度次,走到到空,还没找到,这个值不存在。
2. 二叉搜索树的插入
插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
3.二叉搜索树的删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
删除方法:
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点
中,再来处理该结点的删除问题--替换法删除

1.3 二叉搜索树的实现

这里使用递归版本和非递归版本进行实现,需要注意的是为了保证封装性,这里大多采用子函数的形式来防止封装性被破坏。其中删除的过程最复杂

//节点类
template <class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_key(key),_left(nullptr),_right(nullptr){}
};
//二叉搜索树
template <class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:BSTree():_root(nullptr){}//为了不破坏封装性,采用子函数的形式不暴露根BSTree(const BSTree<K>& t){_root = CopyTree(t._root);}BSTree<K>& operator=(BSTree<K> t){swap(_root,t._root);return *this;}~BSTree(){Destroy(_root);_root = nullptr;}//插入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){if (_root == nullptr)return false;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* 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{//找到了//1.可能是左为空//2.右为空//两边都不为空if(cur->_left == nullptr){//有可能删除根节点if (cur == _root){_root = _root->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = _root->_right;}else{if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else{//都不为空//从当前节点的右子树开始找最小的值Node* minright = cur->_right;Node* parent = cur;while (minright->_left){parent = minright;minright = minright->_left;}cur->_key = minright->_key;//将最小的值的节点剩下的节点链接给parentif (parent->_left == minright){parent->_left = minright->_right;}else{parent->_right = minright->_right;}delete minright;}return true;}}return false;}//打印,为了保证其封装性,可以使用子函数,采用中序遍历void Print(){PrintHelper(_root);}//递归版本的插入bool InsertR(const K& key){return _InsertR(_root, key);}//递归版本的查找bool FindR(const K& key){return _FindR(_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;}Node* CopyTree(Node* root){//前序建树即可if (root == nullptr){return true;}Node* newRoot = new Node(root->_key);newRoot->_left = CopyTree(root->_left);newRoot->_right = CopyTree(root->_right);return newRoot;}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;//还是分3种情况if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{//在当前节点的右子树找到最小值,然后交换Node* minright = root->_right;while (minright->_left){minright = minright->_left;}//交换swap(minright->_key, root->_key);//在右子树中找到要删除的值return _EraseR(root->_right, key);}delete del;return true;}}bool _FindR(Node* root, const K& key){if (root == nullptr){return false;}if (root->_key > key){return _FindR(root->_left, key);}else if (root->_key < key){return _FindR(root->_right, key);}else{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;}}void PrintHelper(const Node* _root){//中序遍历if (_root == nullptr)return;PrintHelper(_root->_left);cout << _root->_key << " ";PrintHelper(_root->_right);}Node* _root = nullptr;
};

1.4 二叉搜索树的应用

1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见
比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
现次数就是<word, count>就构成一种键值对
KV模型代码变形:(这里只修改了插入和查找,因为这个使用的多,而且到后面的map和set会深入学习)
//改造二叉搜索树变为KV模型
namespace KV
{template <class K, class V>struct BSTreeNode{BSTreeNode<K,V>* _left;BSTreeNode<K,V>* _right;K _key;V _val;BSTreeNode(const K& key, const V& val):_key(key), _val(val), _left(nullptr), _right(nullptr){}};template <class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public://插入bool Insert(const K& key, const V& val){//开始插入第一个的情况if (_root == nullptr){_root = new Node(key,val);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,val);//判断链接的左右if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}//查找Node* Find(const K& key){if (_root == nullptr)return nullptr;Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}elsereturn cur;{}}return nullptr;}private:Node* _root = nullptr;};
}

下面就是KV模型的两个例子:

1.在字典中查找你写的单词是否存在:
void TestBSTree1(){BSTree<string, string> dict;dict.Insert("string", "字符串");dict.Insert("tree", "树");dict.Insert("left", "左边、剩余");dict.Insert("right", "右边");dict.Insert("sort", "排序");string str;while (cin >> str){//在字典中查找BSTreeNode<string,string>* ret = dict.Find(str);if (ret){cout << ret->_val << endl;}else{cout << "不存在" << endl;}}}

看看结果:

 2.统计次数:(常用):

void TestBSTree2(){// 统计水果出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };BSTree<string, int> countTree;for (const auto& e : arr){//将数据插入到二叉搜索树中auto ret = countTree.Find(e);if (ret == nullptr){//树中没有该水果countTree.Insert(e, 1);}else{ret->_val++;}}countTree.Print();}

结果:

 1.5 二叉搜索树的性能分析

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

 

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:logN
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N
后面我们学习了AVL树以及红黑树就可以使二叉搜索树的效率达到最高了。

2.二叉树进阶经典题:


1.根据二叉树创建字符串

思路:根据前序遍历,我们可以通过根左子树右子树的顺序进行递归,但是递归子树的时候需要注意条件,如果左子树是空,但是有右子树就需要保留空括号,如果左子树不为空,但右子树为空,就不需要保留空括号。

class Solution {
public:void _tree2str(TreeNode* root,string& result){if(root == nullptr){result += "";return;}result += to_string(root->val);if(root->left || root->right){result += "(";_tree2str(root->left,result);result += ")";}if(root->right){result += "(";_tree2str(root->right,result);result += ")";}}string tree2str(TreeNode* root) {string result;_tree2str(root,result);return result;}
};

2.二叉树的层序遍历

思路:我们可以通过队列来模拟层序遍历:一次输入一层的节点,然后把队列中当层的元素全部弹出,同时进入下一层元素,我们可以通过size来控制当层元素的个数。然后把当层元素放入结果集中

class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {vector<vector<int>> result;if(root == nullptr)return result;queue<TreeNode*> q;q.push(root);while(!q.empty()){vector<int> tmp;int size = q.size();while(size--){TreeNode* frontnode = q.front();q.pop();tmp.push_back(frontnode->val);//放入左右节点if(frontnode->left){q.push(frontnode->left);}if(frontnode->right){q.push(frontnode->right);}}result.push_back(tmp);}return result;}
};

3.二叉树的最近公共祖先

思路:这题可以使用回溯法来解决,我们可以分别将p和q的路径存放在栈中,然后通过对栈的弹出操作,找到他们相同的节点。其中找路径问题就是回溯问题,我们可以把每次递归的结果先保存起来,如果找到就返回真,就可以结束递归,如果没有找到我们就继续递归,当子树递归到了nullptr时,我们就需要回退,回退的本质就是将栈顶元素pop。

class Solution {
public:bool _lowestCommonAncestor(TreeNode* root,TreeNode* p,stack<TreeNode*>& st){if(root == nullptr){return false;}st.push(root);if(root == p){return true;}if(_lowestCommonAncestor(root->left,p,st)){return true;}if(_lowestCommonAncestor(root->right,p,st)){return true;}//回退st.pop();return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {//回溯法stack<TreeNode*> pv;stack<TreeNode*> qv;_lowestCommonAncestor(root,p,pv);_lowestCommonAncestor(root,q,qv);while(pv.size() != qv.size()){if(pv.size() > qv.size()){pv.pop();}elseqv.pop();}while(pv.top() != qv.top()){pv.pop();qv.pop();}return pv.top();}
};

4.二叉搜索树与双向链表

思路:因为二叉搜索树的中序是有序的,我们可以先递归到最小的节点,然后通过中序改变它们之间的链接关系。

class Solution {
public:void _Convert(TreeNode* cur,TreeNode*& prev){if(cur == nullptr){return;}//中序走到最小_Convert(cur->left,prev);//建立链接关系//这里是防止第一次prev为空的情况if(prev){prev->right = cur;}cur->left = prev;prev = cur;_Convert(cur->right,prev);}TreeNode* Convert(TreeNode* root) {if(root == nullptr)return nullptr;TreeNode* prev = nullptr;_Convert(root,prev);//返回根while(root->left){root = root->left;}return root;}
};

5.从前序与中序遍历序列构造二叉树

思路:因为前序是可以确定根的,所以我们可以在中序中找到根,然后划分左右子树的区间,根据前序的顺序,先递归左子树,再递归右子树,当区间不存在时即可回退。

class Solution {
public:TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder,int begin,int end,int& i){if(begin > end)return nullptr;//从中序中找子树区间int j = begin;for(;j<=end;++j){if(inorder[j] == preorder[i])break;}TreeNode* root = new TreeNode(preorder[i++]);//[begin,j-1] j [j+1,end]root->left = _buildTree(preorder,inorder,begin,j-1,i);root->right = _buildTree(preorder,inorder,j+1,end,i);return root;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {//先序找根,中序找子树//采用闭区间int i = 0;return  _buildTree(preorder,inorder,0,inorder.size()-1,i);}
};

6.使用非递归实现二叉树的前序遍历

思路:一般递归可以实现的代码,使用非递归都需要使用到数据结构的栈,我们可以将树分成左路节点和右树,我们先迭代左路节点到空,其中把每个值存放在栈中,并保存到结果集中,然后取栈顶元素再走右树即可。而中序遍历所需要保存的结果刚好是前序遍历栈弹出的结果,代码与这个类似。

class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {//分成两部分,左路节点和右子树TreeNode* cur = root;stack<TreeNode*> st;vector<int> v;while(!st.empty() || cur){while(cur){v.push_back(cur->val);st.push(cur);cur = cur->left;}//走右子树TreeNode* tmp = st.top();st.pop();cur = tmp->right;}return v;}
};

7.使用非递归实现二叉树的后序遍历

思路:这个和前序以及中序有所不同,就是在确定根的时候,我们需要确定两次,第一次是拿到根并走其右子树,第二次拿到根的时候就可以将根从栈中弹出了。我们也可以使用结构体存放每个节点和节点被取出的次数。当然还有更巧妙的方法:当我们走到右子树的最右端时,我们就可以使用一个指针记录下来,在当前节点回退的时候必然存在cur->right  == prev(这个就是用来记录的节点),然后我们再把这个标记节点更新到当前节点,这样就可以不断回退了。具体看代码理解:

class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {vector<int> v;stack<TreeNode*> st;TreeNode* cur = root;TreeNode* prev = nullptr;//用来记录根的右子树是否被访问while(!st.empty() || cur){while(cur){st.push(cur);cur = cur->left;}TreeNode* top = st.top();//如果右子树为空或者到最右端返回的时候就回收结果if(top->right == nullptr || top->right == prev){st.pop();v.push_back(top->val);prev = top;//从最右端回来的时候起重要作用}else{//这时候要往右迭代cur = top->right;}}return v;} 
};

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

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

相关文章

最后一个单词的长度-力扣58-java

一、题目描述给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。示例 1&#xff1a;输入&#xff1a;s "Hello World"输出&#x…

2.8、调度算法的评价指标

1、CPU 利用率 由于早期的 CPU 造价极其昂贵&#xff0c; 因此人们会希望让CPU尽可能多地工作\color{red}希望让 \texttt{CPU} 尽可能多地工作希望让CPU尽可能多地工作 CPU利用率\color{red}\texttt{CPU}利用率CPU利用率&#xff1a;指 CPU “忙碌” 的时间占总时间的比例。 利…

基于VS调试分析 + 堆栈观察问题代码段

文章目录问题代码段1 —— 阶乘之和问题代码段2 —— 越界的危害① 发现问题② 分析问题③ 思考问题【⭐堆栈原理⭐】④ 解决问题【DeBug与Release】&#x1f468;程序员与测试人员&#x1f469;✒总结与提炼问题代码段1 —— 阶乘之和 先来看一道C语言中比较基础的题目&#x…

GAN系列基础知识

原始值函数 原始GAN的值函数是 minGmaxDV(D,G)Ex∼pdata(x)[logD(x)]Ez∼pz(z)[log(1−D(G(z)))]min_Gmax_DV(D,G) E_{x \sim p_{data}(x)}[logD(x)]E_{z \sim p_{z}(z)} [log(1-D(G(z)))]minG​maxD​V(D,G)Ex∼pdata​(x)​[logD(x)]Ez∼pz​(z)​[log(1−D(G(z)))] 其中Ex…

【C++】类和对象---需掌握的功能

目录1.初始化列表1.1构造函数赋值1.2初始化列表格式&#xff1a;编译器执行的顺序&#xff1a;特性&#xff1a;1.3explicit关键字类型替换过程多参数构造函数类型替换&#xff08;C11&#xff09;2.static成员编程题3.匿名对象4.友元4.1友元函数4.2友元类5.内部类6.拷贝对象时…

java中字符串首字母变大写的两种方法

public class 快速排序 {public static void main(String[] args) {int[] arr new int[]{5, 2, 9, 6, 22, 21};//System.out.println(Arrays.toString(kuaiPai(arr)));// System.out.println(Arrays.asList("dada", "dda", "ddd"));//System.o…

学完Scrapy-Splash秒变爬虫大佬

在做爬虫的时候&#xff0c;大多数的网页中会存在数据动态加载的部分&#xff0c;而且多数都是后期渲染上的。正常情况下爬虫程序仅能爬取被渲染过的数据。因此我们看到的数据也许并非是爬虫直接获取来的。 而scrapy-splash担任了一个中间人的角色&#xff0c;程序通过splash服…

Vue3代码初体验找不同

文章目录&#x1f31f; 写在前面&#x1f31f; 代码分析&#x1f31f; 写在最后&#x1f31f; 写在前面 专栏介绍&#xff1a; 凉哥作为 Vue 的忠实 粉丝输出过大量的 Vue 文章&#xff0c;应粉丝要求开始更新 Vue3 的相关技术文章&#xff0c;Vue 框架目前的地位大家应该都晓…

Echarts 设置折线图拐点的颜色,边框等样式,hover时改变颜色

第014个点击查看专栏目录上一篇文章我们讲到了如何设置拐点大小,图形类型&#xff0c;旋转角度&#xff0c;缩放同比&#xff0c;位置偏移等&#xff0c;这篇文章介绍如何设置拐点的颜色、边框大小颜色等样式。hover轴线时候&#xff0c;拐点的填充颜色改变文章目录示例效果示例…

python笔记-- “__del__”析构方法

-#### 1、基本概念&#xff08;构造函数与析构函数&#xff09; 特殊函数&#xff1a;由系统自动执行&#xff0c;在程序中不可显式地调用他们 构造函数&#xff1a; 建立对象时对对象的数据成员进行初始化&#xff08;对象初始化&#xff09; 析构函数&#xff1a; 对象生命期…

解决需求变更难题的8大方案

需求变更8大原因为什么会出现需求变更&#xff0c;这是由于需求约束、规则有了新的变化、由于政策发生变化&#xff0c;客户、沟通方式、流程化、标准化的问题等导致。这里在在过去的项目经验中&#xff0c;提出了常见的8大需求变更的原因。政策发生变化&#xff1a;指由于国家…

Linux/CenterOS 7.9配置汉化gitlab服务器

1.安装gitlab的依赖项 yum install -y curl openssh-server openssh-clients postfix cronie policycoreutils-python2.启动postfix&#xff0c;并设置为开机启动 systemctl start postfixsystemctl enable postfix3.防火墙和selinux的设置 setenforce 0systemctl stop fire…

【macOS】mac电脑M2芯片安装Homebrew 最简单的方法

一 Homebrew的安装 打开终端&#xff0c;复制如下命令&#xff0c;按回车执行 M芯片和Intel芯片均可 中途可能需要你手动输入密码&#xff0c;输入完成回车即可&#xff08;密码不可见 选择中科大或者清华镜像源 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/Hom…

最接近的三数之和-力扣16-java排序+双指针

一、题目描述给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数&#xff0c;使它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在恰好一个解。示例 1&#xff1a;输入&#xff1a;nums [-1,2,1,-4], target 1输出&#xff…

revit中如何创建有坡度的排水沟及基坑?

一、revit中如何创建有坡度的排水沟? 先分享一张有坡度排水沟的族的照片给大家加深一下印象&#xff0c;有了一个粗略的直观认识&#xff0c;小编就来说说做这个族的前期思路吧。 一、前期思路&#xff1a; 1、 用拼接的方式把这个族形状拼出来&#xff0c;先用放样&#xff0…

Vue3 中 axios 的安装及使用

目录前言&#xff1a;一、什么是 axios &#xff1f;二、Axios 的配置项三、Axios 的请求方式四、自定义创建实例五、Axios 请求错误处理六、Axios 解决跨域问题七、Axios 请求案例随机笑话大全总结&#xff1a;前言&#xff1a; 在编写vue里的项目时&#xff0c;必须要用和后台…

【数据库】MySQL的sql语句详解

目录 MySQL之sql语句 一&#xff0c; INSERT语句 insert语句的使用&#xff1a; 1&#xff0c;给表中一次性插入一条记录 2&#xff0c;给表中一次性插入多条记录 二&#xff0c; REPLACE语句 REPLACE语句的使用 1&#xff0c;语法一 2&#xff0c;语法二 3&#xff…

Linux环境变量讲解

目录 环境变量 alias命令 type命令 变量分类 Linux最主要的全局环境变量 环境变量 变量是计算机系统用于保存可变数值的数据类型 在Linux中&#xff0c;一般变量都是大写&#xff0c;命令是小写 在Linux中&#xff0c;变量直接使用&#xff0c;不需要定义&#xff08;更快…

加入bing体验chatGPT大军中来吧,它来了!

1 第一步&#xff1a;加入候选名单 1、首先需要加入候选名单 https://www.microsoft.com/zh-cn/edge?formMA13FJ 2、下载最新的Edge浏览器、androd、iOS都有试用版本&#xff08;可以看到iOS加护当前已满&#xff09; 这里我下载的是dev版本&#xff0c;Canary版本由于是…

点云转3D网格【Python】

推荐&#xff1a;使用 NSDT场景设计器 快速搭建 3D场景。 在本文中&#xff0c;我将介绍我的 3D 表面重建过程&#xff0c;以便使用 Python 从点云快速创建网格。 你将能够导出、可视化结果并将结果集成到您最喜欢的 3D 软件中&#xff0c;而无需任何编码经验。 此外&#xff0…