【C++】AVLTree——高度平衡二叉搜索树

news2024/9/21 3:16:57

文章目录

    • 一、AVL树的概念
    • 二、AVL树节点的定义
    • 三、AVL树的插入
    • 四、AVL树的旋转
      • 1.左单旋
      • 2.右单旋
      • 3.左右双旋
      • 4.右左双旋
    • 五、进行验证
    • 六、AVLTree的性能

个人简介📝

🏆2022年度博客之星Top18;🏆2022社区之星Top2;的🥇C/C++领域优质创作者;

🥇阿里云专家博主;🥇华为云云享专家;🥇腾讯云年度进取作者;🥇掘金成长之星;

一、AVL树的概念

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

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

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

平衡因子= 右子树高度-左子树高度

image-20230211114707078

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


二、AVL树节点的定义

节点结构:三叉链结构(左、右、父),以及平衡因子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;//balance factor
	AVLTreeNode(const pair<K,V>&kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_bf(0)
	{}
};

三、AVL树的插入

AVL树在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。步骤过程

找到插入的位置:根据二叉搜索树的做法

进行插入:判断插入的位置是parent的左还是右

更新平衡因子:如果不平衡的话,就要进行旋转

找到插入位置(比较节点大小即可):

  • 插入的节点key值 > 当前位置的key值,往右子树走
  • 插入的节点key值 < 当前位置的key值,往左子树走
  • 插入的节点key值等于当前位置的key值,不能插入,返回false

插入之后,与二叉搜索树不同的是:我们还需要去进行平衡因子的更新,调平衡:

如果新增加的在右,平衡因子加加

如果新增加的在左,平衡因子减减

更新一个结点之后我们需要去进行判断,子树的高度是否发生了变化:

1.如果parent的平衡因子是0:说明之前parent的平衡因子是1或-1,说明之前parent一边高、一边低;这次插入之后填入矮的那边,parent所在的子树高度不变,不需要继续往上更新

2.如果parent的平衡因子是1或者-1:说明之前parent的平衡因子是0,两边一样高,插入之后一边更高,parent所在的子树高度发生变化,继续往上更新

3.平衡因子是2或-2,说明之前parent的平衡因子是1或-1,现在插入严重不平衡,违反规则,需要进行旋转处理

最坏的情况下:需要一直更新到根root:

image-20230211160543324

我们更新平衡因子时第一个更新的就是parent,如果parent->_bf1或parent->_bf-1需要继续往上进行平衡因子的更新,向上迭代,直到parent为空的情况:

else if (parent->_bf == 1 || parent->_bf == -1)
{
    cur = parent;
    parent = parent->_parent;
}

当parent->_bf = 2或parent->_bf==-2时,我们就需要进行旋转了

🔴如果parent的平衡因子是2,cur的平衡因子是1时,说明右边的右边比较高,我们需要进行左单旋

🔴如果parent的平衡因子是-2,cur的平衡因子是-1时,说明左边的左边比较高,我们需要进行右单旋

🔴如果parent的平衡因子是-2,cur的平衡因子是1时,我们需要进行左右双旋

🔴如果parent的平衡因子是2,cur的平衡因子是-1时,我们需要进行右左双旋

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

四、AVL树的旋转

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

旋转规则:

1.让这颗子树左右高度差不超过1

2.旋转过程中继续保持它是搜索树

3.更新调整孩子节点的平衡因子

4.让这颗子树的高度根插入前保持一致

1.左单旋

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

抽象图:

image-20230211213046519

a/b/c是高度为h的AVL子树,代表多数情况:h>=0,其中h可以等于0、1、2…,不过都可以抽象成h,处理情况都一样:此时parent等于2,subR等于1。

具体左旋的步骤:

subRL成为parent的右子树:注意subL和parent的关系,调整parent的右以及subRL的父(subRL可能为空)

parent成为subR的左子树:调整parent的父与subR的左

subR成为相对的根节点:调整subR与ppNode:注意parent是不是整棵树的root,如果是,则让subR为_root,同时让_root->_parent置为空

更新平衡因子

左旋调整:subR的左子树值(subRL)本身就比parent的值要大,所以可以作为parent的右子树;而parent及其左子树当中结点的值本身就比subR的值小,所以可以作为subR的左子树

**更新平衡因子bf:**subR与parent的bf都更新为0

image-20230211223628460

代码实现左旋转:

//左单旋
	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;
	}

2.右单旋

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

有了前面左旋的基础,我们在来看右旋就没有那么费劲了:

image-20230211233536643

a/b/c是高度为h的AVL树,右旋旋转动作:b变成60的左边,60变成30的右边,30变成子树的根。
30比60小,b值是处于30和60之间,此时作为60的左边是没有问题的。

有了这个图,在结合前面左单旋的基础,我们就能很快实现我们的右单旋代码:

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

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

3.左右双旋

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

image-20230212153433568

a/d是高度为h的AVL树,b/c是高度为h-1的AVL树。

以30为轴点进行左单旋:b变成30的右边,30变成60的左边,60变成子树根

image-20230212154430704

以90为轴点进行右单旋:c变成90的左边,90变成60的右边,60变成子树的根

image-20230212160647342

左右双旋:以subL为轴点左旋,以parent为轴点进行右旋,在进行平衡因子的更新(最大的问题)

我们从总体的角度来看,左右双旋的结果就是:就是把subLR的左子树和右子树,分别作为subL和parent的右子树和左子树,同时subL和parent分别作为subLR的左右子树,最后让subLR作为整个子树的根

subLR的左子树作为subL的右子树:因为subLR的左子树结点比subL的大

subLR的右子树作为parent的左子树:因为subLR的右子树结点比parent的小

平衡因子的更新:重新判断(识别插入节点是在b还是在c)根据subLR平衡因子的初始情况进行分类:

  • 如果subLR初始平衡因子是-1时,左右双旋后parent、subL、subLR的平衡因子分别更新为1、0、0(插入在b)

image-20230212165105682

  • 如果subLR的初始平衡因子是1时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、-1、0(插入在c)

image-20230212170233716

  • 如果subLR初始平衡因子是0时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、0、0(subLR自己新增)

image-20230212171341596

代码实现

//左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR ->_bf;
		RotateL(parent->_left);
		RotateR(parent);
		//更新平衡因子
		if (bf == -1)//b插入,subLR左子树新增
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}
		else if (bf == 1)//c插入,subLR右子树新增
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0)//subLR自己新增加
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

4.右左双旋

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

插入

image-20230212213033620

subR为轴点进行右单旋:

image-20230212214355843

parent为轴进行左单旋:

image-20230212215237967

既右左双旋:

image-20230212215643450

右左双旋后,根据subRL 初始平衡因子的不同分为三种情况分别对应subRL = 0、1、-1情况,与左右双旋情况类似。

void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(subR);
		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);
		}
	}

五、进行验证

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

  • 验证其为二叉搜索树
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
  • 验证其为平衡树
    每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)节点的平衡因子是否计算正确

    如果是空树,是AVL树;高度差不大于2,并且递归左右子树的高度差都不大于2,也是AVL树;判断平衡因子和该点的高度差是否相等

//求高度
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(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
			&& IsBalance(root->_left)
			&& IsBalance(root->_right);
	}

六、AVLTree的性能

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


送上源码:

#pragma once
#include <iostream>
#include <assert.h>
#include <time.h>
using namespace std;
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;//balance factor
	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:
	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->_left)
			{
				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* 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;
	}

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

		//if(_root==parent)
		if (ppNode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}
		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)//b插入,subLR左子树新增
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}
		else if (bf == 1)//c插入,subLR右子树新增
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0)//subLR自己新增加
		{
			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(subR);
		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);
		}
	}
	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 lh = Height(root->_left);
		int rh = Height(root->_right);

		return lh > rh ? lh + 1 : rh + 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 << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		return abs(rightHeight - leftHeight) < 2
			&& IsBalance(root->_left)
			&& IsBalance(root->_right);
	}
private:
	Node* _root = nullptr;
};

//测试
void TestAVLTree()
{
	//int a[] = { 8,3,1,10,6,4,7,14,13 };
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4,2,6,1,3,5,15,7,16,14 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e,e));
	}
	t.InOrder();
	cout << t.IsBalance() << endl;
}

void TestAVLTree2()
{
	srand(time(0));
	const size_t N = 100000;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; i++)
	{
		size_t x = rand();
		t.Insert(make_pair(x, x));
	}
	//t.InOrder();
	cout << t.IsBalance() << endl;
}

本篇结束…

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

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

相关文章

JVM类加载子系统

1、类加载子系统在内存结构中所处的位置通过内存结构图&#xff0c;我们先知道类加载子系统所处的位置&#xff0c;做到心中有图。2、类加载器作用类加载器子系统负责从文件系统或者网络中加载Class文件&#xff0c;class文件在文件开头有特定的文件标识。ClassLoader只负责cla…

anaconda创建环境为空、修改默认环境位置

无论是用navigator还是命令行创建环境都无法指定python版本conda create -n test python3.9其实就是没有路径&#xff0c;添加几个镜像就好&#xff1a;conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels ht…

BUUCTF-[安洵杯 2019]crackMe1

题目下载&#xff1a;下载 这道题涉及到SM4加密和变表base64。 SM4简单了解&#xff1a;SM4算法过程_不是小白才怪的博客-CSDN博客_sm4算法 先运行一下程序&#xff0c; 发现有一个Messagebox&#xff0c;并且内容是hooked。 载入IDA&#xff0c;使用IDA的插件Findcrypt查…

ChatGPT:“抢走你工作的不会是 AI ,而是先掌握 AI 能力的人”

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; ChatGPT&#xff1a;“抢走你工作的不会是 AI &#xff0c;而是先掌握 AI 能力的人” ChatGPT&#xff1a;美国OpenAI 研发的聊天机器人程序&#xff0c;人工智能技术…

Springboot启动过程分析

Springboot启动过程分析 SpringBoot的版本是v3.0.2&#xff0c;下面进行详细的分析。 一、SpringBoot启动流程的主干 示例程序入口如下所示&#xff1a; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApp…

【网络知识】TCP和UDP详解

TCP和UDP 文章目录UDP协议概述TCP协议概述TCP报文段TCP连接的建立两天内完成下面的参考博客&#x1f60a;点此到文末惊喜↩︎ UDP协议 概述 TCP协议 概述 定义 传输控制协议&#xff08;TCP&#xff0c;Transmission Control Protocol&#xff09;是一种传输层通信协议&…

Python 之 Pandas DataFrame 数据类型的简介、创建的列操作

文章目录一、DataFrame 结构简介二、DataFrame 对象创建1. 使用普通列表创建2. 使用嵌套列表创建3 指定数值元素的数据类型为 float4. 字典嵌套列表创建5. 添加自定义的行标签6. 列表嵌套字典创建 DataFrame 对象7. Series 创建 DataFrame 对象三、DataFrame 列操作1. 选取数据…

【LeetCode】剑指 Offer(5)

目录 写在前面&#xff1a; 题目&#xff1a; 题目的接口&#xff1a; 解题思路1&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 解题思路2&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a;…

臻和科技再冲刺港交所上市:近三年亏损14亿元,有股东提前退出

近日&#xff0c;臻和科技集团有限公司&#xff08;下称“臻和科技”&#xff09;再次递交招股书&#xff0c;准备在港交所主板上市。据贝多财经了解&#xff0c;这已经是臻和科技第二次冲刺港交所上市。在此之前&#xff0c;臻和科技曾于2022年9月26日递表&#xff0c;后选择了…

hadoop02【尚硅谷】

HDFS 大数据学习笔记 一、HDFS产出背景及定义 HDFS产生背景 随着数据量越来越大&#xff0c;在一个操作系统存不下所有的数据&#xff0c;那么就分配到更多的操作系统管理的磁盘中&#xff0c;但是不方便管理和维护&#xff0c;迫切需要一种系统来管理多台机器上的文件&#x…

python基于vue的酒店预约管理平台系统

当用户在上一步中的房间展示界面中点击了房间的图片或者名称之后系统会根据房间的ID自动的跳转到房间的详情页面中来&#xff0c;在房间的详情页面中可以看到房间的图片房间的价格房间的详细介绍房间的类型等内容&#xff0c;当用户登录之后还可以根据需要进行对房间进行预定&a…

Vulnhub靶场之PYLINGTON: 1

1.信息收集 1.输入arp-scan 192.168.239.0/24&#xff0c;探索存活主机&#xff0c;发现主机192.168.239.172存活。 2.对存活主机进行端口扫描&#xff0c;发现22(SSH)、80(Web)端口。 3.访问80端口&#xff0c;在浏览器上输出&#xff1a;http://192.168.239.172。 4.查看…

工具篇3.5世界热力图

一、定义 世界热力图是一种地图形式&#xff0c;它使用颜色的变化来显示世界各个地区的某种指标&#xff08;如 GDP、人口、气候等&#xff09;的分布和密度。通常&#xff0c;世界热力图会使用不同的颜色来表示数据的变化&#xff0c;例如使用蓝色表示低值&#xff0c;红色表…

算法leetcode|37. 解数独(rust重拳出击)

文章目录37. 解数独&#xff1a;样例 1&#xff1a;提示&#xff1a;分析&#xff1a;题解&#xff1a;rustgoccpythonjava37. 解数独&#xff1a; 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现…

如何配置git,使其支持多用户

如何配置git&#xff0c;使其支持多用户&#xff1f; 在多数时候&#xff0c; 我们使用git进行操作时&#xff0c;只需要在本地配置一个用户的ssh key&#xff0c;就可以完成基本的pull/push操作。如果现在我有两个github的账号&#xff0c;并需要在一台电脑中操作其中的repo&…

项目管理工具dhtmlxGantt甘特图入门教程(十):服务器端数据集成(下)

这篇文章给大家讲解如何利用dhtmlxGantt在服务器端集成数据。 dhtmlxGantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表&#xff0c;可满足应用程序的所有需求&#xff0c;是完善的甘特图图表库 DhtmlxGantt正版试用下载&#xff08;qun 764149912&#xff09;http…

LVGL WIN32模拟器环境搭建

LVGL WIN32模拟器环境搭建LVGL简介环境搭建IDE 选择模拟器代码下载PC模拟器搭建其他配置项说明LVGL简介 LVGL是一个跨平台、轻量级、易于移植的图形库。因其支持大量特性和其易于裁剪&#xff0c;配置开关众多&#xff0c;且版本升级较快&#xff0c;不同版本之间存在一定的差…

基于springboot+vue的医院信息管理系统

基于springbootvue的医院信息管理系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介…

2、登录界面开发

【任务描述】本任务要求使用线性布局以及TextView、EditText、Button等常见控件完成智慧园区登录界面的开发。1、线性布局&#xff08;LinearLayout&#xff09;1.1、LinearLayout概述线性布局&#xff08;LinearLayout&#xff09;主要以水平或垂直方式来排列界面中的控件。并…

【C++修炼之路】20.手撕红黑树

每一个不曾起舞的日子都是对生命的辜负 红黑树实现:RBTree 前言一.红黑树的概念及性质1.1 红黑树的概念1.2 红黑树的性质二.红黑树的结构2.1 红黑树节点的定义2.2 红黑树类的封装三.红黑树的插入情况1&#xff1a;只变色情况2&#xff1a;变色单旋情况3&#xff1a;双旋插入的代…