【C++进阶】二叉树搜索树

news2024/10/7 20:30:45

⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:C++进阶
⭐代码仓库:C++进阶
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!

二叉树搜索树

  • 一、二叉搜索树
    • 1、概念
    • 2、搜索二叉树的实现(非递归版本和递归版本)
      • (1)搭个框架
      • (2)查找
        • i、非递归版本
        • ii、递归版本
      • (3)插入
        • i、非递归版本
        • ii、递归版本
      • (4)删除
        • i、非递归版本
        • ii、递归版本
      • (5)中序遍历
      • (7)拷贝
      • (8)赋值
      • (9)销毁
  • 二、二叉树的性能分析
  • 三、二叉搜索树的应用
    • Key模型
    • Key-Value模型


一、二叉搜索树

1、概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树

以下均是搜索二叉树,简而言之就是左小右大。
在这里插入图片描述

2、搜索二叉树的实现(非递归版本和递归版本)

(1)搭个框架

要实现二叉搜索树,我们需要搭建一个结点类的框架:
##1、结点类当中包含三个成员变量:节点值,左指针,右指针。
##2、结点类只需要一个构造函数即可,用于构造指定节点值的结点。

// 先来个结点的定义
template<class K>
// struct为了默认是开放的
struct BSTreeNode
{
	// 左树右树和值
	BSTreeNode<K>* _right;
	BSTreeNode<K>* _left;
	K _key;
	// 构造一下左右结点和值(构造函数)
	BSTreeNode(const K& key)
		:_right(nullptr)
		, _left(nullptr)
		, _key(key)
	{}
};
// 二叉搜索树的定义
template<class K>
class BSTree
{
public:
	// 结点重命名为Node
	typedef BSTreeNode<K> Node;
	// 构造函数
	BSTree()
		:_root(nullptr)
	{}
private:
	Node* _root;
};

(2)查找

i、非递归版本

从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。最多查找高度次,走到到空,还没找到,这个值不存在。
在这里插入图片描述

// 查找
	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;
			}
			// 找到则返回true
			else
			{
				return true;
			}
		}
		// 找不到返回false
		return false;
	}

ii、递归版本

代码:

// 查找 -- 递归版本
	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}

	// 查找的子函数
	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
			return false;
		if (root->_key < key)
		{
			return _FindR(root->_right, key);
		}
		else if (root->_key > key)
		{
			return _FindR(root->_left, key);
		}
		else
		{
			return true;
		}
	}

解析:
在这里插入图片描述

(3)插入

i、非递归版本

  1. 树为空,则直接新增节点,赋值给root指针
  2. 树不空,按二叉搜索树性质(左小右大)查找插入位置,插入新节点
    (1)若待插入结点的值小于根节点的值,则需要将结点插入到左子树当中。
    (2)若待插入结点的值大于根节点的值,则需要将结点插入到右子树当中。
    (3)若待插入结点的值等于根结点的值,则插入结点失败。
    往后如此进行下去,直到找到与待插入结点的值相同的结点判定为插入失败,或者是最终插入到某叶子结点的左右子树中(即空树当中)。

在这里插入图片描述

// 插入
	bool Insert(const K& key)
	{
		// _root为空
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		// _root不为空
		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;
			}
			// 值相等则返回false
			else
			{
				return false;
			}
		}
		// 创建一个cur的带值的信结点
		cur = new Node(key);
		// 判断一层parent的值与key的值的大小
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;
	}

ii、递归版本

神之一手:& – const K& key – 我下个递归版本是你的引用,我就是你,就不需要传参了。

在这里插入图片描述

// 查找
bool FindR(const K& key)
{
	return _FindR(_root, key);
}
// 插入的子函数
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;
	}
}

(4)删除

相对来讲会比较难一些,首先查找元素是否在二叉搜索树中,如果不存在,则返回false删除失败即可, 否则要删除的结点可能分下面三种情况:

&&&无儿无女型:也只有一种情况就是叶子结点,删除叶子结点,直接将其父亲节点指向空,把这个结点置空即可。
&&&独生家庭型:只有一个子节点,删除自己本身,并链接子节点和父节点。
&&&俩孩子型:可以让待删除的结点找其左子树中最大的结点保存值,然后将待删结点的值用左子树最大节点的值替代,最后将左子树最大结点删掉。或者是找其待删结点右子树当中最小的值的结点保存值,然后将待删结点的值用右子树最小结点的值代替,最后将右子树最小结点删掉。
俩孩子替换结点还有两种情况:倘若替换结点刚好是叶子结点没有孩子,直接删除置空即可。
倘若替换节点有一个孩子(不管左右孩子)就跟独生家庭的模式一模一样了!

解释为什么替换结点要么没孩子要么只有一个孩子:因为左小右大,倘若俩孩子都有,那这个替换节点一定不是左子树最大/右子树最小的结点!

在这里插入图片描述

i、非递归版本

代码层面解析:
实际上有四种:
1、左子树为空
2、右子树为空
3、左右子树都为空
4、要删的刚好是根节点

在这里插入图片描述

// 删除左子树的最右结点
bool Erase(const K& key)
{
	Node* parent = nullptr; // 刚开始定义parent为NULL
	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已经往后找过结点了
					{
						parent->_right = cur->_right; // 直接连接parent的右和cur的右
					}
					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; // 精妙部分就是parent定义为当前节点,因为保证parent节点不为空
				Node* LeftMax = cur->_left;  // 法一:用的是左树的最右节点
				while (LeftMax->_right)
				{
					parent = LeftMax;// parent永远比cur节点往前走一步
					LeftMax = LeftMax->_right;
				}
				swap(cur->_key, LeftMax->_key); // 交换待删结点的值和
				// 判断LeftMax在哪里的问题
				if (parent->_left == LeftMax)
				{
					parent->_left = LeftMax->_left;
				}
				else
				{
					parent->_right = LeftMax->_left;
				}
				cur = LeftMax; // 当前节点就是最右节点
			}
			delete cur;
			return true;
		}
	}
	return false;
}
// 删除右子树的最左节点
// 删除
bool Erase(const K& key)
{
	Node* parent = nullptr; // 刚开始定义parent为NULL
	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已经往后找过结点了
					{
						parent->_right = cur->_right; // 直接连接parent的右和cur的右
					}
					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; // 精妙部分就是parent定义为当前节点,因为保证parent节点不为空
			//	Node* LeftMax = cur->_left;  // 法一:用的是左树的最右节点
			//	while (LeftMax->_right)
			//	{
			//		parent = LeftMax;// parent永远比cur节点往前走一步
			//		LeftMax = LeftMax->_right;
			//	}
			//	swap(cur->_key, LeftMax->_key); // 交换待删结点的值和
			//	// 判断LeftMax在哪里的问题
			//	if (parent->_left == LeftMax)
			//	{
			//		parent->_left = LeftMax->_left;
			//	}
			//	else
			//	{
			//		parent->_right = LeftMax->_left;
			//	}
			//	cur = LeftMax; // 当前节点就是最右节点
			//}
			else
			{
				Node* parent = cur;
				Node* RightMin = cur->_right;
				while (RightMin->_left)
				{
					parent = RightMin;
					RightMin = RightMin->_left;
				}
				swap(cur->_key, RightMin->_key);
				if (parent->_right = RightMin)
				{
					parent->_right = RightMin->_right;
				}
				else
				{
					parent->_right = cur->_left;
				}
				cur = RightMin;
			}
			delete cur;
			return true;
		}
	}
	return false;
}

ii、递归版本

递归版本好理解,
1、若树为空树。则结点删除失败,返回false。
2、若所给的key值小于树的根节点的值,则问题变为删除左子树中值为key的结点。
3、若所给的key值大于树的根节点的值,则问题变为删除右子树中值为key的结点。
4、若所给的key值为根节点的值,则还是找左子树的最小结点或者右子树的最右结点按照下面的步骤进行即可。

根的左子树为空,新结点为根的右
根的右子树为空,新节点为根的左
根的左右子树都不为空,则可以找左子树的最大或者是右子树的最小值,交换然后删除当前节点即可。

// 删除
bool EraseR(const K& key)
{
	return _EraseR(_root, key);
}
	// 删除的子函数
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;
		// 1、左为空
		// 2、右为空
		// 3、左右都不为空
		if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		else
		{
			Node* leftmax = root->_left;
			while (leftmax->_right)
			{
				leftmax = leftmax->_right;
			}
			swap(leftmax->_key, root->_key);
			return _EraseR(root->_left, key);
		}
		delete del;
		return true;
	}
}

(5)中序遍历

思路就是左子树-根-右子树,递归左子树打印再递归右子树。

// 中序遍历
void InOrder()
{
	_InOrder(_root);
	cout << endl;
}
	// 中序遍历的子函数
void _InOrder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}

	_InOrder(root->_left);
	cout << root->_key << " ";
	_InOrder(root->_right);
}

(7)拷贝

拷贝是深拷贝,创建一个copyroot的树然后递归拷贝左子树拷贝右子树,最后返回copyroot的树即可。

// 拷贝构造 -- 深拷贝
BSTree(const BSTree<K>& t)
{
	_root = Copy(t._root);
}
// 拷贝构造子函数
Node* Copy(Node* root)
{
	if (root == nullptr)
		return nullptr;
	Node* copyroot = new Node(root->_key);
	root->_left = Copy(root->_left); // 拷贝左子树
	root->_right = Copy(root->_right); // 拷贝右子树
	return copyroot;
}

(8)赋值

利用swap库函数直接赋值。

// 赋值
BSTree<K>& operator=(BSTree<K> t)
{
	swap(_root, t._root);
	return *this;
}
	

(9)销毁

// 销毁
void Destroy(Node*& root)
{
	if (root == nullptr)
		return;
	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
	root = nullptr;
}

二、二叉树的性能分析

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

最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N/2。

在这里插入图片描述


三、二叉搜索树的应用

Key模型

key的搜索模型,判断关键字在不在

比如我们刷卡进宿舍,链接终端找到这个人的信息即可。
检查一篇英文文章有没有拼写出错。

Key-Value模型

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

比如:中英文的转换,通过英文单词的输入能够找到相对应的中文翻译< word, chinese > 就构成键对值。

统计单词个数,统计这个单词的出现频次,< word, count >。

// 改造二叉搜索树为KV结构
template<class K, class V>
struct BSTNode
{
	BSTNode(const K& key = K(), const V& value = V())
		: _PLeft(nullptr), _PRight(nullptr), _key(key), _Value(value)
	{}
	BSTNode<T>* _PLeft;
	BSTNode<T>* _PRight;
	K _key;
	V _value
};
template<class K, class V>
class BSTree
{
	typedef BSTNode<K, V> Node;
	typedef Node* PNode;
public:
	BSTree() 
		: _PRoot(nullptr) 
	{}
	PNode Find(const K& key);
	bool Insert(const K& key, const V& value);
	bool Erase(const K& key);
private:
	PNode _PRoot;
}

void TestBSTree()
{
	// 输入单词,查找单词对应的中文翻译
	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 == nullptr)
		{
			cout << "单词拼写错误,词库中没有这个单词:" << str << endl;
		}
		else
		{
			cout << str << "中文翻译:" << ret->_value << endl;
		}
	}
}

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

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

相关文章

十三、函数式编程(2)

本章概要 方法引用 Runnable 接口未绑定的方法引用构造函数引用 函数式接口 多参数函数式接口缺少基本类型的函数 方法引用 Java 8 方法引用没有历史包袱。方法引用组成&#xff1a;类名或对象名&#xff0c;后面跟 :: &#xff0c;然后跟方法名称。 interface Callable {…

系统架构设计师-计算机网络

目录 一、计算机网络技术概述 1、网络概述 2、网络有关指标 3、网络分类 4、5G技术 二、组网技术 1、交换技术 2、基本交换原理 三、TCP/IP协议簇 1、DHCP 2、DNS 四、网络规划与设计 一、计算机网络技术概述 1、网络概述 计算机网络的功能&#xff1a; &#xff08;1&…

项目经理升级却面临挑战?如何解决任务分解和成员职责不明难题

有一个朋友刚升任项目经理。但他这两天却一副愁眉不展的样子&#xff0c;因为他之前是做技术的&#xff0c;缺乏管理经验&#xff0c;在制定计划时没有合理的分解任务&#xff0c;并且没有明确项目成员的职责&#xff0c;导致项目在推进过程中项目进度不清晰。 项目管理涉及到…

Mendeley在linux中无法打开APPimage

原因:FUSE 库为用户空间程序提供了一个接口&#xff0c;可以将虚拟文件系统导出到 Linux 内核。由于缺少这个关键库&#xff0c;AppImage 无法按预期工作。 1 安装fuse,打开终端,输入命令 sudo apt install libfuse2 输入用户密码结,果如下 2 确保APPimage作为程序运行 右击…

在阿里云 linux 服务器上查看当前服务器的Nginx配置信息

我们可以通过命令 sudo nginx -t查看到nginx.conf的路径 可以通过 sudo nginx -T查看 nginx 详细配置信息&#xff0c;包括加载的配置文件和配置块的内容 其中也会包括配置文件的内容

【k8s】Kubernetes版本v1.17.3 kubesphere 3.1.1 默认用户登录失败

1.发帖&#xff1a; Kubernetes版本v1.17.3 kubesphere 3.11 默认用户登录失败 - KubeSphere 开发者社区 2. 问题日志&#xff1a; 2.1问题排查方法 &#xff1a; 用户无法登录 http://192.168.56.100:30880/ 2.2查看用户状态 kubectl get users [rootk8s-node1 ~]# k…

Java 多线程系列Ⅶ(线程安全集合类)

线程安全集合类 前言一、多线程使用线性表二、多线程使用栈和队列三、多线程下使用哈希表 前言 在数据结构中&#xff0c;我们学习过 Java 的内置集合&#xff0c;但是我们知道&#xff0c;我们学过的大多数集合类都是线程不安全的&#xff0c;少数如 Vector&#xff0c;Stack…

Fastjson_1.2.24_unserialize_rce漏洞复现

fastjson_1.2.24_unserialize_rce 说明内容漏洞编号CNVD-2017-02833漏洞名称FastJson < 1.2.24 远程代码执行漏洞评级高危影响范围1.2.24漏洞描述通过parseObject/parse将传入的字符串反序列化为Java对象时由于没有进行合理检查修复方案升级组件&#xff0c;打补丁&#xf…

PWmat计算再发Science:用于甲烷热解高效制氢的三元镍钼铋液态合金催化剂

文章信息 原标题: Ternary NiMo-Bi liquid alloy catalyst for efficient hydrogen production from methane pyrolysis 中文标题&#xff1a;用于甲烷热解高效制氢的三元镍钼铋液态合金催化剂 作者&#xff1a;Luning Chen, Zhigang Song, Shuchen Zhang, Chung-Kai Chang…

opencv 基础(持续更新中)

1 前言 https://www.couragesteak.com/ 2 安装 3 基础属性demo 打开一张图片&#xff1a; import cv2img cv2.imread(./girl.jpg)print(img.shape) # (1536, 1024, 3) 数组形状 print(type(img)) # numpy 数组 print(img) # 三维数组&#xff08;彩色图片&am…

基于SSM的校园快递代取系统设计与实现

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

两个好用的数据标注软件labelme和CVAT

我们使用yolov3、yolov4、yolov5、yolov8等训练自己的权重时&#xff0c;需要有大量标注好的数据集&#xff0c;这里有两个好用的数据标注软件labelme和CVAT 一、labelme labelme&#xff1a;https://github.com/wkentaro/labelme 这个软件用的比较多&#xff0c;但是会经常更…

10:00面试,10:06就出来了,问题问的实在有点变态

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降30%,…

poi-tl word模版生成、动态表格、坑点合集

一、配置 1、导入依赖 <dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.10.0</version></dependency>apache poi版本要对应 <dependency><groupId>org.apache.poi</…

Docker安装RabbitMQ集群_亲测成功

先安装Docker Centos7离线安装Docker 华为云arm架构安装Docker RabbitMQ集群模式介绍 RabbitMQ集群搭建和测试总结_亲测 RabbitMQ 有三种模式&#xff1a;单机模式&#xff0c;普通集群模式&#xff0c;镜像集群模式。单机模式即单独运行一个 rabbitmq 实例&#xff0c;而…

【LangChain系列 6】Prompt模版——自定义prompt模版

原文地址&#xff1a;【LangChain系列 6】Prompt模版——自定义prompt模版 本文速读&#xff1a; 自定义prompt模版 LangChain提供了很多默认的prompt模版&#xff0c;同时LangChain提供了两种基础prompt模版&#xff1a; 字符串prompt模版 对话prompt模版 基于这两种模版&…

1-5 AUTOSAR数据交换文件ARXML

目录 一、Arxml文件 二、各类ARXML文件 一、Arxml文件 arxml文件是AUTOSAR&#xff08;Automotive Open System Architecture&#xff09;标准定义的XML文件&#xff0c;用于描述汽车电子系统中的软件组件、通信接口和参数配置等信息。 arxml文件的主要作用是在AUTOSAR架构下…

秋招,面试被问麻了....

前几天组了一个软件测试面试的群&#xff0c;没想到效果直接拉满&#xff0c;看来大家对面试这块的需求还是挺迫切的。昨天我就看到群友们发的一些面经&#xff0c;感觉非常有参考价值&#xff0c;于是我就问他还有没有。 结果他给我整理了一份非常硬核的面筋&#xff0c;打开…

使用STATCOM对电力系统进行潮流分析(Matlab代码实现)

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

遥感数据与作物模型同化技术应用

基于过程的作物生长模拟模型DSSAT是现代农业系统研究的有力工具&#xff0c;可以定量描述作物生长发育和产量形成过程及其与气候因子、土壤环境、品种类型和技术措施之间的关系&#xff0c;为不同条件下作物生长发育及产量预测、栽培管理、环境评价以及未来气候变化评估等提供了…