[C++进阶]AVL树

news2024/9/20 16:32:37

前面我们说了二叉搜索树在极端条件下时间复杂度为O(n),本篇我们将介绍一种对二叉搜索树进行改进的树——AVL树

一、AVL 树的概念

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

AVL树具有以下性质:

1.AVL树的左右子树都是AVL树

2.左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1) (右子树的高度减去左子树的高度)

AVL树其实就是高度平衡二叉搜索树,

注意:这里的平衡不是相等,而是高度差不超过。其实是因为无法做到高度相等,只能做到高度差不超过1。因为总有一些结点是无法做到满二叉树的

如下是一颗典型的AVL树

对于这种AVL树,它的增删查改效率都是很高的。都是logN级别的复杂度

试想一下:

如果是满二叉树,当它高度为h的时候,他的总结点个数为2^h-1==N。

如果是平衡二叉树的话,当它的高度为h的时候,它的节点个数为2^{h-X==N}。这里的X属于[1,2(h-1)-1],所以它的效率为logN

二、AVL树的实现

1. AVL树的结点定义

如下所示,我们将AVL结点的值定义为一个pair对象,目的是使用key-value模型。bf是一个平衡因子。这里我们还需要使用三叉链结构

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

};

2. AVL树的插入部分

关于AVL树的插入,我们根据它的特性,不难得知,首先要先将一个值给插入进去,再去考虑控制平衡。

	bool Insert(const pair<K, V>& kv)
	{
		Node* newnode = new Node(kv);
		if (_root == nullptr)
		{
			_root = newnode;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first > newnode->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < newnode->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		if (parent->_kv.first > newnode->_kv.first)
		{
			parent->_left = newnode;
		}
		else
		{
			parent->_right = newnode;
		}
		newnode->_parent = parent;
		//平衡
        
		return true;
	}

如上代码所示,与二叉搜索树的方法类似,我们先想办法插入一个值进去。上面我们已经成功插入了一个数据。这一点是不难的。

  1. 我们先申请一个结点
  2. 如果根节点为空,那么我们直接将这个结点交给根节点即可。
  3. 如果根节点不为空,那么由于是二叉搜索树,那么我们先设定两个结点指针,一个指向空称作parent,一个指向根节点称作cur。
  4. 然后我们让cur去与我们要插入结点进行比较,持续迭代,最终我们就可以找出cur为待插入结点的位置,parent为要插入结点的父亲
  5. 然后我们根据待插入的值与父节点进行比较,从而确定插入左子树还是右子树。这一步是非常有必要的。

     在第五步中,我们极其容易犯一个错误,就是我们想当然的认为,cur此时就是要插入的孩子结点,所以我们会写出cur = newnode这样奇葩的代码,其实是万万不可的。我们可以画个内存图了解一下

  6. 插入号了结点以后,我们就可以开始将链接关系了。

3.AVL树的平衡因子的改变

将结点插入以后,我们需要做的就是控制平衡。因为我们AVL树的最终目的还是控制平衡。

我们先来看第一种情况

我们就以这颗树作为例子

它本身已经是一颗AVL树了,我们先考虑对右子树的插入

有以下几种情况:

通过这些图我们可以总结出如何更新平衡因子:

  • 新增在左,parent的平衡因子减减
  • 新增在右,parent的平衡因子加加
  • 更新后的parent的平衡因子==0,说明parent所在的子树高度不变,不再影响祖先,不再沿着祖先的路径往上更新
  • 更新后parent平衡因子==1/-1,说明parent所在子树的高度变化,影响了祖先,需要沿着祖先的路径往上更新
  • 更新后parent的平衡因子==2/-2,说明parent所在子树出现了问题,已经失衡,需要对parent所在子树进行旋转,使其平衡
  • 更新到根节点就可以结束了

最后两种情况出现平衡因子为不为0,-1或1的情况,此时需要进行旋转

我们先实现基础的更新平衡因子的函数

		//更新平衡因子
		cur = newnode;
		while (parent)
		{
			if (parent->_left == cur)
			{
				parent->_bf--;
			}
			else if (parent->_right == cur)
			{
				parent->_bf++;
			}
			else
			{
				assert(false);
			}
			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);
			}
		}

4. AVL树的插入之左旋

当一颗树(这棵树有可能是子树,也可能是一颗完整的树)插入一个结点以后,它会变为如下的形状(红色是插入的结点)

一开始,我们的树还是处于平衡状态的,但是当我们插入了一个结点以后,失去了平衡了,所以我们需要对其进行旋转使其进行平衡。

我们不难得知,上面两种树,都是右边的偏高,我们想,可以让cur作为这颗树的根节点,然后parent作为cur的左子树的话,那么就可以使得高度降低1次了。刚好使得我们的AVL树再次平衡了。

如下所示,当我们进行这样的旋转之后,我们会发现cur与parent的平衡因子都变为了0。

当我们插入了这个结点以后,我们就需要控制平衡了,我们控制平衡的核心操作就是

parent->_right = cur->_left;
cur->_left = parent

这两步操作执行完成之后,parent的左右子树高度一定是相同的,所以parent的平衡因子为0,而且cur的左子树的高度也变为了h+1,右子树由于插入了一个值所以也是h+1,恰好平衡因子也为0了。所以这样一来,使得树的高度整体降了一个且树平衡了。

上面的操作固然是很重要很核心的两步,不过要注意,我们的是三叉链模型,所以我们还需要改变_parent的指向,否则关系会十分混乱。而且还需要去考虑parent所代表的子树是不是一颗完整的树还是说一个子树。如果是子树那么又是左子树还是右子树呢?这些都是需要去考虑的。不过在上面的过程中,我们会发现,需要改变的结点一共就三个:parent,cur,cur->left这三个结点的指针需要进行修改。其他的结点,他们原本的位置是哪里,现在的相对位置仍然不变。

上面这种旋转方式其实就是由于右子树高了,所以需要向左旋转,即将一个结点给左边。我们将其称之为左旋这样的话,就能保证在保证它是搜索树的条件下,还能降低这个子树的高度了
 

5.AVL树的左旋图

如上是我们在左旋的情况下的抽象图,为什么要用一个抽象图呢?这是因为我们前面对于左旋的分析并不能完整的表示出左旋的全部情况。h可以是任意值

在我们插入一个新的结点之前,a/b/c都是符合AVL规则的子树

当h为0的时候。

当h为1的时候

以此类推种类会随着h的增大而变多,所以我们用那个图来表示,接下来是代码实现环节:

	void RotateL(Node* parent)
	{
		Node* pparent = parent->_parent;
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		//修改parent
		parent->_right = subRL;
		parent->_parent = subR;
		//修改subRL
		if (subRL)
		{
			subR->_parent=parent
		}
		//修改subR
		subR->_left = parent;
		subR->_parent = pparent;
		//修改pparent与subR
		if (pparent == nullptr)
		{
			_root = sub;
		}
		else 
		{
			if (pparent->_left == parent)
			{
				pparent->_left = cur;
			}
			else if (pparent->_right == parent)
			{
				pparent->_right = subR;
			}
		}
		parent->_bf = sub->_bf = 0;
	}
};

6.AVL树的右旋图

讲完了左旋图,我们来讲讲右旋,右旋的抽象图和左旋很相似

与左旋类似,它的三颗子树高度必须满足高度一样,否则就不是AVL树了,或者说插入一个结点之后还是平衡的。

当h==0时:

当h==1时

以此类推种类会随着h的增大而变多,所以我们用那个图来表示,接下来是代码实现环节:

	void RotateR(Node* parent)
	{
		Node* sub = parent->_left;
		Node* subR = cur->_right;
		Node* pparent = parent->_parent;

		parent->_left = curR;
		parent->_parent = cur;

		if (subR)
		{
			subR->_parent = parent;
		}

		sub->_right = parent;
		sub->_parent = pparent;

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

7. AVL树的双旋

我们在前面的抽象图中,主要讨论的是如果是右子树高,继续往右子树的右孩子插入的话,就会诱发左旋

但是如果我们像上面这种方式去左旋,我们会旋不明白,对吧,有没有人发现无论我们怎么单旋都是不行的!这时我们需要引入一个新的旋转思路,也就是双旋。 

遇到这种情况我们不会?没事,我们可以把上面的这种图转化成我们熟悉的样子:

比如我们先对60这边进行右旋然后我们发现熟悉的左旋,于是再对b左旋

8.AVL树的右左旋

当h==0的时候,即60这个结点就是新插入的结点,他的旋转图如下

当h==1的时候,这个结点可以在60的左边或者右边插入,都是满足条件的。因为都属于右凸出折线插入。仍然是先以90为旋转点进行右旋,然后以30为旋转点进行左旋。可见树平衡

当h==2的时候, 每个子树又呈现出了不同的情况

然而a和d一定是x/y/z中的任意一种,中间这颗子树一定是z形状的。由于我们已经进行了细分,所以中间这棵树我们直接画出具象图

变化共3*3*4=36种,但是无论是如何的变化,我们都无需担心,因为只要我们先对90进行右旋,然后对30进行左旋,就一定可以使得这棵树变为平衡。

9. AVL树的右左双旋的本质

关于右左双旋,我们似乎可以直接复用前面的左旋和右旋的代码。但是这样做的话会出现问题的。因为只是左旋和右旋的话会导致平衡因子被改变为0,但是实际上,平衡因子不为0。也就是说平衡因子需要再次处理一下。

我们在分析一下右左双旋,当h==1的时候,我们当时可以插入在左边或者右边都是可以的

60的左边变成了30的右边,60的右边变成了90的左边,60变成了这棵树的根.

我们通过观察最终平衡因子的变化,似乎就区域于插入到了60的左边还是右边。即取决于插入后60的平衡因子。由于双旋的本质,所以最终插入结束以后,如果是插入到了60的左边,那么左边将被平衡为0,右边将被平衡为1。如果插入到了60的右边,左边被平衡为-1,右边被平衡为0。

如果60本身就是新插入的结点的话,那么最终三个结点都为0

如果插入到了60的左侧,那么最终的变化如下图所示

如果插入到了60的左侧,那么最终的变化如下图所示

10. AVL树的左右双旋

左右双旋与右左双旋是呈镜像关系的

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

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

	if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else 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
	{
		assert(false);
	}
}
void RotateLR(Node *parent)
{
    Node *subL = parent->_left;
    Node *subLR = subL->_right;
    int bf = subLR->_bf;

    RotateLeft(subL); 
    RotateRight(parent); 
 

    if (bf == 0)
    {
        parent->_bf = subL->_bf = 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);
    }
}

11.AVL树的验证

1.验证有序

完成了平衡,那么我们这棵AVL树能正常使用嘛?让我们来验证一下吧!

在验证是否符合AVL树性质前,我们首先需要验证其是否是一棵二叉搜索树

在之前讲解二叉搜索树中提到过,如果中序遍历能够得到一个有序的序列,就说明是二叉搜索树

中序遍历代码如下:

void InOrder()
{
    _InOrder(_root);
    cout << endl;
}
 
void _InOrder(Node *root)
{
    if (root == nullptr)
        return;
    _InOrder(root->_left);
    cout << root->_kv.first << " "; // key/value模型,我们只打印key即可
    _InOrder(root->_right);
}

验证:

int main()
{
		AVLTree<int, int> t;
		int a[] = { 6, 3, 7, 11, 9, 26, 18, 14, 15 };
		for (auto e : a)
		{
			t.Insert({ e, e });
		}

		t.InOrder();
	return 0;
}

运行结果:

2.验证平衡

我们可以利用AVL树的规则,写出如下的代码判断一棵树是否为AVL树

	int _Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		return leftHeight > rightHeight ? 1 + leftHeight : 1 + rightHeight;
	}
	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);

	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

12. AVL树的删除

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与插入不同的是,删除节点后的平衡因子更新,如果更新后平衡因子是1或者-1,那么说明这棵树是不需要进行调整,如果平衡因子为0,说明原来是1或者-1,需要进行向上调整平衡因子。最差情况下一直要调整到根节点的位置 。他的更新策略与插入是相反的

13.AVL树的性能和代码

AVL树追求的是严格平衡,因此可以保证查找时高效的时间复杂度O(logN),但是如果我们需要频繁的对其进行旋转来维护平衡,一定程度上会影响效率,尤其是删除节点时的最差情况下我们可能需要一路旋转到根的位置。

相对于AVL树的严格平衡,红黑树则追求一种相对平衡,因此会略胜一筹,后面的文章中会对红黑树进行讲解。

AVL树的完整代码如下:

template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	
	pair<K, V> _kv;
	int _bf; //平衡因子
 
	AVLTreeNode(const pair<const K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
};
 
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool insert(const pair<const K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
 
		Node* parent = nullptr;
		Node* cur = _root;
 
		while (cur)
		{
			parent = cur;
			if (kv.first > cur->_kv.first)
				cur = cur->_right;
			else if (kv.first < cur->_kv.first)
				cur = cur->_left;
			else
				return false;
		}
 
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
 
		while (cur != _root)
		{
			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)
				{
					if (cur->_bf == 1)
					{
						RotateLeft(parent);
					}
					else if (cur->_bf == -1)
					{
						RotateRL(parent);
					}
				}
				else
				{
					if (cur->_bf == 1)
					{
						RotateLR(parent);
					}
					else if (cur->_bf == -1)
					{
						RotateRight(parent);
					}
				}
				break;
			}
			else
			{
				assert(false);
			}
		}
 
		return true;
	}
 
	void RotateLeft(Node* parent) //新节点插入较高右子树的右侧:左单旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
 
		parent->_right = subRL;
		if(subRL)
			subRL->_parent = parent;
 
		Node* parentParent = parent->_parent;
 
		if (parent != _root)
		{
			subR->_parent = parentParent;
			if (parent == parentParent->_left)
				parentParent->_left = subR;
			else
				parentParent->_right = subR;
		}
		else
		{
			_root = subR;
			subR->_parent = nullptr;
		}
 
		subR->_left = parent;
		parent->_parent = subR;
 
		parent->_bf = subR->_bf = 0;
	}
 
	void RotateRight(Node* parent) //新节点插入较高左子树的左侧:右单旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
 
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
 
		Node* parentParent = parent->_parent;
 
		if (parent != _root)
		{
			subL->_parent = parentParent;
			if (parent == parentParent->_left)
				parentParent->_left = subL;
			else
				parentParent->_right = subL;
		}
		else
		{
			_root = subL;
			subL->_parent = nullptr;
		}
 
		subL->_right = parent;
		parent->_parent = subL;
 
		parent->_bf = subL->_bf = 0;
	}
 
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
 
		RotateRight(subR);
		RotateLeft(parent);
 
		if (bf == 0)
		{
			parent->_bf = subR->_bf = subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
 
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;
 
		RotateLeft(subL);
		RotateRight(parent);
 
		if (bf == 0)
		{
			parent->_bf = subL->_bf = 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);
		}
	}
 
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
 
	bool IsBalance()
	{
		return _IsBalance(_root);
	}
 
	int Height()
	{
		return _Height(_root);
	}
 
	size_t Size()
	{
		return _Size(_root);
	}
 
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_kv.first)
				cur = cur->_right;
			else if (key < cur->_kv.first)
				cur = cur->_left;
			else
				return cur;
		}
		return nullptr;
	}
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
 
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;
		int leftHeigit = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		if (rightHeight - leftHeigit != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}
 
		return abs(rightHeight - leftHeigit) <= 1 
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}
 
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int higher = max(_Height(root->_left), _Height(root->_right));
		return higher + 1;
	}
 
	size_t _Size(Node* root)
	{
		if (root == nullptr)
			return 0;
		return _Size(root->_left) + _Size(root->_right) + 1;
	}
private:
	Node* _root = nullptr;
};

如有错误欢迎指正

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

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

相关文章

6个Python小游戏项目源码【免费】

6个Python小游戏项目源码 源码下载地址&#xff1a; 6个Python小游戏项目源码 提取码: bfh3

深度学习Day-33:Semi-Supervised GAN理论与实战

&#x1f368; 本文为&#xff1a;[&#x1f517;365天深度学习训练营] 中的学习记录博客 &#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制] 一、 基础配置 语言环境&#xff1a;Python3.8编译器选择&#xff1a;Pycharm深度学习环境&#xff1a; torch1.12.1c…

cout无法正常显示中文

cout无法正常显示中文 虽然你使用了buf.length()来指定写入的字节数&#xff0c;但是在包含中文字符&#xff08;UTF-8编码下每个中文字符占用3个字节&#xff09;的情况下&#xff0c;直接使用length()可能不会正确反映实际的字节数&#xff0c;因为它给出的是字符数而非字节…

基于深度学习,通过病理切片直接预测HPV状态|文献速递·24-09-16

小罗碎碎念 有段时间没有写文献速递的推文了&#xff0c;搞得自己今天写还怪不适应的。 今天所有的推文&#xff0c;都是围绕一个系统的问题展开——既研究了HPV与EBV在头颈癌/鼻咽癌中的致病机制&#xff0c;也总结了如何结合病理组学直接由WSI预测HPV状态——没办法&#x…

变压器漏感对整流电路的影响

目录 1. 电压波形畸变 2. 输出电压波动 3. 电流纹波增加 4. 降低整流效率 5. 影响开关器件的性能 6. EMI&#xff08;电磁干扰&#xff09;增加 总结与应对措施 变压器漏感在整流电路中会产生一些影响&#xff0c;尤其在高频应用或电流变化较大的情况下&#xff0c;其影…

【GESP】C++一级练习BCQM3006,多行输出

多行输出练习题&#xff0c;使用cout或printf函数输出多行内容。 BCQM3006 题目要求 描述 在windows的控制台环境中所有的字符都是等宽的&#xff0c;默认情况下窗口中每行有 80 个字符&#xff0c;每个屏幕有 25 行&#xff0c;组成了一个字符矩阵。利用控制台的这个特点&a…

什么是 HTTP/3?下一代 Web 协议

毫无疑问&#xff0c;发展互联网底层的庞大协议基础设施是一项艰巨的任务。 HTTP 的下一个主要版本基于 QUIC 协议构建&#xff0c;并有望提供更好的性能和更高的安全性。 以下是 Web 应用程序开发人员需要了解的内容。 HTTP/3 的前景与风险 HTTP/3 致力于让互联网对每个人…

从登录到免登录:JSP与Servlet结合Cookie的基本实现

前言 JSP中应用Cookie解析&#xff1a; 用户登录成功后&#xff0c;将用户信息保存到Cookie中&#xff0c;在页面读取Cookie并显示&#xff0c;不需要再次登录可以直接进入页面 第一步&#xff1a;创建JavaWeb项目&#xff0c;配置pom.xml文件 创建maven项目&#xff0c;项目名…

背包问题 总结详解

就是感觉之前 dp 的 blog 太乱了整理一下。 0-1 背包 例题:P1048 朴素算法 思路 对于一个物品&#xff0c;我们可以选&#xff0c;也可以不选。 我们用表示第 i 件物品的重量&#xff0c;表示第 i 件物品的价值。 考虑表示前 i 件物品放入容量为j的背包中的最大价值。 如…

时间复杂度计算 递归(solve2 后续)

原帖 最近校内比较忙&#xff0c;更新缓慢&#xff0c;致歉。 这里函数每次都需要遍历 h h h 和 m m m 之间的数&#xff08;复杂度 O ( n ) O(n) O(n)&#xff09;&#xff0c;所以和 solve1 略有不同。仍然假设 T ⁡ ( n ) \operatorname{T}(n) T(n) 表示 m − h 1 n…

【C++二叉树】606.根据二叉树创建字符串

606. 根据二叉树创建字符串 - 力扣&#xff08;LeetCode&#xff09; 图文分析&#xff1a; 代码实现&#xff1a; 代码说明&#xff1a; 1、前序遍历方式&#xff1a;根-左子树-右子树。 2、题目要求将二叉树转换为字符串输出&#xff0c;所以定义了一个string对象str。 3…

MySQL —— 视图

概念 视图是一张虚拟的表&#xff0c;它是基于一个或多个基本表或其他视图的查询结果集。 视图本身不存储数据&#xff0c;而是通过执行查询来动态生成数据&#xff0c;用户可以像操作普通表一样使用视图来进行查询更新与管理等操作。 视图本身也不占用物理存储空间&#xf…

网络安全学习(五)Burpsuite

经过测试&#xff0c;发现BP需要指定的JAVA才能安装。 需要的软件已经放在我的阿里云盘。 &#xff08;一&#xff09;需要下载Java SE 17.0.12(LTS) Java Downloads | Oracle 1.2023版Burp Suite 完美的运行脚本的环境是Java17 2.Java8不支持 看一下是否安装成功&#xff0c…

开源AI应用安全指导框架 — OWASP AI Exchange

在当今信息化迅猛发展的时代&#xff0c;网络专业人士正竞相提升人工智能&#xff08;AI&#xff09;安全领域的专业技能。随着这一趋势的推进&#xff0c;他们的企业也在快速地引入各类AI工具、平台、应用程序和服务&#xff0c;业界也相应涌现出众多资源&#xff0c;以协助从…

电梯电动车检测-目标检测数据集(包括VOC格式、YOLO格式)

电梯电动车检测-目标检测数据集&#xff08;包括VOC格式、YOLO格式&#xff09; 数据集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1qRMdF08Jinx_5CRa3al24A?pwd3twc 提取码&#xff1a;3twc 数据集信息介绍&#xff1a; 共有 5347 张图像和一一对应的标注文件 …

web基础—dvwa靶场(五)File Upload

File Upload(文件上传) 上传的文件对 web 应用程序来说是一个巨大的风险&#xff0c;许多攻击的第一步是上传攻击代码到被攻击的系统上&#xff0c;然后攻击者只需要找到方法来执行代码即可完成攻击。也就是是说&#xff0c;文件上传是攻击者需要完成的第一步。 不受限制的文件…

c#中给winform定义快捷键的几种方式

快捷键的使用在日常的开发中频率比较高&#xff0c;这里总结了最常见的各种快捷键的设置方式&#xff0c;需要的时候大家直接照抄就可以了&#xff0c;不用再去查询如何实现了。 文章目录 一、按钮快捷键二、菜单快捷键三、窗体快捷键四、全局快捷键1、重写ProcessCmdKey2、使…

C++内存管理详解:各类变量的存储区域

在C中&#xff0c;变量的存储位置取决于它们的类型和生命周期。那么不同的各个变量究竟存储在哪个区域呢&#xff1f; 1.不同类型的变量 我们首先从变量类型的不同来说明&#xff1a; 1. 全局变量和静态变量 - 存储区&#xff1a;全局/静态区&#xff08;静态区&#xff0…

深度学习----------------------文本预处理

目录 文本预处理读取数据集词源化词表该部分总代码该部分总代码 整合所有功能该部分总代码 文本预处理 文本预处理&#xff1a;把文本当作一个时序序列 将解析文本的常见预处理步骤。 这些步骤通常包括&#xff1a; ①将文本作为字符串加载到内存中。 ②将字符串拆分为词元&…

Redis的存储原理和数据模型

一、Redis是单线程还是多线程呢&#xff1f; 我们通过跑redis的代码&#xff0c;查看运行的程序可以得知&#xff0c;Redis本身其实是个多线程&#xff0c;其中包括redis-server&#xff0c;bio_close_file&#xff0c;bio_aof_fsync&#xff0c;bio_lazy_free&#xff0c;io_t…