C++——一种特殊的二叉搜索树之AVL树

news2025/1/18 7:30:24

目录

  • 序言
  • 1 AVL树的概念
  • 2 AVL树节点的定义
  • 3 AVL树的插入
    • 是否继续更新依据:子树的高度是否变化
  • 4 AVL树的旋转
    • 旋转的原则:
      • 1. 新节点插入较高左子树的左侧---左左:右单旋
      • 2. 新节点插入较高右子树的右侧---右右:左单旋
      • 3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋
      • 4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋
  • 5.如何验证一颗树是AVL树的验证?
  • 6 AVL树的删除(了解)
  • 7 AVL树的性能

接——map和set的应用总结

序言

前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

1 AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
    在这里插入图片描述
    如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)。

2 AVL树节点的定义

AVL树节点的定义:
平衡因子(_bf)=右子树的高度-左子树的高度

struct AVLTreeNode
{
	pair<K, V> _kv;//键值对
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	int _bf;  // 平衡因子 balance factor=右子树的高度-左子树的高度

	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};

3 AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。
那么AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

是否继续更新依据:子树的高度是否变化

下面拿一个右单旋的图来说明一下parent、subL、subLR三者之间的关系,让读者更能理解这里向上更新平衡因子的逻辑。
在这里插入图片描述

更新之后如果:(_bf为该节点的平衡因子,平衡因子=右子树的高度-左子树的高度

  • parent->_bf == 0,那么之前parent->_bf是 1 或者 -1。
    说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树整体高度不变,对于parent的双亲结点没有影响,不需要继续往上更新。

  • parent->_bf == 1 或 -1,之前是parent->_bf == 0。
    说明插入前两边一样高,现在插入一边更高了,parent所在子树高度变了,继续往上更新。

  • parent->_bf == 2 或 -2,之前parent->_bf == 1 或者 -1。
    现在插入一个新节点后严重不平衡,违反规则需要就地处理——旋转,如何旋转?下面会有介绍。

AVL树的插入代码整体框架:

bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)//如果当前是空树
	{
		_root = new Node(kv);
		return true;
	}
	
	//向下找该结点的插入位置
	//开始向下寻找该要被插入结点的父节点
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;//往右走
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;//往左走
		}
		else
		{
			return false;//不能插入相等(重复)的值
		}
	}
	//找到位置然后正确的将结点插入
	cur = new Node(kv);
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}

	// 1、开始向上更新 平衡因子
	while (parent) // parent为空,也就更新到根了
	{
		// 新增在右,parent->bf++;
		// 新增在左,parent->bf--;
		if (cur == parent->_left)
		{
			parent->_bf--;
		}
		else
		{
			parent->_bf++;
		}
		
		//parent->_bf == 0,那么之前parent->_bf是 1 或者 -1
		//说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树整体高度不变,
		//对于parent的双亲结点没有影响,不需要继续往上更新,退出更新的循环
		if (parent->_bf == 0)
		{
			break;
		}
		//parent->_bf == 1 或 -1,说明之前是parent->_bf == 0,两边一样高,
		//现在插入一边更高了,parent所在子树高度变了,继续往上更新。
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			cur = parent;
			parent = parent->_parent;
		}
		// parent->_bf == 2 或 -2,说明之前parent->_bf == 1 或者 -1,
		// 现在插入严重不平衡,违反规则需要就地处理——旋转
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//左单旋
			if (parent->_bf == 2 && cur->_bf == 1)
			{
				RotateL(parent);
			}
			//右单旋
			else if (parent->_bf == -2 && cur->_bf == -1)
			{
				RotateR(parent);
			}
			//左右双旋
			else if (parent->_bf == -2 && cur->_bf == 1)
			{
				RotateLR(parent);
			}
			//右左双旋
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				RotateRL(parent);
			}
			
			else
			{
				assert(false);
			}
			break;
		}
		else
		{
			assert(false);
		}
	}

	return true;
}

4 AVL树的旋转

旋转的原则:

1、让这颗子树左右高度不超过1
2、旋转过程中继续保持是搜索树
3、更新调整节点的平衡因子
4、让这颗子树的高度跟插入前保持一致

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

1. 新节点插入较高左子树的左侧—左左:右单旋

在这里插入图片描述
上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。

在旋转过程中,编写程序时有以下几种情况需要考虑:

  • 30节点的右孩子可能存在,也可能不存在
  • 60可能是根节点,也可能是子树。如果是根节点,旋转完成后,要更新根节点;如果是子树,可能是某个节点的左子树,也可能是右子树。

代码及注释:

void RotateR(Node* parent)
{
	// SubL: parent的左孩子
	// SubLR: parent左孩子的右孩子,注意:该结点可能为空
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	// 旋转完成之后,30的右孩子作为60的左孩子
	parent->_left = subLR;
	// 并且如果30的右孩子存在,因为时三叉连,需要更换其双亲结点parent为60
	if (subLR)
	{
		subLR->_parent = parent;
	}

	// 因为60可能是棵子树,因此在更新其双亲前必须先保存60的双亲
	Node* ppNode = parent->_parent;
	// 60 作为 30的右孩子
	subL->_right = parent;
	// 更新60的双亲
	parent->_parent = subL;

	// 如果60是根节点,根新指向根节点的指针
	//if (_root == parent)
	if (ppNode == nullptr)
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	else
	{
		// 如果60是子树,可能是其双亲的左子树,也可能是右子树
		if (ppNode->_left == parent)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}
		//更新subL也就是30的双亲结点
		subL->_parent = ppNode;
	}
	// 根据调整后的结构更新部分节点的平衡因子,看图调整即可,右单旋都是固定的
	subL->_bf = parent->_bf = 0;
}

2. 新节点插入较高右子树的右侧—右右:左单旋

在这里插入图片描述

实现及情况考虑可参考右单旋,这里就不多赘述了。
参考代码:

void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;

	Node* ppNode = parent->_parent;
	subR->_left = parent;
	parent->_parent = subR;

	if (ppNode == nullptr)
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = subR;
		}
		else
		{
			ppNode->_right = subR;
		}

		subR->_parent = ppNode;
	}

	parent->_bf = subR->_bf = 0;
}

3. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋

在这里插入图片描述

将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。
判断整体的平衡因子的重要依据就是看60旋转前的平衡因子,从图中可以知道,60的左右孩子分别变成30的右孩子和90的左孩子,所以最终平衡因子的判断只需要看是

  • 在60的左边b新增(平衡因子:30->0,60->0,90->1)
  • 还是在60右边c新增(平衡因子:30->-1,60->0,90->0)
  • 60自己就是新增的结点,那么之前a、d的高度一定是0(否则插入60之前就不是一颗AVL树了)(平衡因子:30->0,60->0,90->0)
    在这里插入图片描述
void RotateLR(Node* parent)
{
	Node* subL = parent->_left;//30
	Node* subLR = subL->_right;//60

	//最后判断整体的平衡因子的重要依据就是看60的平衡因子,从图中可以知道,60的左右孩子分别变成,
	//30的有孩子和90的左孩子,所以最终平衡因子的判断只需要看在60的左边b新增,还是右边c新增即可
	int bf = subLR->_bf;
	//对30进行左旋
	RotateL(parent->_left);
	//对90进行右旋
	RotateR(parent);

	if (bf == -1) // 60左子树新增
	{
		subL->_bf = 0;
		parent->_bf = 1;
		subLR->_bf = 0;
	}
	else if (bf == 1) // 60右子树新增
	{
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else if (bf == 0) // 60自己就是新增结点
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}//其他情况就报断言错误
	else
	{
		assert(false);
	}
}

4. 新节点插入较高右子树的左侧—右左:先右单旋再左单旋

具体细节可参考左右双旋,先对90进行右单旋,然后再对30进行左单旋,旋转完成后再考虑平衡因子的更新。
判断整体的平衡因子的重要依据就是看60旋转前的平衡因子,从图中可以知道,60的左右孩子分别变成30的右孩子和90的左孩子,所以最终平衡因子的判断只需要看在60的左边b新增,还是右边c新增,或者说自己就是新增的结点即可。
在这里插入图片描述

代码:

void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;
	RotateR(parent->_right);
	RotateL(parent);

	if (bf == 1)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = -1;
	}
	else if (bf == -1)
	{
		subR->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

总结:
假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑:

  • parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为subR
    当subR的平衡因子为1时,执行左单旋
    当subR的平衡因子为-1时,执行右左双旋
  • parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为subL
    当subL的平衡因子为-1是,执行右单旋
    当subL的平衡因子为1时,执行左右双旋

旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新。


5.如何验证一颗树是AVL树的验证?

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  2. 验证其为平衡树
  • 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
  • 节点的平衡因子是否计算正确
void Inorder()//封装中序遍历函数,防止直接访问根节点_root
{
	_Inorder(_root);
}

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

	_Inorder(root->_left);
	cout << root->_kv.first << ":" << root->_kv.second << endl;
	_Inorder(root->_right);
}

int Height(Node* root)
{
	if (root == nullptr)
		return 0;
	
	int lh = Height(root->_left);
	int rh = Height(root->_right);

	return lh > rh ? lh + 1 : rh + 1;
}

bool IsBalance()//封装判断平衡函数,防止直接访问根节点_root
{
	return IsBalance(_root);
}

bool IsBalance(Node* root)
{
	if (root == nullptr)
	{
		return true;
	}

	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);

	if (rightHeight - leftHeight != root->_bf)
	{
		cout <<root->_kv.first<<"平衡因子异常" << endl;//和当前标识的平衡因子不符
		return false;
	}

	return abs(rightHeight - leftHeight) < 2//平衡因子大于等于2就不是一个AVL树了
		&& IsBalance(root->_left)
		&& IsBalance(root->_right);
}

6 AVL树的删除(了解)

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。具体实现可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。

7 AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,通过频繁的旋转才达到这样的绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
红黑树会减少一些旋转次数,插入数据时相比AVL树就不会那么严格。

AVL树完整代码链接

推荐阅读:C++——一种特殊的二叉搜索树之红黑树

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

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

相关文章

Java基础之File

文章目录一、File的声明二、File的创建2.1 创建一个文件2.2 创建一个文件夹2.3 创建一个多级文件夹三、File的删除四、File的获取与判断4.1 获取一个文件夹孩子层所有文件和文件夹&#xff0c;并存入数组4.2 判断一个File对象是否为文件4.3 判断一个File对象是否为文件夹4.4 判…

Abp框架安全升级指南

本文将从GB/T 28448-2019《信息安全技术 网络安全等级保护测评要求》规定的安全计算环境中解读、摘要若干安全要求&#xff0c;结合Abp框架&#xff0c;对站点进行安全升级。 【身份鉴别】应对登录的用户进行身份标识和鉴别&#xff0c;身份标识具有唯一性&#xff0c;身份鉴别…

实验手册 - 第2周Spark RDD

目录标题1 实验内容实验1实验2实验3实验4实验5实验62 实验总结2.1 Spark应用开发步骤2.2 字符串的split()方法列表解析式2.3 常用的Action操作2.4 常用的Transformation操作2.5 RDD间的Transformation操作1 实验内容 查看当前工作目录 import os os.getcwd()D:\\juniortwo\\s…

【JUC】Java内存模型之JMM

【JUC】Java内存模型之JMM 文章目录【JUC】Java内存模型之JMM1. 概念2. JMM三大特性2.1 可见性2.2 原子性2.3 有序性3. 多线程对变量的读写过程4. 先行发生原则——happens-before4.1 happens-before八条规则4.1.1 次序规则4.1.2 锁定规则4.1.3 volatile变量规则4.1.4 传递规则…

【Unity入门】13.脚本外置参数

【Unity入门】脚本外置参数 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;外置脚本参数 &#xff08;1&#xff09;外置自转脚本的速度参数 我们在RotateLogic的时候&#xff0c;为了实现自…

ubuntu虚拟机下搭建zookeeper集群,安装jdk压缩包,搭建Hadoop集群与spark集群的搭建【下篇】

系列文章目录 Hadoop与主机连接以及20版本的Hadoop配置网络的问题_hadoop连不上网 Hadoop升级update命令被锁定的解决方法_hadoop重新初始化被锁住怎么办虚拟机vmware下安装Ubuntu16.04修改屏幕尺寸与更新源&#xff0c;以及对应的安装vim和vim常见的操作命令 文章目录 前言…

ELK部署-实现Nginx日志收集

一、部署ES 1、创建网络下载镜像 docker network create elastic docker pull elasticsearch:7.17.62、目录准备 mkdir /opt/ELK/elastic/{data,config} -p chmod 777 /opt/ELK/elastic/datacat >> /opt/ELK/elastic/config/elasticsearch.yml <<EOF cluster.na…

DFS与BFS寻找图中的所有路径(C++)

文章目录图的存储理论知识数组模拟链表数组模拟邻接表DFS 寻找所有路径代码输入数据对应图输出BFS 寻找所有路径代码输入数据对应图输出备注写在后面图的存储 理论知识 图的存储主要有 2 种方式 邻接表邻接矩阵 邻接矩阵不适合存储稀疏图&#xff0c;本文使用邻接表来存储图 …

运用Navicat 实现 DML(对表的数据进行增删改)

如何使用Navicat呢&#xff1f; 当Navicat配置好后&#xff0c;链接上数据库后。 点击查询后tables中的任意一个新建查询&#xff0c;这时就会跳出一个查询编辑器。 我在初始sql是就创建了stu表。这里就不创建了。 先选择需要的表&#xff0c; select * from 表名; 添加&…

【JAVA】经典面试题:HashMap,Hashtable和ConcurrentHashMap三者之间的区别!!!

本篇的内容是围绕哈希表来展开的&#xff0c;主要是通对HashMap&#xff0c;Hashtable&#xff0c;ConcurrentHashMap三者的特点去了解这它们之间的区别以及运用场景 目录 1. HashMap 2. Hashtable 锁太粗问题&#xff1a; 3. 扩容机制问题 3. ConcurrentHashMap Concurr…

N5183B信号发生器

N5183B N5183B,是德keysight N5183B 主要特性与技术指标信号特征9 kHz &#xff5e; 3 或 6 GHz在 3 GHz 时提供 24 dBm 功率&#xff0c;带有电子衰减器1 GHz 和 20 kHz 偏置时&#xff0c;相位噪声为 -146 dBc≤-73 dBc ACP W-CDMA 64 DPCH 和 <0.4% EVM 160 MHz 802.11…

万字长文解读Stable Diffusion的核心插件—ControlNet

目录 一、介绍 二、使用方法 三、ControlNet结构 1.整体结构 2.ControlLDM 3.Timestep Embedding 4.HintBlock 5.ResBlock 6.SpatialTransformer 7.SD Encoder Block 8.SD Decoder Block 9.ControlNet Encoder Block 10.Stable Diffusion 四、训练 1.准备数据集…

stable-diffusion-webui浅叙

GitHub - AUTOMATIC1111/stable-diffusion-webui: Stable Diffusion web UI 使用Git下载&#xff1a; git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git 运行 webui-user.bat : git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.g…

【NestJs】使用MySQL创建多个实体

如果小伙伴还不会使用nestjs连接数据库的话 可以看我的上一篇文章 NestJs使用连接mysql企业级开发规范 关系 关系是指两个或多个表之间的联系。关系基于每个表中的常规字段&#xff0c;通常包含主键和外键。关系有三种&#xff1a; 名称说明一对一主表中的每一行在外部表中有…

从零到一发布 NPM 包

如果你负责前端的基础能力建设&#xff0c;发布各种功能/插件包犹如家常便饭&#xff0c;所以熟悉对 npm 包的发布与管理是非常有必要的&#xff0c;故此有了本篇总结文章。本篇文章一方面总结&#xff0c;一方面向社区贡献开箱即用的 npm 开发、编译、发布、调试模板&#xff…

【展会邀请】百华与您相约第104届中国劳动保护用品交易会!

重磅消息&#xff01;一场行业极具规模的劳保展 第104届中国劳动保护用品交易会 暨2023中国国际职业安全及健康产业博览会 将于2023.4.13-15在上海新国际博览中心E1-E7馆隆重举办&#xff01; 山东百华鞋业有限公司受邀参展&#xff0c;正在火热筹备中。 百华展位号 2023…

算法:将一个数组旋转k步

题目 输入一个数组如 [1,2,3,4,5,6,7]&#xff0c;输出旋转 k 步后的数组。 旋转 1 步&#xff1a;就是把尾部的 7 放在数组头部前面&#xff0c;也就是 [7,1,2,3,4,5,6]旋转 2 步&#xff1a;就是把尾部的 6 放在数组头部前面&#xff0c;也就是 [6,7,1,2,3,4,5]… 思路 思…

PasteSpider的下载和安装

你是否在纠结于k8s的庞大和复杂&#xff0c;是否在被混论的发布流程搞得焦头烂额。PasteSpider适合你&#xff01;足够小的内存资源消耗(300MB甚至更低&#xff01;)&#xff0c;不需要专业的运维知识&#xff0c;图文操作&#xff0c;支持一键发布&#xff0c;支持自动路由配置…

泛型基本说明

使用传统方法的问题分析 不能对加入到集合ArrayList中的数据类型进行约束&#xff08;不安全&#xff09;遍历的时候&#xff0c;需要进行类型转换&#xff0c;如果集合中的数据量较大&#xff0c;对效率有影响。泛型的好处 编译时&#xff0c;检查添加元素的类型&#xff0c;提…

springbean 的 setter/构造注入

文章目录前言一、另外两种注入的怎么用&#xff1f;二、使用setter和构造注入的步骤1. 搞一个配置类,用户获取spring容器中的bean2. 由于有静态方法,所以直接调用三、使用final 的构造注入方式(推荐)总结前言 我们知道,一般java中的依赖注入有三种: 1 属性注入 2 settter注入 …