二叉树进阶 --- 上

news2024/11/17 9:38:21

目录

1. 二叉搜索树的概念及结构

1.1. 二叉搜索树的概念

1.2. 二叉搜索树的结构样例

2. 二叉搜索树的实现

2.1. insert 的非递归实现

2.2. find 的非递归实现

2.3. erase 的非递归实现

2.3.1. 第一种情况:所删除的节点的左孩子为空

2.3.1.1. 错误的代码

2.3.1.2. 正确的代码

2.3.2. 第二种情况:所删除的节点的右孩子为空

2.3.2.1. 正确的代码

2.3.3. 第三种情况:所删除的节点有两个非空节点 && 找右子树的最左节点

2.3.3.1. 有错误的代码

2.3.3.2. 正确的代码

2.3.4. erase的完整实现如下


1. 二叉搜索树的概念及结构

学习二叉搜索树的一些原因:

  • map 和 set 特性需要先铺垫二叉搜索树,而二叉搜索树也是一种树形结构;
  • 二叉搜索树的特性了解,有助于更好的理解 map 和 set 的特性。

1.1. 二叉搜索树的概念

二叉搜索树(Binary Search Tree,简称BST) 又名为二叉排序树或者是二叉查找树。它可能是一棵空树,或者是满足下面性质的二叉树:

  • 如果它的左子树不为空,那么左子树上的所有节点的值都要小于根节点的值;
  • 如果它的右子树不为空,那么右子树上的所有节点的值都要大于根节点的值;
  • 它的左右子树也是一颗二叉搜索树。

对于一颗二叉搜索树,它的中序遍历可以得到有序的数据;

需要注意的是,二叉搜索树要求每个节点的值都唯一,如果存在重复的值,可以在节点中添加计数器来解决。

1.2. 二叉搜索树的结构样例

一棵树是否是一颗二叉搜索树,必须要符合二叉搜索树的性质。

 为了更好地理解二叉搜索树,我们需要其进行模拟实现:

2. 二叉搜索树的实现

2.1. insert 的非递归实现

对于二叉搜索树的插入,我们需要满足插入后的二叉树仍旧是一颗二叉搜索树,也就是说,插入的元素必须要被插入到特定的位置,以维持二叉搜索树的结构。如上图所示,如果要插入14,那么它的位置是确定的,如下图所示: 

因此 insert 的具体实现我们可以分解为两个过程:

  • 第一步:找到要插入元素的位置;
  • 第二步:插入元素,完成连接关系。 

注意:在这里实现的二叉搜索树的每个值具有唯一性,相同值不插入。

bool insert(const T& key)
{
	// 1. 如果是空树,直接对_root赋值即可,插入成功并返回true
	if (_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;
		// 反之连接到右节点
		else
			parent->_right = cur;
		return true;
	}
}

2.2. find 的非递归实现

find 就很简单了,没什么要说的,根据传递的 key 进行判断,大于当前节点,那么当前节点向左走,反之向右走,如果相等,返回true,循环结束,则说明没有这个key,实现如下:

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. 相等,就返回true
		else
			return true;
	}
	// 5. 循环结束,说明没找到, 返回false
	return false;
}

2.3. erase 的非递归实现

对于搜索二叉树来说,真正有一些难度的是删除,对于删除我们可以分解为不同的情况,根据对应的情况,以特点方式解决。

在这里我们分为三种情况:

  • 所删除的节点的左孩子为空:托孤法删除;
  • 所删除的节点的右孩子为空:托孤法删除;
  • 所删除的节点的有两个非空孩子:替代法删除。

注意:对于叶子结点的处理可以归为第一类情况或者第二类情况。

为了可以更好的理解上面的三种情况,我们用图来说话:

2.3.1. 第一种情况:所删除的节点的左孩子为空

如图所示:假如现在我们要删除的节点是15节点,可以发现它的左孩子为空,那么如何删除呢?

 我们的方法是托孤法删除,什么叫托孤法删除呢?

就是将15的非空孩子(在这里就是19)交给它的父亲节点(在这里就是8),如图所示:

注意:在这里一定是父亲节点的右孩子指向被删除的节点的非空孩子吗?

答案是,不一定,我们需要根据被删除节点和父亲节点的关系判断:

  • 如果被删除节点是父亲节点的右孩子,那么在这里就是父亲节点的右孩子指向被删除节点的非空节点;
  • 如果被删除节点是父亲节点的左孩子,那么在这里就是父亲节点的左孩子指向被删除节点的非空节点。

代码如下:

2.3.1.1. 错误的代码
// 第一种情况: 所删除的节点的左孩子为空
if (del->_left == nullptr)
{
	if (del_parent->_left == del)
	{
		del_parent->_left = del->_right;
	}
	else
	{
		del_parent->_right = del->_right;
	}
	delete del;
    del = nullptr;
}

可能我们认为这段代码没问题,但是如果是下面这种情况呢?  

如果我此时要删除8,而8是这棵树的根节点,它是没有父节点的,那么此时上面的代码就会崩溃;

为了解决这个隐患,我们的方案就是,如果被删除节点是根,且它的左子树为空树,那么我们更新根节点即可,在这里就是让15做根节点。

2.3.1.2. 正确的代码
//第一种情况:所删除的节点的左孩子为空
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;
		}
		else
		{
			del_parent->_right = del->_right;
		}
		delete del;
	}
}

2.3.2. 第二种情况:所删除的节点的右孩子为空

如图所示:假如现在我们要删除的节点是6节点,可以发现它的右孩子为空,那么如何删除呢?

方案依旧是托孤法删除,在这里就是将6(被删除节点)的5(非空孩子节点)交给4(父亲节点) ,如下:

 处理细节,和第一种情况大同小异。

需要注意的就是:最后父亲节点连接非空孩子节点的时候,要根据被删除节点是父亲节点的左孩子还是右孩子来判断。

第二种情况和第一种情况大同小异,也需要对根节点进行特殊处理:

代码如下:

2.3.2.1. 正确的代码
//第二种情况:所删除的节点的右孩子为空
else if (del->_right == nullptr)
{
    // 当被删除节点为根节点
	if (del == _root)
	{
		Node* newroot = del->_left;
		delete del;
		_root = newroot;
	}
    //当被删除节点为非根节点
	else
	{
		if (del_parent->_left == del)
		{
			del_parent->_left = del->_left;
		}
		else
		{
			del_parent->_right = del->_left;
		}
		delete del;
        del = nullptr;
	}
}

2.3.3. 第三种情况:所删除的节点有两个非空节点 && 找右子树的最左节点

较为复杂的就是第三种情况了,由于被删除的节点有两个孩子,因此无法托孤,因为父亲节点至多只能管理两个孩子,所以我们又提出了新的解决方案:替代法删除

如图所示:

假如现在我们要删除4所在的节点,可以发现,4所在的节点有两个孩子,因此无法托孤,那么我们需要采用替代法删除,替代法删除就是在左子树或者右子树找一个"合适节点",将4所在的节点的key进行覆盖,将删除4所在的节点转化为删除我们找的这个"合适节点"。

而这个"合适节点"通常只有两个:

  • 其一:以被删除的节点所在的二叉树开始,左子树的最大节点,即左子树的最右(大)节点;
  • 其二:以被删除的节点所在的二叉树开始,右子树的最小节点,即右子树的最左(小)节点。

而我们在这里就找右子树的最左(小)节点,注意,要从被删除的节点开始,在这里就是5;

当找到这个 "合适节点" 后,交换它与要删除节点的 Key;

此时就将删除目标节点转换为删除这个 "合适节点" 了;

因为此时这个 "合适节点" 只会有两种情况:

  • 第一种:它没有孩子,即左右子树为空;
  • 第二种:它只有一个孩子,且只可能是右孩子,因为这个 "合适节点" 是右子树的最左节点;

如果是第一种,即没有孩子,我们也可以将其归类为第二种情况,即右孩子不为空 && 左孩子为空,因此,可以用统一的方式处理,即托孤法删除 (所删除的节点的左孩子为空)。

故我们的大致步骤如下:

  1. 找合适节点;
  2. 交换合适节点和删除节点的 Key;
  3. 托孤发删除合适节点。

如图所示:

2.3.3.1. 有错误的代码
// 第三种情况:所删除的节点有两个非空节点
else
{
	// 从被删除结点开始, 找右子树的最小(左)节点
	Node* right_min = del->_right;
	// 并记录这个节点的父亲节点
    // 有可能这里我们会习惯的从nullptr开始,但是对于某些特殊情况会崩溃
	Node* right_min_parent = nullptr;
	while (right_min->_left)
	{
		right_min_parent = right_min;
		right_min = right_min->_left;
	}
	// 交换这个节点和要删除节点的 key
	std::swap(del->_key, right_min->_key);

	// 将删除 del 转化为删除 right_min (托孤法删除)
	if (right_min_parent->_left == right_min)
		right_min_parent->_left = right_min->_right;
	else
		right_min_parent->_right = right_min->_right;
	delete right_min;
	right_min = nullptr;
}

如果我们将"合适节点"的父节点初始值设为nullptr,那么在下面的场景会发生崩溃:

由于此时,这个 "合适节点" 正好是 del->_right,不会进入循环,那么 right_min_parent 就是空,那么后面的操作就会对空指针进行解引用,非法操作,进程崩溃。

因此这里的 right_min_parent 的初始值可以从 del 开始,不可以将初始值设为空。

同时,我们发现,最后进行托孤法删除时,我们也进行了判断,这样的原因是因为这个"合适节点"既可能是父节点的左孩子,也可能是父节点的右孩子,因此必须判断。

2.3.3.2. 正确的代码
// 第三种情况:所删除的节点有两个非空节点
else
{
	// 从被删除结点开始, 找右子树的最小(左)节点
	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;
	}
	// 交换这个节点和要删除节点的 key
	std::swap(del->_key, right_min->_key);

	// 将删除 del 转化为删除 right_min (托孤法删除)
	if (right_min_parent->_left == right_min)
		right_min_parent->_left = right_min->_right;
	else
		right_min_parent->_right = right_min->_right;
	delete right_min;
	right_min = nullptr;
}

以此类推,我们也可以找左子树的最大(右)节点,代码如下: 

else
{
    // 从被删除节点开始, 找左子树的最大(右)节点
    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;
    }
    // 交换这个节点和要删除节点的 key
    std::swap(del->_key, left_max->_key);

    // 将删除 del 转化为删除 left_max (托孤法删除)
    if (left_max_parent->_left == left_max)
    	left_max_parent->_left = left_max->_left;
    else
    	left_max_parent->_right = left_max->_left;
    delete left_max;
    left_max = nullptr;
}

最后,我们将第三种情况汇总,第一种是找右子树的最左节点,第二种是找左子树的最右节点,如下:

else 
{
	// 从被删除节点开始, 找右子树的最小(左)节点 || 找左子树的最大(右)节点
	if (del->_right)
		_erase_right_min_node(del);
	else
		_erase_left_max_node(del);
}

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;
	}
	// 交换这个节点和要删除节点的 key
	std::swap(del->_key, right_min->_key);

	// 将删除 del 转化为删除 right_min (托孤法删除)
	if (right_min_parent->_left == right_min)
		right_min_parent->_left = right_min->_right;
	else
		right_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;
	}
	// 交换这个节点和要删除节点的 key
	std::swap(del->_key, left_max->_key);

	// 将删除 del 转化为删除 left_max (托孤法删除)
	if (left_max_parent->_left == left_max)
		left_max_parent->_left = left_max->_left;
	else
		left_max_parent->_right = left_max->_left;
	delete left_max;
	left_max = nullptr;
}

2.3.4. erase的完整实现如下

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;
					else
						del_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;
					else
						del_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;
}

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;
	}
	// 交换这个节点和要删除节点的 key
	std::swap(del->_key, right_min->_key);

	// 将删除 del 转化为删除 right_min (托孤法删除)
	if (right_min_parent->_left == right_min)
		right_min_parent->_left = right_min->_right;
	else
		right_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;
	}
	// 交换这个节点和要删除节点的 key
	std::swap(del->_key, left_max->_key);

	// 将删除 del 转化为删除 left_max (托孤法删除)
	if (left_max_parent->_left == left_max)
		left_max_parent->_left = left_max->_left;
	else
		left_max_parent->_right = left_max->_left;
	delete left_max;
	left_max = nullptr;
}

二叉树进阶 --- 上,至此结束。

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

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

相关文章

[QNX] BSP 网络性能优化:调优io-pkt和ClockPeriod提升网速

0 概要 本文介绍如何在QNX系统上优化网络性能&#xff0c;主要通过调整io-pkt和ClockPeriod参数来实现。通过优化&#xff0c;网络吞吐量可以得到显著提升。 1 优化方法 1.1 调整io-pkt的mclbytes参数: io-pkt是QNX系统中常用的网络协议栈&#xff0c;其mclbytes参数指定了…

一、精准化测试介绍

精准化测试介绍 一、精准化测试是什么&#xff1f;二、什么是代码插桩&#xff1f;三、两种插桩方式Offine模式&#xff1a;On-the-fly插桩: 四、jacoco覆盖率报告展示五、增量代码覆盖率监控原理六、精准测试系统架构图七、全量与增量覆盖率报告包维度对比八、全量与增量覆盖率…

视频断点上传

什么是断点续传 通常视频文件都比较大&#xff0c;所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制&#xff0c;但是客户的网络环境质量、电脑硬件环境等参差不齐&#xff0c;如果一个大文件快上传完了网断了没有上传完成&#xf…

分布式事务?哪几种方式实现?一文看懂!

什么是分布式事务 分布式事务是指在分布式系统中涉及到多个数据库或多个应用程序之间的事务处理&#xff0c;这些数据库或应用程序可能分布在不同的物理节点上&#xff0c;甚至可能位于不同的地理位置。在分布式事务中&#xff0c;需要确保所有参与者的事务操作都能够保持一致性…

SNMPv3-原理浅谈+报文示例+简易配置

个人认为&#xff0c;理解报文就理解了协议。通过报文中的字段可以理解协议在交互过程中相关传递的信息&#xff0c;更加便于理解协议。 因此本文将在 SNMPv3 协议报文的基础上进行介绍。 SNMPv3 相关 RFC 文档。 关于 SNMPv3 的基本内容介绍&#xff0c;可参考RFC3410-Intro…

vue3中如何更优雅的使用echarts?

echarts在vue或者react中使用存在的问题 每个图表需要从头到尾写地一遍完整的option配置&#xff0c;这样一来的话就会显得十分的冗余在同一个项目中&#xff0c;其实不难发现各类图表设计十分相似&#xff0c;甚至是相同&#xff0c;因此我们没必要一直做重复的工作&#xff…

2.2、Gitea忘记密码重置密码

忘记密码后&#xff0c;管理员可以使用gitea的主程序输入命令重置密码。 gitea admin user change-password --username myname --password asecurepassword

Python多线程与互斥锁模拟抢购余票的示例

一、示例代码&#xff1a; from threading import Thread from threading import Lock import timen 100 # 共100张票def task():global nmutex.acquire() # 上锁temp ntime.sleep(0.1)n temp - 1print(购票成…

树的基本介绍

引入 定义 表示 相关概念 结点&#xff1a;数据元素与指向分支的指针两部分组成 树的深度&#xff1a;树中结点的最大层次 将树A结点(根结点)去掉&#xff0c;树A就变成了森林 区别 实现

vuex核心概念-actions

目录 一、概述 二、应用场景 三、使用步骤 三、注意 四、辅助函数-mapActions 一、概述 目标&#xff1a;明确actions的基本语法&#xff0c;处理异步操作。 需求&#xff1a;一秒钟之后&#xff0c;修改state的count成666。 说明&#xff1a;mutations必须是同步的(便于…

Softing工业推出的edgeConnector将Allen-Bradley控制器集成到工业边缘应用中

2024年4月17日&#xff08;哈尔&#xff09;&#xff0c;Softing宣布扩展其基于Docker的edgeConnector产品系列&#xff0c;推出了新软件模块edgeConnector Allen Bradley PLC&#xff0c;可方便用户访问来自ControlLogix和CompactLogix控制器数据。 &#xff08;edgeConnector…

【小红书采集工具】根据搜索关键词批量采集小红书笔记,含笔记正文、笔记链接、发布时间、转评赞藏等

一、背景介绍 1.1 爬取目标 熟悉我的小伙伴都了解&#xff0c;我之前开发过2款软件&#xff1a; 【GUI软件】小红书搜索结果批量采集&#xff0c;支持多个关键词同时抓取&#xff01; 【GUI软件】小红书详情数据批量采集&#xff0c;含笔记内容、转评赞藏等&#xff0c;支持…

从诊室到云端:医疗大模型的应用挑战与未来探索

从诊室到云端&#xff1a;医疗大模型的应用挑战与未来探索 2023年是中国医疗大模型发展的元年&#xff0c;各种医疗大模型已广泛应用于临床辅助决策、医学研究、健康管理等多个场景。未来&#xff0c;医疗大模型有望实现多模态AI与医疗实践全流程的深入链接&#xff0c;应用于医…

LibreNMS简介

目录 1 LibreNMS简单介绍1.1 LibreNMS介绍 2 安装2.1 Ubuntu安装1、安装依赖2、添加 librenms 用户3、下载 LibreNMS4、设置权限5、安装 PHP 依赖项6、设置时区7、配置 MariaDB8、配置 PHP-FPM9、配置 Web 服务器10、启用 lnms 命令11、配置 snmpd12、cron13、启用调度程序14、…

OS复习笔记ch5-2

引言 在上一篇笔记中&#xff0c;我们介绍到了进程同步和进程互斥&#xff0c;以及用硬件层面上的三种方法分别实现进程互斥。其实&#xff0c;软件层面上也有四种方法&#xff0c;但是这些方法大部分都存在着一些问题&#xff1a; “上锁”与“检查”是非原子操作&#xff0…

Kafka学习-Java使用Kafka

文章目录 前言一、Kafka1、什么是消息队列offset 2、高性能topicpartition 3、高扩展broker 4、高可用replicas、leader、follower 5、持久化和过期策略6、消费者组7、Zookeeper8、架构图 二、安装Zookeeper三、安装Kafka四、Java中使用Kafka1、引入依赖2、生产者3、消费者4、运…

盛邦安全荣获北京市海淀区上地街道财源建设工作表彰

近日&#xff0c;盛邦安全受邀出席上地街道2024年第一季度财源建设工作联席会暨上地人工智能产业报告发布大会并收到上地街道颁发的感谢信&#xff0c;这是对公司技术创新、管理提升、营收增长&#xff0c;持续为上地地区财源建设做出突出贡献的鼓励。 盛邦安全副总裁、董事会秘…

第四百九十九回

文章目录 1. 概念介绍2. 使用方法2.1 固定样式2.2 自定义样式 3. 示例代码4. 内容总结 我们在上一章回中介绍了"GetMaterialApp组件"相关的内容&#xff0c;本章回中将介绍使用get显示SnackBar.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在介…

pyqt颜色变换动画效果

pyqt颜色变换动画效果 QPropertyAnimation介绍颜色变换效果代码 QPropertyAnimation介绍 QPropertyAnimation 是 PyQt中的一个类&#xff0c;它用于对 Qt 对象的属性进行动画处理。通过使用 QPropertyAnimation&#xff0c;你可以平滑地改变一个对象的属性值&#xff0c;例如窗…

【企业必备】提升企业新质生产力,英智推出私有化大模型定制方案

市面上有很多性能不错的大模型&#xff0c;但大部分企业都不敢使用&#xff0c;担心模型训练和使用过程中出现数据泄露的风险。智能化升级是当今时代企业发展的必然趋势&#xff0c;在大模型发展迅猛的时代&#xff0c;借助AI能力的公司能够更快速提升自身的核心竞争力&#xf…