【DS】AVL树

news2025/1/5 8:49:28

目录

  • AVL树的介绍
  • AVL树节点的定义
  • 认识AVL树的抽象图
  • AVL树的插入
    • BSTree规则插入
    • 更新平衡因子
    • 平衡因子的判断
  • AVL树的旋转
    • 左单旋
    • 右单旋
    • 左右双旋
    • 右左双旋
  • AVL树的验证
  • AVL树的查找
  • AVL树的性能

在上篇搜索二叉树末尾提到过,得益于搜索二叉树的性质:大于根往右,小于根往左;在平衡的条件下,查找一个数的效率可以达到惊人的O(logN),但搜索二叉树最致命的缺陷就是无法保证树身的平衡。于是今天的主角AVL树就为之而生。

本篇对AVL树的插入,查询功能进行模拟实现,学习它旋转调平衡的奥秘,感悟大佬的神奇构思。

AVL树的介绍

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。

因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,维持树身的平衡,从而减少平均搜索长度。

AVL树的定义

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

  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
  • 它的左右子树都是AVL

AVL树
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有N个结点,其高度可保持在O(logN),搜索的时间复杂度则为O(logN)

AVL树节点的定义

AVL树也是链式的树型结构,数据都保存在节点当中。

这里我们的数据不再是一个具体的类型,而是存放在一个类模板pair的对象里,pair也称键值对pair中有firstsecond两个成员。键值对用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量keyvaluekey代表键值,value表示与key对应的信息。这也就是我们提到过的key_value模型。
pair
AVL树节点中有:

  • 保存数据的键值对pair
  • 三叉链
  • 平衡因子

三叉链
有了三叉链,一定程度上可以减少递归的使用,实现起来容易一点;毕竟递归和循环我还是爱循环多一点。增加平衡因子bf,可以更好判断树身的平衡,但是后面你就知道是需要付出代价的。

对于AVLTreeNode,只需要一个构造函数初始化节点即可。

template<class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;//键值对
	//三叉链
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;//平衡因子

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

认识AVL树的抽象图

在开始介绍AVL树的插入调平衡前,需要学会看AVL树的抽象图。

在讲解旋转调平衡时,将采用类似下面的抽象图讲解。

  • h代表子树的高度

在下图中,树已近不平衡,高度差的绝对值大于1;需要进行左单旋进行调平衡。此时看到的a,b,c代表的是一颗子树其高度hh的大小不确定,所以子树的树型是无法确认的,有可能是一个节点(h==1),也有可能是一颗颗的子树(h>=2),所以才称下图为抽象图。

抽象图

那为什么要学会看抽象图呢?画成具体的一棵树不好吗?

通过算一组数来解答这个问题。
假设此时的h==2,那么子树a,b,c可能的形状就有x,y,z三种可能。而为了符合抽象图的形状,子树a,b可以是x,y,z三种里的任意一种(因为插入节点在c这颗子树,所以对a,b子树的形状没有要求),而子树c只能是形状x才符合抽象图的形状。这样一来,这整棵树的排列组合就有3(x,y,z选一个)*3(x,y,z选一个)*4(新节点可以插在x形状的两个节点的左右两个位置之一)=36种。这才只是当h==2的情况下,h继续增加时也绝不是简单的按倍数增长。
而旋转调平衡的策略也才四种,所以必须将同一类模型的树身归纳于一个抽象图才能清楚知道当树身不平衡时采取四种旋转策略种的哪一种,从而既维持和BSTree的特性,又能维持树身平衡。所以会看抽象图是很有必要的,是学习旋转调平衡的第一步。
抽象图理解

注意:子树c只能是形状x的子树是因为:当子树c的形状是形状z的子树时(y同理),节点插入到子树c时,确实也需要旋转调平衡了,但是却不是我们上面对应的左单旋的抽象图了。如下图。
抽象图2

AVL树的插入

AVL树的插入操作实际上就是BSTree插入操作的升级:增加了平衡因子对树身平衡的判断以及后续旋转调平衡的操作。对于AVL树的插入,分为以下四步

BSTree规则插入

插入流程:

  1. BSTree的性质插入。
  2. 更新平衡因子bf
  3. 使用平衡因子bf判断树身是否平衡(bf的绝对值是否大于1)
  4. 若不平衡,旋转调平衡。

BSTree的性质插入

  • 树为空:则直接新增节点,该节点即为root。
  • 树不为空:按照BST的性质插入:
    • 大于根节点,插入到根节点的右子树,直到找到空的位置,插入成功。
    • 小于根节点,插入到根节点的左子树,直到找到空的位置,插入成功。
    • BST不支持重复数据,若树中已有该值,插入失败。

更新平衡因子

更新平衡因子bf

当成功完成一次插入后,会影响部分祖先节点的平衡因子。
由于一个结点的平衡因子是否需要更新,是取决于该结点的左右子树的高度是否发生了变化,因此插入一个结点后,该结点的祖先结点的平衡因子可能需要更新。

平衡因子

对于平衡因子的更新,我们规定:右正左负

  1. 新增节点在右子树上,则新增节点的父节点平衡因子++
  2. 新增节点在左子树上,则新增节点的父节点平衡因子- -

因此,平衡因子在插入节点后可能出现的值为:-2 -1 0 1 2
对于不同的平衡因子,影响路径上祖先节点的平衡因子是否需要更新。

cur为新插入的节点或者是在向上更新平衡因子时原来的父节点parent

parent的平衡因子为0

当parent的平衡因子为0时,说明此时parent的左右子树由原来的一边高变为左右等高,也就是说此时parent这棵子树的高度并没有发生变化,也就不需要再向上更新平衡因子。
平衡因子

parent的平衡因子不为0

分两种情况,parent的平衡因子的绝对值为1或者2

parent的平衡因子为1或者-1

这种情况下说明parent的左右子树由原来的平衡,变为现在的一边高;导致parent的左或右子树高度增加,这时候就需要向上更新平衡因子。直到parent的平衡因子变为0,停止更新;或者parent的平衡因子变为2或者-2,也就是下一种状况。

向上更新方式

cur = parent;
parent = parent->_parent;

parent的平衡因子为2或者-2

当parent的平衡因子变为2或-2,就违反了AVL树的平衡原则(平衡因子的绝对值大小不大于1),这种情况就需要进行旋转调平衡。

平衡因子3

而当parent的平衡因子为-2/2时,cur的平衡因子必定是-1/1而不会是0。
理由如下:
cur的平衡因子是0,那么cur一定是新增节点,而不是上一次更新平衡因子时的parent,否则在上一次更新平衡因子时,会因为parent的平衡因子为0而停止继续往上更新。
而cur是新增结点的话,其父结点的平衡因子更新后一定是-1/0/1,而不可能是-2/2,因为父节点的平衡因子的2/-2一定是由1/-1变来的。

在新增节点插入前,其父结点的状态有以下两种可能:

  1. 其父节点是一个左右子树均为空的叶子节点,其平衡因子是0,新增节点插入后其平衡因子更新为-1/1。
  2. 其父节点是一个左子树或右子树为空的节点,其平衡因子是-1/1,新增节点插入到其父节点的空子树当中,使得其父节点左右子树当中较矮的一棵子树增高了,新增节点后其平衡因子更新为0。
  • 所以parent的平衡因子为2/-2时,只能由上述的情况1演变而来。新增节点插入后parent的平衡因子更新为-1/1,继续向上更新平衡因子,此时cur就变为原来的parent,其平衡因子就为-1或1了;更新后的parent的平衡因子之后就有可能变为0(停止继续向上更新平衡因子),或者为2或-2(旋转调平衡)

综上所述,当parent的平衡因子为-2/2时,cur的平衡因子必定是-1/1而不会是0。 这也是接下来判断进行何种旋转的判断条件。

平衡因子的判断

根据上面的结论,依据parentcur的平衡因子来判断进行四种旋转调平衡的策略中的一种。
此时parentcur的平衡因子只有以下可能:

  • parent的平衡因子为2/-2
  • cur的平衡因子为1/-1
  1. parent的平衡因子为2,cur的平衡因子为1时,进行左单旋。
  2. parent的平衡因子为2,cur的平衡因子为-1时,进行右左双旋。
  3. parent的平衡因子为-2,cur的平衡因子为-1时,进行右单旋。
  4. parent的平衡因子为-2,cur的平衡因子为1时,进行左右双旋。
  • 同号单旋,异号双旋

旋转判断
实现代码:

bool Insert(const pair<K,V>& kv)
	{
		if (_root == nullptr)//空树的情况
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;//遍历
		Node* parent = nullptr;
		while (cur)
		{
			if (kv.first > cur->_kv.first)//<K,V>,比较first
			{
				parent = cur;
				cur = cur->_right;

			}
			else if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		//到在这里说明找到位置了,开始插入。
		//此时cur已经为nullptr,让其成为新节点
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)//确认在左还是在右
		{
			parent->_right = cur;
		}
		else//不存在相等情况
		{
			parent->_left = cur;
		}

		//处理三叉链的最后一环
		cur->_parent = parent;

		//更新平衡因子
		while (parent)//最差情况下会更新到根
		{
			if (cur == parent->_right)//连接在父节点的右边
			{
				parent->_bf++;//右减左:右为正
			}
			else//左边
			{
				parent->_bf--;
			}

			//检查是否需要继续更新
			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == -1 || parent->_bf == 1)
			{
				//需要向上更新
				//cur和parent向上爬,所以等会进行什么旋转也是由这两位的平衡因子决定
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == -2 || parent->_bf == 2)
			{
				//此时已经不平衡,需要旋转调平衡
				//RR:左单旋
				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)
				{
					//RotateLR(parent);//很棒,三小时
					RotateRL(parent);
				}

				break;//旋转后就一定平衡了,无需继续往上更新平衡因子(旋转后树高度变为插入之前了)
			}
			else
			{
				//说明平衡因子有问题
				assert(false);
			}
		}

		return true;
	}

AVL树的旋转

左单旋

新节点插入较高右子树的右侧

右高,左单旋
parent的平衡因子为2,cur(subR)的平衡因子为1 :同号单旋

进行实现时,还是按数据结构学习的方法来——画图,对照着图思路会更加清晰。

旋转步骤:

  1. 列出旋转涉及的节点
    • subR
    • subRL
    • parentparent
  2. 处理parent与subRL
    • 让subRL变为parent的右
    • 注意:subRL可能为空;若不为空则让subRL的parent指向parent
  3. 处理subR与parent
    • 让parent变为subR的左
    • parent的parent指向subR
  4. 处理当前根subR与向上的关系
    • 若parentparent为空,说明此时旋转的树就是整棵树,让subR成为新的根
    • parentparent不为空,说明此时旋转的树是一棵子树,处理subR的向上关系
  5. 最后设置平衡因子,此时已经平衡,直接设为0。

实际上所谓旋转,其本质就是改变对应节点的指针指向;有了三叉链,可以减少递归的使用,方便了实现,但是在调平衡需要多维护一个指针,增加了维护成本。

左单旋

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

		parent->_right = subRL;
		if (subRL)//当h为0时subRL为nullptr
		{
			subRL->_parent = parent;
		}

		subR->_left = parent;
		parent->_parent = subR;

		//上面先考虑当前子树的调整,接下来再向上考虑整体
		if (parentparent == nullptr)//说明此次调整的就是根
		{
			_root = subR;//subR为新根,更新_root
			_root->_parent = nullptr;
		}
		else//调整的是子树
		{
			//原本的根为左子树
			if (parent == parentparent->_left)
			{
				parentparent->_left = subR;
			}
			else
			{
				parentparent->_right = subR;
			}

			subR->_parent = parentparent;
		}
		
		//调整后的这颗树平衡因子都为0,
		parent->_bf = subR->_bf = 0;//paret和subR才是在插入节点路径上的

	}

右单旋

新节点插入较高左子树的左侧

左高,右单旋
parent的平衡因子为-2,cur(subL)的平衡因子为-1 :同号单旋

参照着抽象图,逻辑和左单旋是一致的。

旋转步骤:

  1. 列出旋转涉及的节点
    • subL
    • subLR
    • parentparent
  2. 处理parent与subLR
    • 让subLR变为parent的左
    • 注意:subLR可能为空;若不为空则让subLR的parent指向parent
  3. 处理subL与parent
    • 让parent变为subL的右
    • parent的parent指向subR
  4. 处理当前根subL与向上的关系
    • 若parentparent为空,说明此时旋转的树就是整棵树,让subL成为新的根
    • parentparent不为空,说明此时旋转的树是一棵子树,处理subL的向上关系
  5. 最后设置平衡因子,此时已经平衡,直接设为0。

右单旋

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* parentparent = parent->_parent;


		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}

		subL->_right = parent;
		parent->_parent = subL;

		if (parentparent == nullptr)
		{
			//说明parent为整棵树的根,此时subL为新根,更新
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == parentparent->_left)
			{
				parentparent->_left = subL;
			}
			else
			{
				parentparent->_right = subL;
			}
			subL->_parent = parentparent;
		}
		subL->_bf = parent->_bf = 0;
	}

左右双旋

新节点插入较高左子树的右侧

不是单纯的一边高,需要转化为单旋的情况
parent的平衡因子为-2,cur(subL)的平衡因子为1 :异号双旋

此时的树不是纯粹的一边高,仅通过单旋无法达到平衡的效果。
双旋

通过多旋转一次将树型转化为单旋的情况。如下面抽象图的情况:可以先进行左单旋转化为纯粹左边高的情况,这时再进行右单旋。

旋转步骤:

  1. 列出相关的节点
    • subL第一次旋转的点,进行树型转化
    • subL对应的是此时旋转树的根,虽然它的平衡因子没有违反规定,但我们以它为根旋转的目的是树型转化
    • subLR不是直接旋转相关的点,列出该节点为记录subLR的平衡因子
  2. 旋转
    • 第一次以subL为旋转点左旋
    • 第二次以parent为旋转点右旋
  3. 调节平衡因子
    • 两次旋转都是复用前面的单旋,而单旋的平衡因子都会处理成0,这和双旋的情况是不是适用的
    • 需要左右双旋是因为新节点插入较高左子树的右侧,但是较高左子树的右侧也有可能有它的左右子树,因此需要分情况讨论

左右双旋

双旋调节平衡因子:以subLR的平衡因子作为判断条件,如下图:新增节点分别插入在较高左子树的右侧的左右子树上。会有以下三种情况:

  1. subLR的平衡因子为-1
    • 这种情况下subLR,subL都为0,parent为1
  2. subLR的平衡因子为1
    • subLR,parent都为0,subL为-1
  3. 当subLR的平衡因子为0
    • 这种状况说明subLR为空,插入新节点前只有两个节点

具体请对照图看
平衡因子的判断

void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;//记录

		RotateL(subL);//先左旋,对应parent
		RotateR(parent);//再右旋
		//左,右旋的平衡因子都会调成0,是不对的。
		//这里根据插入位置的父亲节点的平衡因子来调节,有三种情况

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

	}

双旋的第一次旋转实际上是我们主动控制旋转的,其节点的平衡因子并没有违反平衡规则。主动控制旋转的目的也很明确,就是将当前树型转化为左单旋或右单旋的形状,再通过第二次旋转调平衡。其中可能对第一次的旋转会不会破坏搜索二叉树的性质感到疑惑。

这里通过下面这个双旋进行解答:搜索二叉树的中序遍历会得到一个升序序列;所以,一棵树如果通过中序遍历得到一个升序序列,那么它就具有搜索二叉树的特性。从下面例子可以看到中序遍历旋转一次后的二叉树仍为一个升序序列,则说明其具有搜索二叉树的特性。所以双旋的第一次旋转是不会破坏搜索二叉树特性的。这跟搜索二叉树的删除操作中的找人带策略是类似的
性质不变

右左双旋

新节点插入较高右子树的左侧

不是单纯的一边高,需要转化为单旋的情况
parent的平衡因子为2,cur(subR)的平衡因子为-1 :异号双旋

右左双旋的旋转步骤,逻辑和左右双旋一致
旋转步骤:

  1. 列出相关的节点
    • subR第一次旋转的点,进行树型转化
    • subR对应的是此时旋转树的根,虽然它的平衡因子没有违反规定,但我们以它为根旋转的目的是树型转化
    • subRL不是直接旋转相关的点,列出该节点为记录subRL的平衡因子
  2. 旋转
    • 第一次以subR为旋转点右旋
    • 第二次以parent为旋转点左旋
  3. 调节平衡因子
    • 参考左右双旋

平衡因子调节的三种情况:

  1. subRL的平衡因子为1
    • 这种情况下subRL,subR都为0,parent为-1
  2. subRL的平衡因子为-1
    • subRL,parent都为0,subR为1
  3. 当subRL的平衡因子为0
    • 这种状况说明subRL为空,插入新节点前只有两个节点

右左双旋

//右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		int bf = subRL->_bf;
		RotateR(parent->_right);
		RotateL(parent);

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

	}

AVL树的验证

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

  1. 验证其为二叉搜索树
    • 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  2. 验证其为平衡树
    • 每个节点子树高度差的绝对值不超过1
    • 节点的平衡因子是否计算正确

二叉树验证

中序遍历,与搜索二叉树的一致

  • 此时使用了pair,与上篇的搜索二叉树有所区别,但整体逻辑不变。
publicvoid InOrder()
	{
		//嵌套一层,类外不能访问私有成员
		_InOrder(_root);
		cout << endl;
	}
	
private:
	//嵌套一层
	void _InOrder(Node* root)//中序遍历搜索二叉树是有序的
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

平衡树验证

平衡的验证是通过树高的比对实现的,所以先实现一个求高度的递归函数_Height;写一个_IsBalanceTree验证平衡。
_IsBalanceTree:通过递归,从叶子节点开始一层一层向上验证;验证的点有两个:左右子树的高度差不大于1当前节点的平衡因子是否正确(左右子树高度差diff==平衡因子bf)

public:
    bool IsBalanceTree()
	{
		return _IsBalanceTree(_root);
	}

	int Height()
	{
		return _Height(_root);
	}
privateint _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		//记录左右子树的高度
		int lefthight = _Height(root->_left);
		int righthight = _Height(root->_right);

		//比较高度,返回大的+1(自身)
		return lefthight > righthight ? lefthight + 1 : righthight + 1;
	}

	bool _IsBalanceTree(Node* root)
	{
		// 空树也是AVL树
		if (nullptr == root) 
			return true;

		// 计算pRoot节点的平衡因子:即root左右子树的高度差
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		int diff = rightHeight - leftHeight;//平衡树的diff与bf相等
		// 如果计算出的平衡因子与root的平衡因子不相等,或者
        // root平衡因子的绝对值超过1,则一定不是AVL树
		if (abs(diff)>=2||root->_bf!=diff)//abs计算绝对值
			return false;
		// root的左和右如果都是AVL树,则该树一定是AVL树
		return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
	}

AVL树的查找

和搜索二叉树的查找是一致的,只不过现在使用的是键值对pair

	Node* Find(const pair<K,V>& kv)
	{
		if (_root == nullptr)
		{
			return nullptr;
		}
		Node* cur = _root;
		while (cur)
		{
			if (kv.first > cur->_kv.first)
			{
				cur = cur->_right;

			}
			else if (kv.first < cur->_kv.first)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

对于AVL树的实现就到此为止,另一个重要的功能——删除会比插入更加麻烦,旋转次数可能会更多,甚至一次删除调整到根的位置。由于本文有点长了,就不介绍了(实际上没学没写)。对删除功能有兴趣的可以自己查阅相关书籍,或者参考龙哥这位大佬的AVL树的博客——AVL树详细实现

AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g ( N ) log(N) log(N)
但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。AVL树极度追求平衡,如果需要经常修改数据,旋转的消耗是很大的;这时候就不太适用AVL树了;而红黑树在这方面会更胜一筹。

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

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

相关文章

计算机毕业设计Python深度学习垃圾邮件分类检测系统 朴素贝叶斯算法 机器学习 人工智能 数据可视化 大数据毕业设计 Python爬虫 知识图谱 文本分类

基于朴素贝叶斯的邮件分类系统设计 摘要&#xff1a;为了解决垃圾邮件导致邮件通信质量被污染、占用邮箱存储空间、伪装正常邮件进行钓鱼或诈骗以及邮件分类问题。应用Python、Sklearn、Echarts技术和Flask、Lay-UI框架&#xff0c;使用MySQL作为系统数据库&#xff0c;设计并实…

java: 程序包org.junit.jupiter.api不存在

明明idea没有报错&#xff0c;引用包也没问题&#xff0c;为啥提示java: 程序包org.junit.jupiter.api不存在&#xff1f; 配置&#xff01;还TMD是配置&#xff01; 如果是引用包的版本不对或者其他&#xff0c;直接就是引用报错或者pom里面飘红了。 这个应该是把generat…

微服务-- Sentinel的使用

目录 Sentinel&#xff1a;微服务的哨兵 生态系统景观 sentinel与spring cloud Hystrix 对比 Sentinel 主要分为两部分 Sentinel安装与使用 Sentinel的控制规则 流控规则 流控规则的属性说明 新增流控规则 关联流控模式 SentinelResource注解的使用 SentinelResou…

如何将自己的项目发布到Maven中央仓库

1.背景 本教程为2024年9月最新版 我有一个java项目想发布到maven中央仓库&#xff0c;然后任何人都可以在pom文件中引用我写的代码 引用格式如下&#xff1a; <!-- 这是引用rocketmq的坐标 --> <dependency><groupId>org.apache.rocketmq</groupId>&l…

炫酷HTML蜘蛛侠登录页面

全篇使用HTML、CSS、JavaScript&#xff0c;建议有过基础的进行阅读。 一、预览图 二、HTML代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-w…

ESP8266做httpServer提示Header fields are too long for server to interpret

CONFIG_HTTP_BUF_SIZE512 CONFIG_HTTPD_MAX_REQ_HDR_LEN1024 CONFIG_HTTPD_MAX_URI_LEN512CONFIG_HTTPD_MAX_REQ_HDR_LEN由512改为1024

C++ | Leetcode C++题解之第404题左叶子之和

题目&#xff1a; 题解&#xff1a; class Solution { public:bool isLeafNode(TreeNode* node) {return !node->left && !node->right;}int sumOfLeftLeaves(TreeNode* root) {if (!root) {return 0;}queue<TreeNode*> q;q.push(root);int ans 0;while …

氢能源多旋翼无人机技术详解

1. 技术背景与优势 随着全球对低碳、环保和高效能源解决方案的需求日益增长&#xff0c;氢能源作为一种清洁、高效的能源形式&#xff0c;在多个领域展现出巨大的应用潜力。在无人机领域&#xff0c;氢能源多旋翼无人机因其独特的优势逐渐受到关注。相比传统锂电池无人机&…

Linux sh命令

目录 一. 基本语法二. 选项2.1 -c 字符串中读取内容&#xff0c;并执行2.1.1 基本用法2.1.2 获取当前目录下失效的超链接 2.2 -x 每个命令执行之前&#xff0c;将其打印出来2.3 结合Here文档使用 一. 基本语法 ⏹Linux 和 Unix 系统中用于执行 shell 脚本 或 运行命令 的命令。…

【我的 PWN 学习手札】Fastbin Double Free

前言 Fastbin的Double Free实际上还是利用其特性产生UAF的效果&#xff0c;使得可以进行Fastbin Attack 一、Double Free double free&#xff0c;顾名思义&#xff0c;free两次。对于fastbin这种单链表的组织结构&#xff0c;会形成这样一个效果&#xff1a; 如果我们mallo…

卡西莫多的手信

通过网盘分享的文件&#xff1a;卡西莫多的手信2022-2024.9.15-A5.pdf 链接: 百度网盘 请输入提取码 提取码: gig1

oracle数据库安装和配置详细讲解

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; Oracle 数据库是全球广泛使用的关系型数据库管理系统 (RDBMS)&#xff0c;提供高性能、可靠性、安全性和可扩展性&#xff0c;广泛应用于企业关键任务系统。下面详细介绍如何在 CentOS 系统上安装和配置 Or…

非金属失效与典型案例分析培训

随着生产和科学技术的发展&#xff0c;人们不断对高分子材料提出各种各样的新要求。因为技术的全新要求和产品的高要求化&#xff0c;而客户对产品的高要求及工艺理解不一&#xff0c;于是高分子材料断裂、开裂、腐蚀、变色等之类失效频繁出现&#xff0c;常引起供应商与用户间…

O2O营销,中小企业数字化转型的加速器

嘿&#xff0c;小伙伴们&#xff0c;今天咱们要聊的&#xff0c;可是那让中小企业焕发新生的O2O营销魔法&#xff01;它就像是一位时空穿梭者&#xff0c;轻松跨越线上与线下的鸿沟&#xff0c;带着商家们开启了一场数字化转型的奇妙之旅。 O2O营销&#xff1a;不只是连接&…

【主机入侵检测】Wazuh规则详解

前言 Wazuh 规则是一组用XML格式编写的条件&#xff0c;它们定义了应该如何解释日志数据。这些规则由Wazuh Manager使用&#xff0c;用于在日志消息中检测特定的模式或行为&#xff0c;并相应地生成警报或响应。它们在威胁检测中扮演着至关重要的角色&#xff0c;因为它们允许系…

NAS远程下载,Docker部署qBittorrent、Transmission、贝锐花生壳

与电脑不同&#xff0c;NAS通常都是7*24小时不间断运行&#xff0c;这使得下载资源变得更加便捷&#xff0c;解决了bt、pt下载需要长时间在线、挂机的问题。 所以&#xff0c;对于许多选择品牌NAS或自行搭建NAS系统的用户而言&#xff0c;像qBittorrent、Transmission这样的下载…

DAY 9 - 10 : 树

树的概念 定义 树&#xff08;Tree&#xff09;是n&#xff08;n≥0&#xff09;个节点的有限集合T&#xff0c;它满足两个条件 &#xff1a; 1.有且仅有一个特定的称为根&#xff08;Root&#xff09;的节点。 2.其余的节点可以分为m&#xff08;m≥0&#xff09;个互不相交的…

rk3568 parameter.txt 添加自己的分区,或者去掉已有的分区

问题&#xff1a; 客户在 之前的核心板上 可以烧写自己的镜像&#xff0c;但是在最新的核心板上却烧写不上&#xff0c;新旧核心板 只是变了emmc &#xff0c; 由 江波龙 ------->星火。 分析&#xff1a; 客户的镜像的分区是经过自己的定制的&#xff0c;所以有可能 是 由…

Linux云计算 |【第三阶段】PROJECT1-DAY3

主要内容&#xff1a; Keepalived高可用、部署Ceph分布式存储 一、网站架构进阶项目案例 案例1&#xff1a;Keepalived高可用 延续 PROJECT1-DAY2 案例&#xff0c;部署两台代理服务器&#xff0c;实现如下效果&#xff1a; 1&#xff09;利用keepalived实现两台代理服务器的…

【Java面试】第十一天

&#x1f31f;个人主页&#xff1a;时间会证明一切. 目录 Springboot是如何实现自动配置的&#xff1f;Spring的Autowired能用在Map上吗&#xff1f;ListMapSet数组注意事项 Spring的AOP在什么场景下会失效&#xff1f; Springboot是如何实现自动配置的&#xff1f; Spring Bo…