C++二叉搜索树(二叉树进阶)

news2024/9/18 19:26:23

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

C++二叉搜索树(二叉树进阶)

收录于专栏 [C++进阶学习]
本专栏旨在分享学习C++的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

1. 二叉搜索树的概念

2. 二叉搜索树的操作

 2.1 二叉搜索树的查找

 2.2 二叉搜索树的插入

 2.3 二叉搜索树的删除

3. 二叉搜索树的实现

3.1 二叉搜索树的基本结构

3.2 二叉搜索树的插入操作

3.3 二叉搜索树的查找操作

3.4 二叉搜索树的删除操作

3.5 二叉搜索树的中序遍历

4. 二叉搜索树的的应用

1. K模型:

2. KV模型:

3.KV模型实现 

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


1. 二叉搜索树的概念

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

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

它的左右子树也分别为二叉搜索树

 

由于二插搜索树的性质:二叉搜索树的中序遍历是有序的

2. 二叉搜索树的操作

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

 2.1 二叉搜索树的查找

a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。

b、最多查找高度次,走到到空,还没找到,这个值不存在。

 2.2 二叉搜索树的插入

插入的具体过程如下:

a. 树为空,则直接新增节点,赋值给root指针

b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

 2.3 二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情 况:

a. 要删除的结点无孩子结点

b. 要删除的结点只有左孩子结点

c. 要删除的结点只有右孩子结点

d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下: 

情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除

情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除

情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点 中,再来处理该结点的删除问题--替换法删除

 没有孩子或者一个孩子的情况:

删除两个孩子的节点需要用右子树的最小节点作为替代节点,因为这个替代节点一定比它的左孩子大也一定比它的右孩子小,满足二叉搜索树的性质. 

当删除的节点有两个孩子并且它的右子树没有左孩子时:

直接使用右子树的根节点替换

 

3. 二叉搜索树的实现

3.1 二叉搜索树的基本结构

template<class K>
struct BSTNode
{
	K _key;
	BSTNode<K>* _left;
	BSTNode<K>* _right;

	BSTNode(const K& key)
		:_key(key)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

template<class K>
class BSTree
{
	typedef BSTNode<K> Node;
public:
	bool Insert(const K& key);
	bool Find(const K& key);
	bool Erase(const K& key);
	void InOrder();
private:
	Node* _root = nullptr;
};

这段代码定义了一个简单的二叉搜索树(BST)的结构。BSTNode 是节点的结构体,包含一个键值和两个子节点指针。BSTree 是二叉搜索树的类,提供了插入、查找、删除和中序遍历的接口,_root 是指向树根节点的指针。

3.2 二叉搜索树的插入操作

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->_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;
}

1.检查树是否为空:
如果树为空(即根节点 _root 为 nullptr),则创建一个新的节点并将其设为根节点,然后返回 true 表示插入成功。
2.查找插入位置:
如果树非空,则从根节点开始,遍历树以找到适当的插入位置。parent 记录当前节点的父节点,而 cur 遍历树以查找插入点。根据键值与当前节点的比较,决定向左子树还是右子树移动。若找到相同的键值,返回 false 表示键值已存在,插入失败。
3.插入新节点:
找到插入位置后,创建一个新节点 cur 并将其作为 parent 的子节点。根据键值比较结果,将新节点插入为父节点的右子节点或左子节点。
4.返回插入成功:
插入操作完成,返回 true 表示成功插入新节点。

 

3.3 二叉搜索树的查找操作

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

3.4 二叉搜索树的删除操作

bool Erase(const K& key)
{
	Node* parent = nullptr;
	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
		{
			// 删除
			// 0-1个孩子的情况
			if (cur->_left == nullptr)
			{
				if (parent == nullptr)
				{
					_root = cur->_right;
				}
				else
				{
					if (parent->_left == cur)
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;
				}

				delete cur;
				return true;
			}
			else if (cur->_right == nullptr)
			{
				if (parent == nullptr)
				{
					_root = cur->_left;
				}
				else
				{
					if (parent->_left == cur)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;
				}

				delete cur;
				return true;
			}
			else
			{
				// 2个孩子的情况
				// 右子树的最小节点作为替代节点
				Node* rightMinP = cur;
				Node* rightMin = cur->_right;
				while (rightMin->_left)
				{
					rightMinP = rightMin;
					rightMin = rightMin->_left;
				}

				cur->_key = rightMin->_key;

				if (rightMinP->_left == rightMin)
					rightMinP->_left = rightMin->_right;
				else
					rightMinP->_right = rightMin->_right;

				delete rightMin;
				return true;
			}
		}
	}

	return false;
}

 删除的节点是根节点或者只有一个孩子时(左孩子||右孩子):

1. 节点没有左子树(cur->_left == nullptr)

if (cur->_left == nullptr)
{
    if (parent == nullptr)
    {
        _root = cur->_right;
    }
    else
    {
        if (parent->_left == cur)
            parent->_left = cur->_right;
        else
            parent->_right = cur->_right;
    }

    delete cur;
    return true;
}

cur->_left == nullptr当前节点没有左子树,只可能有右子树或没有子树。
parent == nullptr:当前节点是根节点。将根节点 _root 更新为当前节点的右子树。
parent != nullptr:当前节点是某个节点的子节点。根据 parent->_left == cur 判断当前节点是其父节点的左子节点还是右子节点,并将父节点的对应子节点指针更新为当前节点的右子树。

2. 节点没有右子树(cur->_right == nullptr) 

else if (cur->_right == nullptr)
{
    if (parent == nullptr)
    {
        _root = cur->_left;
    }
    else
    {
        if (parent->_left == cur)
            parent->_left = cur->_left;
        else
            parent->_right = cur->_left;
    }

    delete cur;
    return true;
}

cur->_right == nullptr:当前节点没有右子树,只可能有左子树或没有子树。
parent == nullptr:当前节点是根节点。将根节点 _root 更新为当前节点的左子树。
parent != nullptr:当前节点是某个节点的子节点。根据 parent->_left == cur 判断当前节点是其父节点的左子节点还是右子节点,并将父节点的对应子节点指针更新为当前节点的左子树。

 3. 节点两个孩子都不为空

else
{
				// 2个孩子的情况
				// 右子树的最小节点作为替代节点
				Node* rightMinP = cur;
				Node* rightMin = cur->_right;
				while (rightMin->_left)
				{
					rightMinP = rightMin;
					rightMin = rightMin->_left;
				}

				cur->_key = rightMin->_key;

				if (rightMinP->_left == rightMin)
					rightMinP->_left = rightMin->_right;
				else
					rightMinP->_right = rightMin->_right;

				delete rightMin;
				return true;
}

1. 找到右子树的最小节点 

Node* rightMinP = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
    rightMinP = rightMin;
    rightMin = rightMin->_left;
}

rightMinP:指向 rightMin 的父节点。
rightMin:初始化为当前节点 (cur) 的右子节点。
while (rightMin->_left):在右子树中向下遍历,找到最小节点。最小节点是右子树中最左边的节点。
    更新 rightMinP 为当前节点(rightMin)。
    更新 rightMin 为 rightMin 的左子节点。 

 2. 替代当前节点的值

cur->_key = rightMin->_key;

 cur->_key = rightMin->_key:将 rightMin 节点的键值复制到当前节点 cur。这样 cur 节点的键值就与要删除的节点的键值一致。

 3. 删除右子树最小节点

if (rightMinP->_left == rightMin)
    rightMinP->_left = rightMin->_right;
else
    rightMinP->_right = rightMin->_right;

delete rightMin;

rightMinP->_left == rightMin:检查 rightMin 是否是 rightMinP 的左子节点。
    如果是,则将 rightMinP 的左子节点指向 rightMin 的右子节点(因为 rightMin 节点没有左子树,右子节点可能存在)。
else:如果 rightMin 是 rightMinP 的右子节点。
    将 rightMinP 的右子节点指向 rightMin 的右子节点。
delete rightMin:释放 rightMin 节点的内存,因为它已经被当前节点取代。
 

3.5 二叉搜索树的中序遍历

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

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

4. 二叉搜索树的的应用

1. K模型:

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树

在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

2. KV模型:

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

该种方式在现实生活中非常常见:

比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文就构成一种键值对

再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是就构成一种键值对

3.KV模型实现 

//key-value
	template<class K, class V>
	class BSTree
	{
		typedef BSTNode<K, V> Node;
	public:
		BSTree() = default;

		BSTree(const BSTree<K, V>& t)
		{
			_root = Copy(t._root);
		}

		~BSTree()
		{
			Destroy(_root);
			_root = nullptr;
		}

		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->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(key, value);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = 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)
		{
			Node* parent = nullptr;
			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
				{
					// 删除
					// 0-1个孩子的情况
					if (cur->_left == nullptr)
					{
						if (parent == nullptr)
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_left == cur)
								parent->_left = cur->_right;
							else
								parent->_right = cur->_right;
						}

						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						if (parent == nullptr)
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_left == cur)
								parent->_left = cur->_left;
							else
								parent->_right = cur->_left;
						}

						delete cur;
						return true;
					}
					else
					{
						// 2个孩子的情况
						// 右子树的最小节点作为替代节点
						Node* rightMinP = cur;
						Node* rightMin = cur->_right;
						while (rightMin->_left)
						{
							rightMinP = rightMin;
							rightMin = rightMin->_left;
						}

						cur->_key = rightMin->_key;

						if (rightMinP->_left == rightMin)
							rightMinP->_left = rightMin->_right;
						else
							rightMinP->_right = rightMin->_right;

						delete rightMin;
						return true;
					}
				}
			}

			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

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

		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;

			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}

		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;

			Node* newRoot = new Node(root->_key, root->_value);
			newRoot->_left = Copy(root->_left);
			newRoot->_right = Copy(root->_right);

			return newRoot;
		}

	private:
		Node* _root = nullptr;
	};
}

 实现和key模型差不多,这里就不在过多解释.

测试:

int main()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	keyValue::BSTree<string, int> countTree;
	for (const auto& str : arr)
	{
		// 先查找水果在不在搜索树中
		// 1、不在,说明水果第一次出现,则插入<水果, 1>
		// 2、在,则查找到的节点中水果对应的次数++
		//BSTreeNode<string, int>* ret = countTree.Find(str);
		auto ret = countTree.Find(str);
		if (ret == NULL)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}

	countTree.InOrder();

	return 0;
}

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

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二 叉搜索树的深度的函数,即结点越深,则比较次数越多。 

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树: 

 

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

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

问题:

如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插 入关键码,二叉搜索树的性能都能达到最优?那么我们的重量级AVL树和红黑树就可以上场了。 感兴趣的宝子们赶紧点赞关注支持一下吧!我马上回更新下一个内容!

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

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

相关文章

Java重修笔记 第五十七天 坦克大战(七)多线程基础 - 编程练习

1. 线程之间的协调控制&#xff08;通知方式&#xff09; public class Homework04 {public static void main(String[] args) {// 在 main 方法中启动两个线程// 第一个线程内循环打印 1 到 100 以内的整数// 直到第二个线程从键盘读取到 "Q" 指令后结束第一个线程…

Porcupine - 语音关键词唤醒引擎

文章目录 一、关于 Porcupine特点用例尝试一下 语言支持性能 二、Demo1、Python Demo2、iOS DemoBackgroundService DemoForegroundApp Demo 3、网页 Demo3.1 Vanilla JavaScript 和 HTML3.2 Vue Demos 三、SDK - Python 一、关于 Porcupine Porcupine 是一个高度准确和轻量级…

LC并联电路在正弦稳态下的传递函数推导(LC并联谐振选频电路)

LC并联电路在正弦稳态下的传递函数推导&#xff08;LC并联谐振选频电路&#xff09; 本文通过 1.解微分方程、2.阻抗模型两种方法推导 LC 并联选频电路在正弦稳态条件下的传递函数&#xff0c;并通过仿真验证不同频率时 vo(t) 与 vi(t) 的幅值相角的关系。 电路介绍 已知条件…

Axure RP实战:打造高效图形旋转验证码

Axure RP实战&#xff1a;打造高效图形旋转验证码 在数字产品设计的海洋中&#xff0c;验证码环节往往是用户交互体验的细微之处&#xff0c;却承载着验证用户身份的重要任务。 传统的文本验证码虽然简单直接&#xff0c;但随着用户需求的提高和设计趋势的发展&#xff0c;它…

vue2的diff算法

Vue2 的虚拟 DOM diff 算法是一种高效的算法&#xff0c;用于比较新旧两个虚拟 DOM 树&#xff0c;找出差异并更新到真实 DOM 上。这个算法的核心在于尽量减少不必要的 DOM 操作&#xff0c;提高性能。 虚拟dom&#xff1a;把DOM数据化&#xff0c;先通过不断地操作数据&#…

如何在手机端跑大模型?

最近新入手了一台 arm 开发板&#xff0c;内置安装了 Android 13 系统。 昨天把网络问题给解决了&#xff1a;安卓连接 WIFI 但无法上网&#xff1f;盘点踩过的那些坑 今日分享&#xff0c;继续带大家实操&#xff1a;如何把大模型&#xff08;LLM&#xff09;部署到移动端&a…

文章资讯职场话题网站源码整站资源自带2000+数据

介绍&#xff1a; 数据有点多&#xff0c;数据资源包比较大&#xff0c;压缩后还有250m左右。值钱的是数据&#xff0c;网站上传后直接可用&#xff0c;爽飞了 环境&#xff1a;NGINX1.18 mysql5.6 php7.2 代码下载

JUC学习笔记(三)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 八、共享模型之工具--JUC8.1 AQS 原理1. 概述2 实现不可重入锁自定义同步器自定义锁 3.心得起源目标设计1) state 设计2&#xff09;阻塞恢复设计3&#xff09;队列…

学习笔记 韩顺平 零基础30天学会Java(2024.9.16)

P563 自定义泛型方法 当调用方法时&#xff0c;要传入参数&#xff0c;因为当传入参数时&#xff0c;编译器就可以确定泛型代表的类型 泛型方法和方法使用了泛型是不一样的 泛型方法可以使用类声明的泛型&#xff0c;也可以使用自己的泛型 P564 泛型方法练习 P565 泛型的继承和…

Python编码系列—Python适配器模式:无缝集成的桥梁

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

二叉树OJ题——另一棵树的子树

文章目录 一、题目链接二、解题思路三、解题代码 一、题目链接 另一棵树的子树 题目描述&#xff1a;判断当前树A是否是树B的子树。 二、解题思路 时间复杂度&#xff1a;O(n*m) 三、解题代码

Learn ComputeShader 15 Grass

1.Using Blender to create a single grass clump 首先blender与unity的坐标轴不同&#xff0c;z轴向上&#xff0c;不是y轴 通过小键盘的数字键可以快速切换视图&#xff0c;选中物体以后按下小键盘的点可以将物体聚焦于屏幕中心 首先我们创建一个平面&#xff0c;宽度为0.2…

AI替代插画师跟设计师?不用焦虑!

一个固定的工作流&#xff0c; 一个训练好的lora模型 输入一段提示词 二三十秒的时间&#xff0c;就能生成一张精致美观有韵味的中秋国风插画 这张不喜欢&#xff0c;改下提示词重新生成一张不一样的。还是二十几秒 同样的插画&#xff0c;你用手绘&#xff0c;从起稿到上…

大数据新视界 --大数据大厂之MongoDB与大数据:灵活文档数据库的应用场景

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

【设计模式-外观】

这里写自定义目录标题 定义UML图角色作用代码使用场景 定义 为子系统中一组相关接口提供一致界面&#xff0c;定义一个高级接口&#xff0c;使得子系统更加容易使用。 UML图 角色作用 外观&#xff08;Facade&#xff09;角色&#xff1a;这是外观模式的核心&#xff0c;它知…

MongoDB的详细安装教程

6、MongoDB安装 6.1 为什么使用MongoDB 性能好大规模数据存储&#xff08;可拓展性&#xff09;可靠安全&#xff08;本地复制、自动故障转移&#xff09;方便存储复杂数据结构 6.2 下载安装 【1】下载地址&#xff0c;这里下载的是5.0版本的&#xff0c;否则配置环境变量之…

【电路笔记】-差分运算放大器

差分运算放大器 文章目录 差分运算放大器1、概述2、差分运算放大器表示2.1 差分模式2.2 减法器模式3、差分放大器示例3.1 相关电阻3.2 惠斯通桥3.3 光/温度检测4、仪表放大器5、总结1、概述 在之前的文章中,我们讨论了反相运算放大器和同相运算放大器,我们考虑了在运算放大器…

revisiting拉普拉斯模板

二维向量的二阶微分是Hessian矩阵&#xff0c;拉普拉斯算子是将两个独立的二阶微分求和&#xff0c;对二阶微分的近似。 我不认同冈萨雷斯的8邻域拉普拉斯模板。 MATLAB图像处理工具箱中fspecial函数’laplacian’参数给的拉普拉斯模板&#xff1a; 对于数字滤波器&#xff…

中秋前夕-我居然使用技术来鞭策兄弟

中秋前夕-我居然使用技术来鞭策兄弟 前言 最近在带领一些小伙伴在完成功能&#xff0c;因为人数不少&#xff0c;那么我们如何统计大家有没有摸鱼偷懒呢&#xff1f; 聪明的朋友们可以想到&#xff0c;利用git的提交记录统计。 因为git提交时&#xff0c;会给我们带上一些关…

高德2.0 多边形覆盖物无法选中编辑

多边形覆盖物无法选中编辑。先检查一下数据的类型得是<number[]>,里面是字符串的虽然显示没问题&#xff0c;但是不能选中编辑。 &#xff08;在项目中排查了加载时机&#xff0c;事件监听…等等种种原因&#xff0c;就是没发现问题。突然想到可能是数据就有问题&#xf…