【进阶数据结构】平衡搜索二叉树 —— AVL树

🌈感谢阅读East-sunrise学习分享——[进阶数据结构]AVL树
博主水平有限,如有差错,欢迎斧正🙏感谢有你 码字不易,若有收获,期待你的点赞关注💙我们一起进步🚀


🌈我们上一篇博客分享了搜索二叉树,在文中也铺垫了搜索二叉树的一些结构局限性
而今天分享的一种特殊的搜索二叉树——AVL树,便是一种结构优异的搜索二叉树🎄那么我们就开始吧🚀🚀🚀

目录

  • 一、AVL树的概念
  • 二、AVL树结点的定义
  • 三、AVL树的插入
  • 四、AVL树的旋转
    • 1.左单旋
    • 2.右单旋
    • 3.左右双旋
    • 4.右左双旋
  • 五、最终代码展示

一、AVL树的概念

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

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hDG6AbRQ-1679390790392)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320143958347.png)]

这里的平衡因子是指:右子树高度-左子树高度

注意:平衡因子只是博主分享的这种实现方法的一种自定义名字(不是必须的),除了使用平衡因子之外还有许多实现AVL树的方法

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


二、AVL树结点的定义

AVL树的结点我们定义了一个三叉链结构,便于后续的操作;并且在每个结点中都引入了平衡因子

template<class K, class V>
struct AVLTreeNode
{
    //存储键值对的pair类
	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树
template<class K,class V>
struct AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
    //插入
    bool Insert(const pair<K, V>& kv)
    {}
    
private:
	Node* _root = nullptr;
};

三、AVL树的插入

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

  1. 按照二叉搜索树的方式插入新节点
  2. 调整平衡因子,若不平衡则需要旋转调整AVL树

⭕⭕当有新节点插入后我们就需要判断此时的树是否仍然平衡仍然是AVL树了


🚩插入后平衡因子的变化类型?

我们知道,假如平衡,则每个结点的平衡因子只有三种可能:-1,0,1

而插入新结点肯定会使得高度的变化,假如插入新节点后仍平衡,则父节点的平衡因子的变化有:

  • 0 --> 1
  • 0 --> -1
  • 1 --> 0
  • -1 --> 0

知道了平衡因子的变化情况后,又抛出了一个问题

🚩插入新节点影响父节点的平衡因子,那是否会影响祖先结点的平衡因子?

最简单的情况就是插入了新节点,只影响了其父结点,只需更新父节点的平衡因子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yGlce0wT-1679390790393)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320151927976.png)]

插入新节点后,改变了其父结点(8)的子树高度,所以需要更新父节点的平衡因子,但是插入之后并不会改变其祖先结点的子树高度,所以不需要往上更新平衡因子

📌因此我们可以总结出:是否持续更新平衡因子,取决于其结点的子树高度是否变化

再结合一开始的平衡因子变化情况我们可以得出插入新结点后:

  • parent -> _bf == 0 —— 说明之前parent -> _bf 是 1 或者 -1(一边高一边低)新节点刚好插入填上矮的那边,parent所在子树高度不变 —— 祖先的子树高度也不会变 —— 只需更新parent的平衡因子,不需要继续往上更新
  • parent -> _bf == 1 或 -1 —— 说明之前parent -> _bf == 0(两边一样高)新结点插入使得parent所在子树的高度变得一高一低 —— 祖先的子树高度也产生变化 —— 更新parent的平衡因子之外,还需要继续往上更新祖先结点的平衡因子
  • parent -> _bf == 2 或 -2 —— 说明本就一高一低的子树,插入新节点后造成更加不平衡,此时违反了AVL树的平衡规则 —— 就地处理 ——旋转调整

最坏的情况就是插入了新节点,直接影响到了root根结点,所以需要持续更新到root根结点的平衡因子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-inVjVnIr-1679390790394)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320154047198.png)]

💭更新结点的平衡因子时,假若我们需要持续向上更新平衡因子,一开始我们更新的是最下面的parent结点,更新后则可向上迭代,直到parent为空就停止

✏️代码实现

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

		//调整平衡因子
		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;
				parent = parent->_parent;
			}
			else if (parent->_bf == -2 || parent->_bf == 2)
			{
				//旋转调整
			}
			else
				assert(false);
		}
		return true;
	}

四、AVL树的旋转

🌏如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。

因此旋转的要求即是:

  1. 旋转后仍保持二叉搜索树的结构
  2. 旋转后整棵树保持平衡,平衡因子不超过1

而根据节点插入位置的不同,AVL树的旋转分为四种:

1.左单旋

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ewTMX0mf-1679390790394)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320191813189.png)]

此处我们给出左单旋过程的抽象图📌

我们发现,当parent的平衡因子是2,cur是1时,便进行左单旋 ——> 将cur的左子树给parent的右子树,然后将parent及其子树一整棵树变为cur的左子树

左单旋真就如此吗?不信我们可以画出具象图看看

当 h = 0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JOtMtrcS-1679390790394)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320192229670.png)]

当 h = 1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TNDYGHPL-1679390790394)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320192802162.png)]

当 h = 2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o56wtp0U-1679390790395)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320193527342.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ndiplxh7-1679390790395)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320194200461.png)]

💭有的兄弟看到这就有疑问,为什么h = 2时,子树c一定就得是z的模样呢?
因为假如子树c是x或y的模样,插入新节点时并不会引发节点30的旋转,那样最多只是变成以节点60为parent的树进行左单旋,那就和h = 1是同样的情况了💤因此以上的情况,其实是笼盖了所有需要进行左单旋的子情况了🚩然后以上的情况可能是某棵树的子树

最后我们发现,所有需要进行左单旋的情况,最后的操作都是如一开始所说

✏️代码实现(对照图更清晰易懂)

void RotateL(Node* parent)
{
	Node* subR = parent->_right;//parent的右孩子
	Node* subRL = subR->_left;//parent的右孩子的左孩子
		
    //旋转后subR的左孩子作为parent的右孩子
    parent->_right = subRL;

    //subR的左孩子有可能为空也有可能存在
    //如果存在则需要更新父子关系
	if (subRL)
		subRL->_parent = parent;

    //subR的左孩子变为以parent为根的子树结构
    //同时更新父子关系
    subR->_left = parent;
    parent->_parent = subR;
    
    //parent也可能只是一棵子树的根,其pparent可能为空也可能存在
	Node* pparent = parent->_parent;
	
	if (pparent)
	{
        //如果pparent不为空,则说明parent是一棵子树
        //可能是存在于其父节点的左子树or右子树
		if (parent == pparent->_left)
			pparent->_left = subR;
		else
			pparent->_right = subR;
		subR->_parent = pparent;
	}
	else
	{
        //若pparent为空,则说明parent是整棵树的根节点
        //旋转后根节点已经换人了需要更新
		_root = subR;
		subR->_parent = nullptr;
	}
    //最后更新平衡因子
	parent->_bf = subR->_bf = 0;
}

📌看完以上的代码实现,发现旋转的代码实现起来也有许多细节需要注意啊…

因为旋转后也要保持一棵正常的树的结构,因此那些父子链接关系也需要正确更新

2.右单旋

2️⃣新节点插入较高左子树的左侧 - 右单旋

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0rczBlb6-1679390790395)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320224253056.png)]

✏️实现及情况考虑可参考左单旋

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

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

	Node* pparent = parent->_parent;
	subL->_right = parent;
	parent->_parent = subL;

	if (pparent)
	{
		if (parent == pparent->_left)
			pparent->_left = subL;
		else
			pparent->_right = subL;
		subL->_parent = pparent;
	}
	else
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	subL->_bf = parent->_bf = 0;
}

3.左右双旋

3️⃣新节点插入较高左子树的右侧 - 先左单旋再右单旋

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Mn250NT-1679390790395)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320225421511.png)]

左右双旋我们可以复用上面的左单旋和右单旋的代码🚩但是需要注意的是,左右双旋完各个节点的平衡因子有不同的情况,正是因为左右双旋会因为新节点插入的位置不同而影响不同的旋转结果,因此我们总结出了以下三种情况:

  1. h = 0 —— 节点60即是新插入节点
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fkFAFXcU-1679390790396)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320230931222.png)]

  2. 新节点插入在b
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1pptemfj-1679390790396)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320231609130.png)]

  3. 新节点插入在c
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JGZNUPir-1679390790396)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320231628170.png)]

综上所述,当我们在实现左右双旋时的最后,可根据插入新节点后节点60的平衡因子大小,来确定不同的情况

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

	RotateL(parent->_left);
	RotateR(parent);
    
    //更新平衡因子

	if (bf == 1) //新增在sublr右子树
	{
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}

	else if (bf == -1) //新增在sublr左子树
	{
		subL->_bf = 0;
		parent->_bf = 1;
		subLR->_bf = 0;
	}

	else //本身就是新增
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
}

4.右左双旋

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RGitbiDD-1679390790396)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20230320232205457.png)]

✏️实现及情况考虑可参考左右双旋

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;
		parent->_bf = -1;
		subRL->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else if(bf == 0)
	{
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
}

五、最终代码展示

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

template<class K,class V>
struct AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:

	AVLTree()
		:_root(nullptr)
	{}

	

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

		//调整平衡因子
		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;
				parent = parent->_parent;
			}
			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;
	}

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

		if (subRL)
			subRL->_parent = parent;

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

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

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

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

		Node* pparent = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;

		if (pparent)
		{
			if (parent == pparent->_left)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		else
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		subL->_bf = parent->_bf = 0;
	}

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

		RotateL(parent->_left);
		RotateR(parent);

		if (bf == 1) //新增在sublr右子树
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}

		else if (bf == -1) //新增在sublr左子树
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}

		else if (bf == 0) //本身就是新增
		{

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

	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;
			parent->_bf = -1;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if(bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void Inorder()
	{
		_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 hl = Height(root->_left);
		int hr = Height(root->_right);

		return hl > hr ? hl + 1 : hr + 1;
	}

	bool IsBalance()
	{
		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 << "平衡因子异常" << endl;
			return false;
		}

		return abs(rightHeight - leftHeight) < 2
			&& IsBalance(root->_left)
			&& IsBalance(root->_right);
	}

private:
	Node* _root = nullptr;
};

🌈🌈写在最后 我们今天的学习分享之旅就到此结束了
🎈感谢能耐心地阅读到此
🎈码字不易,感谢三连
🎈关注博主,我们一起学习、一起进步

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学习Linux只要学会这个命令就够了!

大家好&#xff0c;我是良许。 这段时间又是搬家&#xff0c;又是找新办公室&#xff0c;现在终于安顿下来了&#xff0c;有时间给大家分享干货了。 今天给大家介绍一个 Linux 超级实用命令&#xff0c;有了这个命令&#xff0c;你就可以愉快使用 Linux 上几乎所有常用命令了…

【Unity入门】3D物体

【Unity入门】3D物体 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;物体移动旋转缩放 &#xff08;1&#xff09;物体移动 在上一篇文章【Unity入门】场景视图操作我们学会了在场景中创建3…

Java现在好找工作吗?

Java到2023年已经28岁了&#xff0c;可能你会怀疑它是否还一如当年一样的强大&#xff0c;在应用层领域独占鳌头。但是基于Java庞大的市场占有率和需求&#xff0c;它依然在保持着更新迭代&#xff0c;依然是最常用的底层开发语言&#xff0c;基于其安全性、开放性、稳定性和跨…

springboot Aspect切面

问题描述 配置切面&#xff0c;但未切到目标类上 切面类 Component Aspect public class ControllerAspect {//Pointcut("execution(* com.yzk.learn.springbootsecurity.controller.UserController.info(..))")Pointcut("execution(* com.learn..*.controlle…

类ChatGPT开源项目的部署与微调:从LLaMA到ChatGLM-6B

前言 近期&#xff0c;除了研究ChatGPT背后的各种技术细节 不断看论文(至少100篇&#xff0c;100篇目录见此&#xff1a;ChatGPT相关技术必读论文100篇)&#xff0c;还开始研究一系列开源模型(包括各自对应的模型架构、训练方法、训练数据、本地私有化部署、硬件配置要求、微调…

Java代码是如何被CPU狂飙起来的?

&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3; &#x1f38d;大家好&#xff0c;我是慕枫 &#x1f38d;前阿里巴巴高级工程师&#xff0c;InfoQ签约作者、阿里云专家博主&#xff0c;一直致力于用大白话讲解技术知识 &#x…

安全防御之防火墙篇(二)

目录 1.防火墙如何处理双通道协议&#xff1f; 2.防火墙如何处理NAT&#xff1f; 3.防火墙支持哪些NAT技术&#xff0c;主要应用的场景是什么&#xff1f; 4.当内网PC通过公网域名解析访问内网服务器的时候&#xff0c;会存在什么问题&#xff0c;如何解决&#xff1f;请详细…

【MySQL】CentOS编译安装MySQL5.7实战

前言 这篇文章是关于MySQL编译安装的&#xff0c;重点掌握的是编译的过程&#xff0c;以及体会排错的痛苦。出错在所难免&#xff0c;最重要的是要有一颗不放弃的心。 本文收录于《数据库入门与精通》专栏, 本专栏写作的过程中&#xff0c;联合了csdn几位DBA大佬&#xff0c;…

SpringBoot整合Kafka(包含Kafka_2.12-3.3.1单节点安装,kafka可视化程序efak v3.0.1安装)

SpringBoot整合Kafka&#xff08;包含Kafka_2.12-3.3.1单节点安装&#xff0c;kafka可视化程序efka v3.0.1安装&#xff09;kafka、efak安装包下载kafka安装资源下载&#xff1a;下载tgz安装包&#xff1a;http://archive.apache.org/dist/kafka/ //解压 tar -zxvf /home/soft/…

自定义类型的超详细讲解ᵎᵎ了解结构体和位段这一篇文章就够了ᵎ

目录 1.结构体的声明 1.1基础知识 1.2结构体的声明 1.3结构体的特殊声明 1.4结构体的自引用 1.5结构体变量的定义和初始化 1.6结构体内存对齐 那对齐这么浪费空间&#xff0c;为什么要对齐 1.7修改默认对齐数 1.8结构体传参 2.位段 2.1什么是位段 2.2位段的内存分配…

【java】笔试强训Day1

⛳选择题 1.在 Java 中&#xff0c;存放字符串常量的对象属于 &#xff08; &#xff09;类对象 A、Character B、String C、StringBuffer D、Vector &#x1f648;大家觉得答案是什么呢 &#x1f649;答案是…

GPT-4发布:人工智能新高度,以图生文技术震撼,短时间内挤爆OpenAI模型付费系统

“GPT-4&#xff0c;起飞&#xff01;”今日凌晨1点&#xff0c;OpenAI正式推出史上最强大的GPT-4文本生成AI系统 GPT-4&#xff1a;人工智能的新里程碑 你可能已经听说过GPT-3&#xff0c;它是一种能够生成自然语言文本的强大模型&#xff0c;可以用来回答问题、写文章、编程…

【Java SE】变量的本质

目录一. 前言二. 变量(variable)2.1 性质2.2 变量类型2.2.1 核心区别2.3 变量的使用三. 总结一. 前言 一天一个Java小知识点&#xff0c;助力小伙伴更好地入门Java&#xff0c;掌握更深层次的语法。 二. 变量(variable) 2.1 性质 变量本质上就是代表一个”可操作的存储空间”…

STL库中list的迭代器实现痛点分析

前文本篇文章准备换个模式&#xff0c;之前都是先详解模拟实现&#xff0c;但是模拟实现的基本逻辑大多数老铁都是明白的&#xff0c;所以我们这次主要讲解STL库中list的独特性&#xff0c;也就是模拟实现中的重难点文末有模拟实现的源码一&#xff0c;list实现的特殊类list实现…

【pytorch】使用deepsort算法进行目标跟踪,原理+pytorch实现

目录deepsort流程一、匈牙利算法二、卡尔曼滤波车速预测例子动态模型的概念卡尔曼滤波在deepsort中的动态模型三、预测值及测量值的含义deepsort在pytorch中的运行deepsort流程 DeepSORT是一种常用的目标跟踪算法&#xff0c;它结合了深度学习和传统的目标跟踪方法。DeepSORT的…

WireShark如何抓包,各种协议(HTTP、ARP、ICMP)的过滤或分析,用WireShark实现TCP三次握手和四次挥手

WireShark一、开启WireShark的大门二、如何抓包 搜索关键字2.1 协议过滤2.2 IP过滤2.3 过滤端口2.4 过滤MAC地址2.5 过滤包长度2.6 HTTP模式过滤三、ARP协议分析四、WireShark之ICMP协议五、TCP三次握手与四次挥手5.1 TCP三次握手实验5.2 可视化看TCP三次握手5.3 TCP四次挥手5.…

PCL 使用ICP点云拼接

一、简介 ICP算法详解——我见过最清晰的解释_负壹的博客-CSDN博客 两个点集&#xff0c;source和target&#xff0c;target不变&#xff0c;source经过旋转&#xff08;Rotation&#xff09;和平移&#xff08;Translation&#xff09;甚至加上尺度&#xff08;Scale&#x…

大聪明教你学Java | 深入浅出聊 SpringBoot 中的 starter 机制

前言 &#x1f34a;作者简介&#xff1a; 不肯过江东丶&#xff0c;一个来自二线城市的程序员&#xff0c;致力于用“猥琐”办法解决繁琐问题&#xff0c;让复杂的问题变得通俗易懂。 &#x1f34a;支持作者&#xff1a; 点赞&#x1f44d;、关注&#x1f496;、留言&#x1f4…

网络安全横向移动指南

在网络安全方面&#xff0c;了解威胁参与者的工具、技术和思维过程非常重要。 一旦对手获得对网络的初始访问权限&#xff0c;横向移动允许他们通过破坏目标组织网络中的其他主机来扩展访问权限并保持持久性。 威胁行为者可以收集有关公司用户活动和凭据、重要数据位置的信息…

Spark - 继承 FileOutputFormat 实现向 HDFS 地址追加文件

目录 一.引言 二.源码浅析 1.RDD.saveAsTextFile 2.TextOutputFormat 3.FileOutputFormat 三.源码修改 1.修改文件生成逻辑 - getRecordWriter 2.允许目录存在 - checkoutputSpecs 3.全部代码 - TextOutputFormatV2 四.追加存储代码实战 五.总结 一.引言 Output d…