【C++】二叉搜索树+变身 = AVL树

news2024/11/29 22:37:28
头像
🚀个人主页:@小羊
🚀所属专栏:C++
很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~

动图描述

目录

  • 前言
  • 一、AVL树
  • 二、AVL树的实现
    • 2.1 平衡因子
    • 2.2 旋转处理
      • 2.2.1 左单旋:插入新节点后单纯的右边高
      • 2.2.2 右单旋:插入新节点后单纯的左边高
      • 2.2.3 左右旋:插入新节点后不是单纯的左边高
      • 2.2.4 右左旋:插入新节点后不是单纯的右边高
    • 2.3 验证AVL树的平衡
  • 三、完整代码


前言

本文仅适合了解二叉搜索树,但不了解AVL树底层原理的同学阅读哦。

本篇文章不会带你从头到尾实现AVL树,但会带你深入理解AVL树是怎么实现平衡的,怎么通过旋转变换实现保持平衡,以及实现平衡过程中的细节应该怎么处理等。


一、AVL树

前面的文章中我们分析过二叉搜索树的性能,得到的结果是理想情况下二叉搜索树的时间复杂度为O(LogN),但在极端情况下(即树蜕化为单边树时),这些操作的时间复杂度会退化为O(n),即使情况不那么极端,效率也不是特别高。

为了防止二叉搜索树出现一边偏高的情况,就需要想办法让二叉搜索树尽量保持平衡,所以两位苏联数学家(或称为俄罗斯数学家)G.M. Adelson-Velsky和E.M. Landis就发明了AVL树,其任何节点的两个子树的高度最大差别为1。

AVL树是具有一下性质的二叉搜索树:

  • 其左右子树都是AVL树
  • 左右子树高度差不超过1

二、AVL树的实现

本篇文章将沿用之前文章中Key-Value模型的代码,不再从底层开始实现,主要介绍在插入新节点后如何保持二叉搜索树的平衡问题。

2.1 平衡因子

如何保证AVL树的左右子树高度差不超过1?在AVL树的每个节点中存一个平衡因子,本文我们定义平衡因子 = 此节点右子树的高度 - 左子树的高度

  • 插入在左子树,平衡因子 - -
  • 插入在右子树,平衡因子++

更新祖先节点的平衡因子时,我们首先需要找到祖先节点,因此每个节点中还需要增加一个指向父节点的指针。
按照我们的需求,其AVL树的节点可以定义为:

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

是否继续往上更新祖先节点的平衡因子,要看parent所在子树的高度是否发生变化。

插入新节点后其父节点的平衡因子有以下几种情况:

  1. parent的平衡因子 == 0
    parent的平衡因子更新前是 -1 / 1,新节点插入在矮的那边,高度不变,不再往上更新
  2. parent的平衡因子 == 1 / -1
    parent的平衡因子更新前是0,parent所在子树高度都变化了,需要往上更新
  3. parent的平衡因子 == 2 / -2
    parent的平衡因子更新前是 -1 / 1,插入新节点后树不再平衡,需要旋转处理
pcur = new Node(kv);
if (parent->_kv.first > kv.first)//判断新节点应该插入左还是右
{
	parent->_left = pcur;
}
else
{
	parent->_right = pcur;
}
pcur->_parent = parent;//与父节点链接关系

while (parent)//有可能更新到根节点去
{
	parent->_bf = parent->_left == pcur ? parent->_bf - 1 
	: parent->_bf + 1;
	if (parent->_bf == 0)//插入前后高度不变
	{
		break;
	}
	else if (parent->_bf == 1 || parent->_bf == -1)
	{
		//高度变了,继续往上更新
		pcur = parent;
		parent = parent->_parent;
	}
	else if (parent->_bf == 2 || parent->_bf == -2)
	{
		//插入节点后二叉树不平衡了,需要旋转处理
	}
	else
	{
		assert(false);//检测AVL树是否异常
	}
}

2.2 旋转处理

当二叉搜索树出现不平衡的情况时,需要旋转处理,对应插入后二叉搜索树的各种情况,主要有四种旋转的方式来保持平衡。

其中:

  • h代表子树的高度,可以是0、1、2…
  • 我们用能代表所有情况的四种类型的抽象图来研究旋转方式,单纯研究某几种情况没有意义

原则:

  1. 保持搜索树的性质
  2. 降低高度,控制平衡

2.2.1 左单旋:插入新节点后单纯的右边高

在这里插入图片描述

旋转处理过程中,我们主要关注三个节点(以上图为例):10(标记为parent)、30(标记为subR)、b(标记为subLR)。

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

  1. subR的左孩子可能存在,也可能不存在
  2. parent可能是根节点,也可能是子树。如果是根节点,旋转完成后,要更新根节点;如果是子树,可能是某个节点的左子树,也可能是右子树
//左单旋
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;//subRL是有可能为空的

	parent->_right = subRL;
	subR->_left = parent;
	Node* parentparent = parent->_parent;
	parent->_parent = subR;
	if (parentparent == nullptr)//subR有可能变成根
	{
		_root = subR;
	}
	else
	{
		if (parentparent->_left == parent)
		{
			parentparent->_left = subR;
		}
		else
		{
			parentparent->_right = subR;
		}
	}
	subR->_parent = parentparent;
	if (subRL)
	{
		subRL->_parent = parent;
	}

	parent->_bf = subR->_bf = 0;//更新平衡因子
}

旋转处理过程中主要是处理各节点的父节点指针的指向和平衡因子的更新。


2.2.2 右单旋:插入新节点后单纯的左边高

在这里插入图片描述

其处理方式和左单旋相似,可参考左单旋。

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

	parent->_left = subLR;
	subL->_right = parent;
	Node* parentparent = parent->_parent;
	parent->_parent = subL;
	if (parentparent == nullptr)
	{
		_root = subL;
	}
	else
	{
		if (parentparent->_left == parent)
		{
			parentparent->_left = subL;
		}
		else
		{
			parentparent->_right = subL;
		}
	}
	subL->_parent = parentparent;
	if (subLR)
	{
		subLR->_parent = parent;
	}
	subL->_bf = parent->_bf = 0;
}

2.2.3 左右旋:插入新节点后不是单纯的左边高

在这里插入图片描述

这种情况只用左旋或右旋只会原地打转,不能降低平衡。
我们需要先对subL进行左单旋,再对parent进行右单旋,最后更新平衡因子。

  • 双旋后平衡因子的更新要根据插入新节点后subLR的平衡因子来分情况讨论
  • 双旋最终结果是把subLR推到最上面,让其平衡因子为0
//左右旋
void RotateLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;

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

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

2.2.4 右左旋:插入新节点后不是单纯的右边高

在这里插入图片描述

可参考左右旋。

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

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


2.3 验证AVL树的平衡

我们可以分别计算出其左子树和右子树的高度,将其相减的值与节点中记录的平衡因子的值比较,看是否符合我们的预期。

int _Height(Node* root)
{
	if (root == nullptr)
	{
		return 0;
	}
	int leftheight = _Height(root->_left);
	int rightheight = _Height(root->_right);
	return leftheight > rightheight ? leftheight + 1 : rightheight + 1;
}

bool _isBalanceTree(Node* root)
{
	if (root == nullptr)
	{
		return true;
	}
	int leftheight = _Height(root->_left);
	int rightheight = _Height(root->_right);
	int bf = rightheight - leftheight;
	if (abs(bf) > 1)
	{
		cout << root->_kv.first << "高度差异常" << endl;
		return false;
	}
	if (root->_bf != bf)
	{
		cout << root->_kv.first << "平衡因子异常" << endl;
		return false;
	}
	return _isBalanceTree(root->_left) && _isBalanceTree(root->_right);
}

三、完整代码

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>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:

	AVLTree() = default;

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

	AVLTree<K, V>& operator=(AVLTree<K, V> t)
	{
		swap(_root, t._root);
		return *this;
	}

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

	bool Find(const K& key)
	{
		Node* pcur = _root;
		while (pcur)
		{
			if (key < pcur->_kv.first)
			{
				pcur = pcur->_left;
			}
			else if (key > pcur->_kv.first)
			{
				pcur = pcur->_right;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

	bool Insert(const pair<K, V>& kv)
	{
		//没有节点时需要单独处理
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		Node* pcur = _root;
		Node* parent = nullptr;
		while (pcur)
		{
			if (kv.first < pcur->_kv.first)
			{
				parent = pcur;
				pcur = pcur->_left;
			}
			else if (kv.first > pcur->_kv.first)
			{
				parent = pcur;
				pcur = pcur->_right;
			}
			else
			{
				return false;
			}
		}
		pcur = new Node(kv);
		if (parent->_kv.first > kv.first)//判断新节点应该插入左还是右
		{
			parent->_left = pcur;
		}
		else
		{
			parent->_right = pcur;
		}
		pcur->_parent = parent;//与父节点链接关系

		//更新平衡因子
		while (parent)//有可能更新到根节点去
		{
			parent->_bf = parent->_left == pcur ? parent->_bf - 1 : parent->_bf + 1;
			if (parent->_bf == 0)//插入前后高度不变
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//高度变了,继续往上更新
				pcur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//插入节点后二叉树不平衡了,需要旋转处理
				if (parent->_bf == 2 && pcur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && pcur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && pcur->_bf == -1)
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && pcur->_bf == 1)
				{
					RotateLR(parent);
				}
				break;//不管是哪种情况,旋转完后子树的高度没有变化,所以不再调整
			}
			else
			{
				assert(false);//检测AVL树是否异常
			}
		}
		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool IsBalanceTree()
	{
		return _isBalanceTree(_root);
	}

private:

	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;//subRL是有可能为空的

		parent->_right = subRL;
		subR->_left = parent;
		Node* parentparent = parent->_parent;
		parent->_parent = subR;
		if (parentparent == nullptr)//subR有可能变成根
		{
			_root = subR;
		}
		else
		{
			if (parentparent->_left == parent)
			{
				parentparent->_left = subR;
			}
			else
			{
				parentparent->_right = subR;
			}
		}
		subR->_parent = parentparent;
		if (subRL)
		{
			subRL->_parent = parent;
		}

		parent->_bf = subR->_bf = 0;//更新平衡因子
	}

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

		parent->_left = subLR;
		subL->_right = parent;
		Node* parentparent = parent->_parent;
		parent->_parent = subL;
		if (parentparent == nullptr)
		{
			_root = subL;
		}
		else
		{
			if (parentparent->_left == parent)
			{
				parentparent->_left = subL;
			}
			else
			{
				parentparent->_right = subL;
			}
		}
		subL->_parent = parentparent;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		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 == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
			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 == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	Node* copy(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		Node* copynode = new Node(root->_kv);
		copynode->_left = copy(root->_left);
		copynode->_right = copy(root->_right);
		return copynode;
	}

	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;
		Destroy(root->_left);
		Destroy(root->_right);
		delete 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 leftheight = _Height(root->_left);
		int rightheight = _Height(root->_right);
		return leftheight > rightheight ? leftheight + 1 : rightheight + 1;
	}

	bool _isBalanceTree(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		int leftheight = _Height(root->_left);
		int rightheight = _Height(root->_right);
		int bf = rightheight - leftheight;
		if (abs(bf) > 1)
		{
			cout << root->_kv.first << "高度差异常" << endl;
			return false;
		}
		if (root->_bf != bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}
		return _isBalanceTree(root->_left) && _isBalanceTree(root->_right);
	}

private:
	Node* _root = nullptr;
};

本篇文章的分享就到这里了,如果您觉得在本文有所收获,还请留下您的三连支持哦~

头像

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

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

相关文章

Linux dlsym符号查找疑惑分析

dlsym 函数是 Linux 下动态链接库&#xff08;shared library&#xff09;编程中的一个重要函数。它用于在运行时获取动态链接库中符号的地址&#xff0c;通常用于获取函数指针或变量的地址。 以下是 dlsym 函数的基本用法和示例。 1. 函数原型 void *dlsym(void *handle, c…

3. OOP封装 -- get、set方法

文章目录 1. 抛出问题&#xff1a;存在安全隐患2. 那咋办呢&#xff1f;3. 上述方案行不通&#xff0c;只能引入封装的概念① public向private过渡② 这种情况下如何修改信息&#xff0c;使用get和set方法如何限制用户输入企业中为什么不用public快捷键生成所有私有成员变量的g…

老系统处理策略

1. 改造策略 定义&#xff1a;对技术含量高、业务价值大的遗留系统进行功能增强和数据模型改造。 适用场景&#xff1a;系统较新&#xff0c;能满足业务需求&#xff0c;但需增加新功能或优化数据模型。 优点&#xff1a;增强功能&#xff0c;优化数据&#xff0c;提升系统性…

第一批用大模型的程序员,已经碾压同事了...

自 ChatGPT 面世以来&#xff0c;市场上一直用“iPhone 时刻”“划时代”“工业革命”等关键词来形容 AI 领域的飞速进展。如今&#xff0c;AI 大模型的战争已经开启大卷特卷模式。 OpenAI 炸裂推出 GPT-4o&#xff0c;科幻电影照进现实&#xff0c;不仅免费可用&#xff0c;能…

Buck电路-电感电容计算

目录&#xff1a; 1、前置知识 1&#xff09;电感的公式 2&#xff09;电容的公式 3&#xff09;Buck电路框图 2、占空比D的计算 1&#xff09;Switch(on)状态 2&#xff09;Switch(off)状态 3&#xff09;占空比计算 3、电感计算 4、电容计算 5、电荷平衡与伏秒…

企业数字化转型中优化IT投资与资源管理的战略路径

IT投资优化与资源管理在数字化转型中的关键作用 在数字化时代&#xff0c;企业的成功不仅取决于其业务创新和市场拓展&#xff0c;还极大依赖于信息技术&#xff08;IT&#xff09;投资和资源管理的效率。随着云计算、大数据、人工智能等技术的飞速发展&#xff0c;企业必须在…

Python 能用来开发桌面客户端吗?

Python 作为一门多功能、跨平台的编程语言&#xff0c;适用于不同领域的开发&#xff0c;包括桌面客户端程序。在桌面客户端开发中&#xff0c;Python 以其简洁、可读性高的语法和广泛的第三方库生态圈提供了强大的支持。尽管 Python 的强项可能更多地体现在 web 开发、数据分析…

《精通开关电源设计》笔记一

重点 效率 纹波 环路响应 尺寸&#xff0c;从静态到动态的研究方法&#xff0c;假设开关电源稳态运行&#xff0c;以电感为中心&#xff0c;根据半导体器件(mos管或二极管)分段分析电路的状态&#xff0c;工具有电路原理和能量守恒 影响效率的主要是开关损耗&#xff0c;所以…

速览!2024 CSP-J1/S1 河北也被实名举报泄题

据NOI官网消息&#xff0c;继2024 CSP-J/S第一轮认证陕西鸿泉培训机构泄题之后&#xff0c;重考&#xff01;CSP-J/S 2024第一轮认证泄题后续进展及疑问&#xff0c;河北某学校也被学生实名举报泄题&#xff0c;河北某同学在认证前一天以非正当手段获得了认证题目且属实&#x…

码随想录算法训练营第62天|卡码网:97. 小明逛公园、127. 骑士的攻击

1. 卡码网 97. 小明逛公园 题目链接&#xff1a;https://kamacoder.com/problempage.php?pid1155 文章链接&#xff1a;https://www.programmercarl.com/kamacoder/0097.小明逛公园.html 思路&#xff1a; 使用Floyd 算法&#xff0c;目的是解决多源最短路问题&#xff0c;即 …

【11】纯血鸿蒙HarmonyOS NEXT星河版开发0基础学习笔记-模块化语法与自定义组件

序言&#xff1a; 本文详细讲解了关于鸿蒙系统学习中的模块化语法与自定义组件&#xff0c;在模块化语法中我们学习到了多种导入导出方式&#xff0c;实现了在一个项目中&#xff0c;通过引用不同的组件&#xff0c;让我们整体代码的可读性更强&#xff0c;相当于我们把一个手…

【系统方案】系统设计方案书,可视化设计方案(word)

第 一 章 系统总体设计 1.1 总体架构 1.1.1 系统拓扑 1.1.2 系统组成 1.2 设计概述 1.3 平台系统功能 1.3.1 总部数据看板 1.3.2 项目部数据看板 1.3.3 视频联网系统 1.3.4 实名制考勤系统 1.3.5 安全生产系统 1.3.6 塔吊安全监控子系统 1.3.7 施工升降机安全监控管系统 1.3.8 …

聊天记录怎么监控?企业微信聊天记录监控的2个方法分享!员工权益vs企业管理

在企业管理与员工权益的平衡中&#xff0c;聊天记录的监控成为了一个备受争议的话题。 一方面&#xff0c;企业希望通过监控聊天记录来确保信息安全、规范员工行为&#xff0c;并防止潜在的风险&#xff1b; 另一方面&#xff0c;员工则强调个人隐私和沟通自由的重要性。 本文…

大模型技术进阶路线,有了基础应该怎么进阶?

“ 高性能大模型的打造&#xff0c;是一项复杂的系统性工程 ” 在上一篇文章中讲了学习大模型的基础路线&#xff0c;而如果是对有一定基础的人来说&#xff0c;应该怎么进阶呢&#xff1f;也就是说大模型更加高级的技术栈有哪些&#xff1f; 一个好的基础能够让你在学习的道…

《向量数据库指南》——Mlivus Cloud打造生产级AI应用利器

哈哈,各位向量数据库和AI应用领域的朋友们,大家好!我是大禹智库的向量数据库高级研究员王帅旭,也是《向量数据库指南》的作者。今天,我要和大家聊聊如何使用Mlivus Cloud来搭建生产级AI应用。这可是个热门话题哦,相信大家都非常感兴趣! 《向量数据库指南》 使用Mlivus …

降低大模型幻觉的5种方案

降低大模型幻觉的5种方案 大语言模型&#xff08;如GPT-4&#xff09;在生成文本时&#xff0c;有时会产生所谓的“幻觉”——即生成的内容虽然语法和逻辑上看似正确&#xff0c;但实际上是不准确或虚构的。为了减少这种现象&#xff0c;以下是五种有效的方案&#xff1a;Prom…

必备指南:人人适用的AI大模型学习路径!

23年 AI 大模型技术狂飙一年后&#xff0c;24年 AI 大模型的应用已经在爆发&#xff0c;因此掌握好 AI 大模型的应用开发技术就变成如此重要&#xff0c;那么如何才能更好地掌握呢&#xff1f;一份 AI 大模型详细的学习路线就变得非常重要&#xff01; 由于 AI 大模型应用技术…

R语言绘制散点图

散点图是一种在直角坐标系中用数据点直观呈现两个变量之间关系、可检测异常值并探索数据分布的可视化图表。它是一种常用的数据可视化工具&#xff0c;我们通过不同的参数调整和包的使用&#xff0c;可以创建出满足各种需求的散点图。 常用绘制散点图的函数有plot()函数和ggpl…

图解IP分类及子网掩码计算实例

一、什么是IP地址 在网络世界中&#xff0c;人们为了通信方便给每一台计算机都事先分配一个类似电话号码一样的标识地址&#xff0c;即IP地址。根据TCP/IP协议&#xff0c;IP地址由32位二进制数组成&#xff0c;而且在INTERNET范围内是唯一的。假如某台计算机IP地址为11000000…

基于SpringBoot vue 医院病房信息管理系统设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php python(flask Django) 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找…