Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >二叉搜索树

二叉搜索树

作者头像
小灵蛇
发布于 2024-06-06 13:30:42
发布于 2024-06-06 13:30:42
10900
代码可运行
举报
文章被收录于专栏:文章部文章部
运行总次数:0
代码可运行

一. 二叉搜索树

1.1 二叉搜索树的概念

二叉搜索树是在普通的二叉树上进阶的,所以咱们今天的内容也可以说是,数据结构二叉树的进阶。二叉搜索树可谓是起到了承上启下的作用,向前承接了数据结构的二叉树,向后对于map和set的模拟实现也起到了启示作用。

那什么是二叉搜索树呢?

我们对于具有以下特征的二叉树为二叉搜索树:

  1. 若左子树不为空,则左子树所有节点的值比根节点的值小
  2. 若右子树不为空,则右子树所有节点的值比根节点的值大
  3. 左右子树都是二叉搜索树

1.2 二叉搜索树的常用操作以及实现

1.2.1 二叉搜索树的查找操作

查找操作要求我们从跟开始找,如果要找的值小于根节点的值,则走左子树,反之走右子树。

那么我们的实现就可以分为非递归写法和递归写法:

(1)非递归写法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//非递归写法
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;
}

(2)递归写法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//递归写法
bool _FindR(Node* root, const K& key)
{
	if (root == nullptr)
	{
		return false;
	}
	if(root->_key>key)
	{
		_FindR(root->_left, key);
	}
	else if(root->_key<key)
	{
		_FindR(root->_right, key);
	}
	else
	{
		return true;
	}
}

bool FindR(const K& key)
{
	return _FindR(_root, key);
}
1.2.2 二叉搜索树的插入操作:

如果树为空的话,只需要新增一个节点,赋值给根节点,如果不为空,则先找到该插入的位置,再插入。

(1)非递归写法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//非递归写法
bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}
	cur = new Node(key);
	if (parent->_key > key)
	{
		parent->_left = cur;
	}
	else if (parent->_key < key)
	{
		parent->_right = cur;
	}
	return true;
}

(2)递归写法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//递归写法
bool _InsertR(Node*& root, const K& key)//注意引用
{
	if (root == nullptr)
	{
		root = new Node(key);
		return true;
	}
	if (root->_key > key)
	{
		return _InsertR(root->_left, key);
	}
	else if (root->_key < key)
	{
		return _InsertR(root->_right, key);
	}
	else
	{
		return false;
	}
}
bool InsertR(const K& key)
{
	return _InsertR(_root, key);
}

此处对于形参root指针的引用可谓是妙到了极点。在我们递归过程中,当找到了该插入的位置时,令root等于新插入的节点,而我们上一层递归加引用则表示上一层递归根节点的左子树或者右子树等于新插入的节点,就不用再去考虑插入的节点与父辈节点之间的连接。

1.2.3 二叉搜索树的删除操作:

首先应该查找要删除的节点在不在,若在则可分为下面四种情况:

  1. 无左右子树
  2. 只有左子树
  3. 只有右子树
  4. 左右子树均存在

那我们来看看该如何实现这几种情况,其实第一种情况可以跟第二和第三种情况合并,那么就剩下三种情况:

对于只有左子树或只有右子树这两种情况,只需删除该节点,再根据删除的节点与父节点之间的连接关系来连接父节点与子节点的关系。

而对于左右子树都有的情况:我们则需要找到右子树中的最小节点,用来替换删除的该节点。

(1)非递归写法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//非递归写法
bool Erase(const K& key)
{
	if (!Find(key))
	{
		return false;
	}
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			if (cur->_left == nullptr)
			{
				if (cur == _root)
				{
					cur = cur->_right;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_right;
					}
					else if (cur == parent->_right)
					{
						parent->_right = cur->_right;
					}
				}
				delete cur;
				return true;
			}
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
				{
					cur = cur->_left;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					else if (cur == parent->_right)
					{
						parent->_right = cur->_left;
					}
				}
				delete cur;
				return true;
			}
			else
			{
				//替换法找右子树最小的元素
				Node* rightminparent = cur;
				Node* rightmin = cur->_right;
				while (rightmin->_left)
				{
					rightminparent = rightmin;
					rightmin = rightmin->_left;
				}
				cur->_key = rightmin->_key;
				if (rightmin == rightminparent->_left)
				{
					rightminparent->_left = rightmin->_right;
				}
				else//当cur是根节点的时候,即while循环都没进去
				{
					rightminparent->_right = rightmin->_right;
				}
				delete rightmin;
				return true;
			}
		}
	}
	return false;
}

(2)递归写法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//递归写法
bool _EraseR(Node*& root, const K& key)
{
	if (root == nullptr)
	{
		return false;
	}
	if (root->_key > key)
	{
		return _EraseR(root->_left, key);
	}
	else if (root->_key < key)
	{
		return _EraseR(root->_right, key);
	}
	else
	{
		Node* del = root;
		if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		else
		{
			Node* rightmin = root->_right;
			while (rightmin->_left)
			{
				rightmin = rightmin->_left;
			}
			swap(root->_key, rightmin->_key);//目的是把他转换为叶子
			return _EraseR(root->_right,key);
		}
		delete del;
		return true;
	}
}
bool EraseR(const K& key)
{
	return _EraseR(_root, key);
}

二. 二叉搜索树的应用

2.1 K模型

K模型即只存储关键码Key,根据关键码就能找到节点。上面的操作都是K模型下的操作。

我们整体看一下K模型的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<class K>
struct BSTreeNode
{
	typedef BSTreeNode<K> Node;
	Node* _left;
	Node* _right;
	K _key;
	BSTreeNode(const K& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};
template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	BSTree() = default;
	BSTree(const BSTree<K>& t)
	{
		_root = Copy(t._root);
	}
	BSTree<K>& operator=(BSTree<K> t)
	{
		swap(_root, t._root);
		return *this;
	}
	~BSTree()
	{
		Destory(_root);
	}
	//非递归写法
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (parent->_key > key)
		{
			parent->_left = cur;
		}
		else if (parent->_key < key)
		{
			parent->_right = 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)
	{
		if (!Find(key))
		{
			return false;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						cur = cur->_right;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
						else if (cur == parent->_right)
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;
					return true;
				}
				else if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						cur = cur->_left;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
						else if (cur == parent->_right)
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
					return true;
				}
				else
				{
					//替换法找右子树最小的元素
					Node* rightminparent = cur;
					Node* rightmin = cur->_right;
					while (rightmin->_left)
					{
						rightminparent = rightmin;
						rightmin = rightmin->_left;
					}
					cur->_key = rightmin->_key;
					if (rightmin == rightminparent->_left)
					{
						rightminparent->_left = rightmin->_right;
					}
					else//当cur是根节点的时候,即while循环都没进去
					{
						rightminparent->_right = rightmin->_right;
					}
					delete rightmin;
					return true;
				}
			}
		}
		return false;
	}
	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}
	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}
	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}
	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}
private:
	//递归写法
	void _Inorder(Node* root)
	{
		if (root == nullptr)
		{
			return ;
		}
		_Inorder(root->_left);
		cout << root->_key<<" ";
		_Inorder(root->_right);
	}
	//递归写法
	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}
		if(root->_key>key)
		{
			_FindR(root->_left, key);
		}
		else if(root->_key<key)
		{
			_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->_left, key);
		}
		else if (root->_key < key)
		{
			return _InsertR(root->_right, key);
		}
		else
		{
			return false;
		}
	}
	//递归写法
	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}
		if (root->_key > key)
		{
			return _EraseR(root->_left, key);
		}
		else if (root->_key < key)
		{
			return _EraseR(root->_right, key);
		}
		else
		{
			Node* del = root;
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				Node* rightmin = root->_right;
				while (rightmin->_left)
				{
					rightmin = rightmin->_left;
				}
				swap(root->_key, rightmin->_key);//目的是把他转换为叶子
				return _EraseR(root->_right,key);
			}
			delete del;
			return true;
		}
	}
	Node* Copy(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		Node* newnode = new Node(root->_key);
		newnode->_left = Copy(root->_left);
		newnode->_right = Copy(root->_right);
		return newnode;
	}
	void Destory(Node* root)
	{
		if (root == nullptr)
		{
			return ;
		}
		Destory(root->_left);
		Destory(root->_right);
		delete root;
	}
	Node* _root;
};

2.2 KV模型

KV模型就是需要存储一个键值对<Key,Value>,对于每一个Key,都有对应的Value。

我们同样也来看一下KV模型的整体代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<class K,class V>
struct BSTreeNode
{
	typedef BSTreeNode<K,V> Node;
	Node* _left;
	Node* _right;
	K _key;
	V _value;

	BSTreeNode(const K& key,const V& value)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
		,_value(value)
	{}
};
template<class K,class V>
class BSTree
{
	typedef BSTreeNode<K,V> Node;
public:
	bool Insert(const K& key, const V& value)
	{
		if (_root == nullptr)
		{
			_root = new Node(key,value);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key,value);
		if (parent->_key > key)
		{
			parent->_left = cur;
		}
		else if (parent->_key < key)
		{
			parent->_right = cur;
		}
		return true;
	}
	Node* 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 cur;
			}
		}
		return nullptr;
	}
	bool Erase(const K& key)
	{
		if (!Find(key))
		{
			return false;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						cur = cur->_right;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->left = cur->_right;
						}
						else if (cur == parent->_right)
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;
					return true;
				}
				else if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						cur = cur->_left;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->left = cur->_left;
						}
						else if (cur == parent->_right)
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
					return true;
				}
				else
				{
					//替换法找右子树最小的元素
					Node* rightminparent = cur;
					Node* rightmin = cur->_right;
					while (rightmin->_left)
					{
						rightminparent = rightmin;
						rightmin = rightmin->_left;
					}
					cur->_key = rightmin->_key;
					if (rightmin == rightminparent->_left)
					{
						rightminparent->_left = rightmin->_right;
					}
					else//当cur是根节点的时候
					{
						rightminparent->_right = rightmin->_right;
					}
					delete rightmin;
					return true;
				}
			}
		}
		return false;
	}
	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}
private:
	void _Inorder(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		_Inorder(root->_left);
		cout << root->_key<<" ";
		_Inorder(root->_right);
	}
	Node* _root=nullptr;
};

三. 二叉搜索树的性能分析

对于比较完美的搜索树,比如下图左边这种情况:

这种时间复杂度是O(logN)的。

而对于右边的这种极端情况,时间复杂度是O(N)的。

总结

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-03-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【C++高阶】二叉搜索树的全面解析与高效实现
二叉搜索树(BST,Binary Search Tree)又称二叉排序树,是一种特殊的二叉树,它或者是一棵空树,或者是具有以下性质的二叉树:
IsLand1314
2024/10/15
1470
【C++高阶】二叉搜索树的全面解析与高效实现
C++:二叉搜索树
二叉搜索树(BST, Binary Search Tree)又叫做二叉排序树,它可以是一颗空树,其性质如下:
二肥是只大懒蓝猫
2023/03/30
2970
C++:二叉搜索树
C++两种方法实现二叉搜索树
a.从根节点出发,如果要找的值大于当前的节点值,去右边找, 如果要找的值小于当前的节点值,则去左边找, 如果相等则表示找到了。 b.如果值存在的话,最多查找高度次
Yui_
2024/10/15
1070
C++两种方法实现二叉搜索树
数据结构初阶 · 二叉搜索树
在最初学习二叉树的时候,就提及到过单独用树来存储数据是既不如链表也不如顺序表的,二叉树的用处可以用来排序,比如堆排序,也可以用来搜索数据,这是二叉树的用处,用来排序可以实现堆,用来搜索数据可以实现二叉搜索树,即今天实现的一种结构。
_lazy
2024/10/16
1290
数据结构初阶 · 二叉搜索树
C++从入门到精通(第十篇) :二叉搜索树
一:二叉搜索树概念 二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 它的左右子树也分别为二叉搜索树 例: int a [] = {5,3,4,1,7,8,2,6,0,9}; 二: 二叉搜索树实现 节点的定义 template <class K> //模板 class TreeNode { public: TreeNode<K>* _left;
雪芙花
2022/10/31
2310
C++从入门到精通(第十篇) :二叉搜索树
【数据结构与算法】AVL二叉搜索树的CRUD实现与应用详解
二叉搜索树(Binary Search Tree)又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
利刃大大
2025/02/03
740
【数据结构与算法】AVL二叉搜索树的CRUD实现与应用详解
C++二叉搜索树与KV模型
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 它的左右子树也分别为二叉搜索树
有礼貌的灰绅士
2023/04/12
4280
C++二叉搜索树与KV模型
二叉搜索树的模拟实现
由于这些特性,就使得在该树中查找值非常的方便,大于目前节点的值,就遍历右子树;小于目前节点的值,就遍历左子树。其次,二叉排序树还有以下特点:不可出现重复数据
用户11039529
2024/08/17
1140
二叉搜索树的模拟实现
C++【二叉搜索树】
时隔多日,又回到了二叉树的学习中,在 C++ 进阶中,我们首先要学习 二叉搜索树,重新捡起二叉树的相关知识,然后会学习 AVL 树 及 红黑树,最后会用红黑树封装实现库中的 set 和 map,作为 C++ 进阶中的难度最高峰,整个学习过程非常艰辛,但 关关难过关关过,让我们先从比较简单的 二叉搜索树 开始学习
北 海
2023/07/01
2060
C++【二叉搜索树】
二叉搜索树模拟实现
二叉搜索树(BST,Binary Search Tree)又称二叉排序树,二叉查找树,主要功能是排序,去重,查找一个值是否存在。
咬咬
2024/06/12
1050
二叉搜索树模拟实现
【C++二叉搜索树】树语静谧:探寻二叉搜索树的内在秩序
我们现在想去找这个4,4比8小,那么我们就没必要去右子树进行查找了,我们直接在左子树进行查找就行了
Undoom
2025/05/29
1110
【C++二叉搜索树】树语静谧:探寻二叉搜索树的内在秩序
C++进阶:二叉搜索树介绍、模拟实现(递归迭代两版本)及其应用
假设我们插入以下元素:5, 3, 7, 1, 4, 6, 8,可以构建如下的二叉搜索树(BST):
是Nero哦
2024/03/21
2820
C++进阶:二叉搜索树介绍、模拟实现(递归迭代两版本)及其应用
C++-你知道二叉搜索树吗?(循环版)
 在二叉搜索树中,右子树上的任意一个节点的值都大于当前节点的值,左子树上的任意一个节点的值都小于当前节点的值,所以查找值的时候效率就很高,在任意位置插入和删除数据也不需要挪动,而且搜索二叉树走中序遍历就是一个升序。
用户10923087
2024/03/02
1380
C++-你知道二叉搜索树吗?(循环版)
【C++】模拟实现二叉搜索(排序)树
二叉搜索树结点(BSTreeNode)需要包含三个要素:键值_key,左指针域_left,右指针域_right.结点(BSTreeNode)逻辑结构图示如下:
修修修也
2024/09/24
1520
【C++】模拟实现二叉搜索(排序)树
【C++高阶】高效搜索的秘密:深入解析搜索二叉树
前言: 在数据结构和算法的广阔领域中,二叉搜索树(Binary Search Tree,简称BST)无疑是一颗璀璨的明星。它以其高效的数据检索能力和独特的树形结构,在计算机科学领域扮演着举足轻重的角色。对于任何对编程和数据结构感兴趣的人来说,掌握二叉搜索树都是至关重要的一步
Eternity._
2024/06/21
2090
【C++高阶】高效搜索的秘密:深入解析搜索二叉树
【二叉树进阶】搜索二叉树(递归+非递归两种版本详解)
我们之前学的普通的二叉树其实意义不大,因为如果只是用二叉树来存储数据的话,还不如直接用链表或者顺序表等这些顺序结构。
YIN_尹
2024/01/23
3440
【二叉树进阶】搜索二叉树(递归+非递归两种版本详解)
【C++】二叉搜索树
1. 搜索树的结点的定义也比较简单,每个结点都有左右子树和自身存储的_key值,_key就是利用搜索树进行搜索时的数据。
举杯邀明月
2023/04/12
3020
【C++】二叉搜索树
【深入C++】二叉搜索树
二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树,其每个节点最多有两个子节点,分别称为左子节点和右子节点。BST具有以下性质:
用户11305458
2024/10/09
1490
【深入C++】二叉搜索树
【C++】二叉搜索树
从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。 最多查找高度次,走到到空,还没找到,这个值不存在
lovevivi
2023/10/16
1870
【C++】二叉搜索树
【C++航海王:追寻罗杰的编程之路】一篇文章带你了解二叉搜索树
二叉搜索树(BST, Binary Search Tree)又称二叉排序树或二叉查找树,它或者是一棵空树,或者具有以下性质的二叉树:
枫叶丹
2024/06/04
1200
【C++航海王:追寻罗杰的编程之路】一篇文章带你了解二叉搜索树
相关推荐
【C++高阶】二叉搜索树的全面解析与高效实现
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验