C++数据结构 -- AVL树

news2024/11/25 9:53:21

目录

  • 一、什么是AVL树?
    • AVL树的概念
  • 二、 AVL树的节点的定义
  • 三、 AVL树新结点的插入
    • 3.1 左单旋
    • 左单旋代码实现
    • 3.2 右单旋
    • 右单旋代码实现
    • 3.3 左单旋或者右单旋解决不了的问题
    • 3.4 左右双旋
    • 左右双旋代码实现
    • 3.5 右左双旋
    • 右左双旋代码实现
  • 四、代码汇总

一、什么是AVL树?

AVL树的概念

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

一棵AVL树是空树或者是具有以下性质的二叉搜索树:
它的左右子树都是AVL树左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)(我们用的是右子树高度减左子树高度)
在这里插入图片描述

像这样的一颗树,每一个节点的平衡因子的绝对值都小于2的树就称为AVL树。
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索的时间复杂度为O( l o g 2 n log_2 n log2n)。
在这里插入图片描述

这里就有一个问题了,AVL树又称高度平衡二叉搜索树,那么既然是平衡搜索树,为什么不是高度相等,而是高度差不大于1呢?显然,它是做不到在任何情况下都是平衡的,为什么呢?通过以下图片你就能知道答案了。
在这里插入图片描述

二、 AVL树的节点的定义

	//KV模型
	template <class K, class V>
	struct AVLTreeNode
	{
	public:
		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树也可以看成是二叉搜索树。

AVL树插入新结点分为两步:
1、按照二叉搜索树的方式插入节点。
2、检查平衡因子并调整树平衡。
在这里插入图片描述
在这里插入图片描述
假设现在插入18
在这里插入图片描述
调整父节点parent的平衡因子:
在这里插入图片描述

根据上图可以看出插入了18之后parent的平衡因子变成了1,证明插入节点后影响了以parent为根节点的这颗子树的高度,需要继续沿祖先路径检查平衡因子。

在这里插入图片描述
可以看到,再往上更新一次发现parent的平衡因子变成了2,所以以parent为根节点的这颗子树需要进行旋转来调整平衡,那么如何旋转呢??

现在以parent为根节点的这棵子树明显是右子树高,左子树低,所以需要进行左单旋,那么如何旋转呢??

3.1 左单旋

在这里插入图片描述
有人说这不对啊,你这里只是随机拿了一种情况出来是这样子旋转而已,怎么能代表所有的右边高左边低的情况呢?那我们就来操作一个抽象图的左单旋。
在这里插入图片描述
这棵h高度的AVL树在这里举例出三种情况如下:

当h=0时:
在这里插入图片描述
当h=1时:
在这里插入图片描述
当h=2时:
在这里插入图片描述
在这里插入图片描述
当h=3,h=4……后面都是一样的,通过以上的几个例子就可以确定左单旋的方法和平衡因子以及旋转后就不用再沿祖先的路径更新的方法都是正确的。

左单旋代码实现

		//画图理解
		void RotateL(Node* parent)
		{
			Node* cur = parent->_right;
			Node* curleft = cur->_left;

			Node* parentParent = parent->_parent;
			parent->_right = curleft;
			cur->_left = parent;

			if (curleft)
			{
				curleft->_parent = parent;
			}
			parent->_parent = cur;

			if (parentParent == nullptr)
			{
				_root = cur;
				cur->_parent = nullptr;//这里记得更新,曾经忘记更新这里
			}
			else
			{
				if (parent == parentParent->_left)
				{
					parentParent->_left = cur;
				}
				else
				{
					parentParent->_right = cur;
				}
				cur->_parent = parentParent;
			}

			cur->_bf = parent->_bf = 0;
		}

3.2 右单旋

在这里插入图片描述
对于右单旋,h=0,h=1,h=2的树的旋转和左子树的h=0,h=1以及h=2…的情况是完全类似的,参考左单旋就好了。

右单旋代码实现


		void RotateR(Node* parent)
		{
			Node* cur = parent->_left;
			Node* curright = cur->_right;
			Node* parentParent = parent->_parent;

			parent->_left = curright;
			cur->_right = parent;
			if (curright != nullptr)
			{
				curright->_parent = parent;
			}
			parent->_parent = cur;

			if (parentParent == nullptr)
			{
				_root = cur;
				cur->_parent = nullptr;//这里记得更新,曾经忘记更新这里
			}
			else
			{
				if (parent == parentParent->_left)
				{
					parentParent->_left = cur;
				}
				else if (parent == parentParent->_right)
				{
					parentParent->_right = cur;
				}
				cur->_parent = parentParent;
			}

			cur->_bf = parent->_bf = 0;
		}

3.3 左单旋或者右单旋解决不了的问题

在这里插入图片描述
在这里插入图片描述

3.4 左右双旋

在这里插入图片描述
双旋分为左右双旋和右左双旋。

左右双旋:

当h=0时:
在这里插入图片描述
当h=1时:
在这里插入图片描述
当h=2时:

情况一:
在这里插入图片描述
情况二:
在这里插入图片描述

情况三:
在这里插入图片描述

情况四:
在这里插入图片描述
通过上面左右双旋的例子,直接观察左右双旋后的结果不难发现,双旋的本质是其实是把80的左子树给50的右指针,把80的右子树给90的左指针,最后80成为了这棵树的根,无一例外。这个已然是一个规律了。

那么这个规律又给了我们什么启示呢?

情况一:
在这里插入图片描述

情况二:
在这里插入图片描述
情况三:

在这里插入图片描述
左右双旋的总结:

在这里插入图片描述

左右双旋代码实现


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

			RotateL(cur);
			RotateR(parent);

			//画图观察,发现根据bf的值更新这三个节点的平衡因子
			if (bf == 0)
			{
				parent->_bf = 0;
				cur->_bf = 0;
				curright->_bf = 0;
			}
			else if (bf == -1)
			{
				parent->_bf = 1;
				cur->_bf = 0;
				curright->_bf = 0;
			}
			else if (bf == 1)
			{
				parent->_bf = 0;
				cur->_bf = -1;
				curright = 0;
			}
			else
			{
				assert(false);
			}
		}

以上就是左右双旋需要注意的点,然后右左双旋和左右双旋的情况是完全类似的。

3.5 右左双旋

右左双旋:

情况一:

在这里插入图片描述

情况二:
在这里插入图片描述

情况三:
在这里插入图片描述

右左双旋的总结:
在这里插入图片描述

右左双旋代码实现

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

			RotateR(cur);
			RotateL(parent);

			//画图观察,发现根据bf的值更新这三个节点的平衡因子
			if (bf == 0)
			{
				parent->_bf = 0;
				cur->_bf = 0;
				curleft->_bf = 0;
			}
			else if (bf == 1)
			{
				parent->_bf = -1;
				cur->_bf = 0;
				curleft->_bf = 0;
			}
			else if (bf == -1)
			{
				parent->_bf = 0;
				cur->_bf = 1;
				curleft = 0;
			}
			else
			{
				assert(false);
			}
		}

四、代码汇总

#pragma once

#include <iostream>
using namespace std;
#include <assert.h>
#include <vector>

namespace kb
{
	//KV模型
	template <class K, class V>
	struct AVLTreeNode
	{
	public:
		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:

		//插入
		bool Insert(const pair<K, V>& kv)
		{
			//第一次插入时是空树,直接new一个节点即可
			if (_root == nullptr)
			{
				_root = new Node(kv);
				return true;
			}

			//走到这里说明这棵树不是空树,需要找到新结点插入的位置
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				//新插入的元素的key值比cur的key值小,那么应该往左边找新插入元素的插入位置
				if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				//新插入的元素的key值比cur的key值大,那么应该往右边找新插入元素的插入位置
				else if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					//如果要插入的元素已经存在,那么应该就返回false表示插入失败,因为这棵树不允许有重复元素
					return false;
				}
			}

			//走到这里证明已经找到了新插入元素的插入位置了
			cur = new Node(kv);
			//新结点的key比父节点的key小就插入父节点的左边,比父节点的key大就插入在父节点的右边
			if (cur->_kv.first < parent->_kv.first)
			{
				parent->_left = cur;
				cur->_parent = parent;
			}
			else
			{
				parent->_right = cur;
				cur->_parent = parent;
			}

			//走到这里说明已经插入新结点了,这时需要更新并检查从该父节点到祖先的所有节点对应的子树是否是平衡的,
			// 如果不平衡需要旋转树的节点使其平衡,如果插入后使得沿新结点的祖先的路径中的某一个节点的平衡因子
			//的绝对值变成了2,则需要旋转调整树的平衡
			while (parent)
			{
				//更新沿祖先路径的节点的平衡因子
				if (cur == parent->_left)
				{
					parent->_bf--;
				}
				else if (cur == parent->_right)
				{
					parent->_bf++;
				}

				if (parent->_bf == 0)
				{
					//如果插入新结点后cur的父节点的平衡因子变成了0,说明新节点的插入是往父节点的
					//低的那边的子树中插入了节点,平衡了以父节点为根节点的这棵子树,并且新结点的
					//插入并没有影响到以父节点为根的这颗子树的高度,所以绝对不会影响到父节点往上的
					//节点的平衡因子,所以往上的树不可能会出现不平衡的情况,所以无需再检查父节点到
					//祖先的节点了,直接break即可
					break;
				}
				else if (abs(parent->_bf) == 1)
				{
					//如果插入新结点后cur的父节点的平衡因子的绝对值变成了1,说明新结点的插入改变了以
					//该父节点为根节点的这颗子树的高度,同时也会改变从该父节点往上的祖先的平衡因子,
					//所以需要往上再检查祖先节点的平衡因子是否符合平衡树的规定,如果出现大于2就要旋转
					cur = parent;
					parent = parent->_parent;

				}
				else
				{
					//走到这里说明这棵树已经出事了,左右子树的高度差大于1了,所以需要通过旋转来调整这棵树的平衡

					//如果cur的平衡因子是1并且parent的平衡因子是2,那么说明这棵树是右边高,左边低的(画图理解)
					//这时需要向左旋转,这里称为左单旋
					if (cur->_bf == 1 && parent->_bf == 2)
					{
						//左单旋
						RotateL(parent);
					}
					else if (cur->_bf == -1 && parent->_bf == -2)
					{
						//右单旋
						RotateR(parent);
					}
					else if (cur->_bf == -1 && parent->_bf == 2)
					{
						//右左双旋
						RotateRL(parent);
					}
					else if (cur->_bf == 1 && parent->_bf == -2)
					{
						//左右双旋
						RotateLR(parent);
					}
					else
					{
						assert(false);
					}

					//旋转后会把高的那颗子树的高度降下来,也就是说插入了一个新节点后这颗子树的高度
					//增加了1,但是旋转后又把这颗子树的高度减了1,等于是插入了节点没有影响这颗旋转
					//的子树的高度,也就是说也不会影响到这棵子树往祖先的路径的节点的平衡因子,所以
					//往上的节点就无需再检查了,说明旋转之后就可以break了
					break;
				}
			}

			return true;
		}

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

		void Inorder()
		{
			_Inorder(_root);
		}

	private:

		size_t 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 _IsBalance(Node* root)
		{
			if (root == nullptr)
			{
				return true;
			}

			//计算左右子树的高度
			int leftHeight = Height(root->_left);
			int rightHeight = Height(root->_right);

			//判断root的平衡因子是否等于右子树的高度减去左子树的高度,如果不等,证明这棵树已经出问题了
			if (rightHeight - leftHeight != root->_bf)
			{
				cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
				return false;
			}

			//判断当前树的右子树高度减左子树高度的绝对值是否小于2,再判断左右子树是否平衡
			return abs(rightHeight - leftHeight) < 2
				&& _IsBalance(root->_left)
				&& _IsBalance(root->_right);
		}

		void _Inorder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			_Inorder(root->_left);
			cout << root->_kv.second << " ";
			_Inorder(root->_right);
		}

		//画图理解
		void RotateL(Node* parent)
		{
			Node* cur = parent->_right;
			Node* curleft = cur->_left;

			Node* parentParent = parent->_parent;
			parent->_right = curleft;
			cur->_left = parent;

			if (curleft)
			{
				curleft->_parent = parent;
			}
			parent->_parent = cur;

			if (parentParent == nullptr)
			{
				_root = cur;
				cur->_parent = nullptr;//这里记得更新,曾经忘记更新这里
			}
			else
			{
				if (parent == parentParent->_left)
				{
					parentParent->_left = cur;
				}
				else
				{
					parentParent->_right = cur;
				}
				cur->_parent = parentParent;
			}

			cur->_bf = parent->_bf = 0;
		}

		void RotateR(Node* parent)
		{
			Node* cur = parent->_left;
			Node* curright = cur->_right;
			Node* parentParent = parent->_parent;

			parent->_left = curright;
			cur->_right = parent;
			if (curright != nullptr)
			{
				curright->_parent = parent;
			}
			parent->_parent = cur;

			if (parentParent == nullptr)
			{
				_root = cur;
				cur->_parent = nullptr;//这里记得更新,曾经忘记更新这里
			}
			else
			{
				if (parent == parentParent->_left)
				{
					parentParent->_left = cur;
				}
				else if (parent == parentParent->_right)
				{
					parentParent->_right = cur;
				}
				cur->_parent = parentParent;
			}

			cur->_bf = parent->_bf = 0;
		}

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

			RotateR(cur);
			RotateL(parent);

			//画图观察,发现根据bf的值更新这三个节点的平衡因子
			if (bf == 0)
			{
				parent->_bf = 0;
				cur->_bf = 0;
				curleft->_bf = 0;
			}
			else if (bf == 1)
			{
				parent->_bf = -1;
				cur->_bf = 0;
				curleft->_bf = 0;
			}
			else if (bf == -1)
			{
				parent->_bf = 0;
				cur->_bf = 1;
				curleft = 0;
			}
			else
			{
				assert(false);
			}
		}

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

			RotateL(cur);
			RotateR(parent);

			//画图观察,发现根据bf的值更新这三个节点的平衡因子
			if (bf == 0)
			{
				parent->_bf = 0;
				cur->_bf = 0;
				curright->_bf = 0;
			}
			else if (bf == -1)
			{
				parent->_bf = 1;
				cur->_bf = 0;
				curright->_bf = 0;
			}
			else if (bf == 1)
			{
				parent->_bf = 0;
				cur->_bf = -1;
				curright = 0;
			}
			else
			{
				assert(false);
			}
		}

	private:
		Node* _root = nullptr;
	};

	void TestAVLTree1(void)
	{
		AVLTree<int, int> t;
		t.Insert(make_pair(1, 1));
		t.Insert(make_pair(-1, -1));
		t.Insert(make_pair(3, 3));
		t.Insert(make_pair(2, 2));
		t.Insert(make_pair(5, 5));
		t.Insert(make_pair(7, 7));
		
		t.Inorder();
		cout << endl;
	}

	void TestAVLTree2(void)
	{
		AVLTree<int, int> t;
		t.Insert(make_pair(5, 5));
		t.Insert(make_pair(2, 2));
		t.Insert(make_pair(6, 6));
		t.Insert(make_pair(4, 4));
		t.Insert(make_pair(3, 3));

		t.Inorder();
		cout << endl;
	}

	void TestAVLTree3(void)
	{
		AVLTree<int, int> t;
		t.Insert(make_pair(6, 6));
		t.Insert(make_pair(3, 3));
		t.Insert(make_pair(8, 8));
		t.Insert(make_pair(1, 1));
		t.Insert(make_pair(2, 2));

		t.Inorder();
		cout << endl;
	}

	void TestAVLTree4(void)
	{
		/*vector<int> v = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };*/
		vector<int> v = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
		AVLTree<int, int> t;
		for (const auto& e : v)
		{
			t.Insert(make_pair(e, e));
		}
		t.Inorder();
		cout << endl;
	}

	void TestAVLTree5(void)
	{
		/*vector<int> v = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };*/
		vector<int> v;
		srand((unsigned int)time(nullptr));
		AVLTree<int, int> t;
		int N = 1000000;
		for (size_t i = 0; i < N; i++)
		{
			int e = rand();
			t.Insert(make_pair(e, e));
		}
		//t.Inorder();
		int ret = t.IsBalance();
		cout << ret << endl;

		cout << endl;
	}
}

以上就是关于AVL树的重点内容啦!学习AVL树最重要的就是学习它插入元素,然后通过旋转控制平衡的过程,至于AVL树删除元素大家有兴趣的可以去学习一下,在《算法导论》这本书中有介绍,大概思路也是先删除元素,如果删除元素后某一棵子树的平衡因子不满足要求就通过旋转调整树的平衡。好了,今天就聊到这里,如果感觉到这篇文章对你有所帮助的话,点个小心心,点点关注呗,后期还会持续更新C++相关的知识哦,我们下期见啦!!!!!!!

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

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

相关文章

【Vue篇】Vue 项目下载、介绍(详细版)

如何创建一个vue项目&#xff1f;首先要有环境&#xff0c;如下&#xff1a; nodejs vue-cli如果有以上的工具就直接跳过安装教程 【Vue篇】mac上Vue 开发环境搭建、运行Vue项目&#xff08;保姆级&#xff09; 创建vue项目 选择一个位置&#xff0c;你要存放项目的路径&…

App线上网络问题优化策略

在我们App开发过程中&#xff0c;网络是必不可少的&#xff0c;几乎很难想到有哪些app是不需要网络传输的&#xff0c;所以网络问题一般都是线下难以复现&#xff0c;一旦到了用户手里就会碰到很多疑难杂症&#xff0c;所以对于网络的监控是必不可少的&#xff0c;针对用户常见…

golang flag 包的使用指北

说起 golang 的 flag 个包&#xff0c;我们第一反应的是什么呢&#xff1f;至少我曾经第一次看到 flag 包的时候&#xff0c;第一反应是想起写 C 语言的时候咱们用于定义一个表示的&#xff0c;我们一般会命名为 flag 变量 实际上 golang 的 flag 包是用于处理命令行参数的工具…

《深入浅出OCR》第六章:OCR数据集与评价指标

一、OCR技术流程 在介绍OCR数据集开始&#xff0c;我将带领大家和回顾下OCR技术流程&#xff0c;典型的OCR技术pipline如下图所示&#xff0c;其中&#xff0c;文本检测和识别是OCR技术的两个重要核心技术。 1.1 图像预处理&#xff1a; 图像预处理是OCR流程的第一步&#xf…

5147. 数量

题目&#xff1a; 样例1&#xff1a; 输入 4 输出 1 样例2&#xff1a; 输入 7 输出 2 样例3&#xff1a; 输入 77 输出 6 思路&#xff1a; 根据题意&#xff0c;如果直接 for 循环暴力&#xff0c;肯定会超时&#xff0c;但是我们换个思路想&#xff0c;只要包含 4 和 7的…

【2023年数学建模国赛】C题代码与技术文档分享

2023年数学建模国赛C题 第一问代码code1_Q1_1.mCode1_Q1_2.mCode1_Q1_3.m实验结果 技术文档问题分析假设符号说明1 第一问1.1分布检验模型的建立1.2 相关性模型的建立1.3各种类蔬菜的销量分布及相关关系 写在最后 第一问代码 code1_Q1_1.m clc clear Dxlsread(合成表1,合成表…

通过实例学习:使用Spring Cache实现实际场景的缓存策略

文章目录 前言一、Spring Cache 常用注解1.Cacheable&#xff1a;2.CachePut&#xff1a;3.CacheEvict&#xff1a;4.CacheConfig&#xff1a;5.EnableCathing: 二、使用步骤1.引入依赖2.配置3.EnableCaching的使用&#xff1a;4.Cacheable的使用&#xff1a;5.CachePut的使用&…

c语言练习46:模拟实现strncpy

模拟实现strncpy 模拟实现&#xff1a; #include<stdio.h> char* my_strncpy(char*dest,char*src,size_t num) {char* ret dest;size_t i 0;for (i 0; i < num; i) {*dest *src;dest;src;}*dest \0;return ret; } int main() {char aim[50] { 0 };char src[] …

03_kafka-eagle 监控

文章目录 安装修改 kafka-server-start.sh修改 kafka-run-class.sh问题eagle 日志报错mysql 报错 时区问题 kafka-eagle 监控 安装 download.kafka-eagle.org &#xff1a; https://github.com/smartloli/kafka-eagle-bin/archive/v3.0.1.tar.gzhttps://docs.kafka-eagle.org/…

C语言“牵手”lazada商品详情数据方法,lazada商品详情API接口,lazadaAPI申请指南

lazada是东南亚最大的自营式电商企业&#xff0c;在线销售计算机、手机及其它数码产品、家电、汽车配件、服装与鞋类、奢侈品、家居与家庭用品、化妆品与其它个人护理用品、食品与营养品、书籍与其它媒体产品、母婴用品与玩具、体育与健身器材以及虚拟商品等。 lazada平台的商…

C基础-数组

1.一维数组的创建和初始化 int main() {// int arr1[10];int n 0;scanf("%d",&n);//int count 10;int arr2[n]; //局部的变量&#xff0c;这些局部的变量或者数组是存放在栈区的&#xff0c;存放在栈区上的数组&#xff0c;如果不初始化的话&#xff0c;默认…

heap堆结构以及堆排序

堆的定义 堆&#xff08;heap&#xff09;是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质&#xff1a; 堆中某个结点的值总是不大于或不小于其父结点的值&#xff1b; 堆总是一棵完全二叉树。 将根结点最大的堆叫做…

YOLO目标检测——复杂场景人员行人数据集+已标注voc格式标签下载分享

实际项目应用&#xff1a;安防监控、人群管理、自动驾驶、城市规划、人机交互等等数据集说明&#xff1a;YOLO目标检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;图片格式为jpg&#xff0c;分为训练集和验证集。标注说明&#xff1a;使用…

kubernetes(K8S)笔记

文章目录 大佬博客简介K8SDocker VS DockerDockerK8S简介K8S配合docker相比较单纯使用docker 大佬博客 Kubernetes&#xff08;通常缩写为K8s&#xff09;是一个用于自动化容器化应用程序部署、管理和扩展的开源容器编排平台。它的构造非常复杂&#xff0c;由多个核心组件和附加…

【Java基础篇 | 类和对象】--- 聊聊什么是内部类

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【JavaSE_primary】 本专栏旨在分享学习Java的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 前言 当一个事物的内部&…

分享日常电脑遇到msvcr110.dll丢失的解决方法

最近&#xff0c;我在尝试运行一款新的软件时&#xff0c;突然遇到了一个错误提示&#xff0c;提示说缺少msvcr110.dll文件&#xff0c;导致软件无法启动。在使用电脑过程中&#xff0c;我们常常会遇到一些系统文件丢失的问题。其中&#xff0c;msvcr110.dll是Windows操作系统中…

10分钟从实现和使用场景聊聊并发包下的阻塞队列

上篇文章12分钟从Executor自顶向下彻底搞懂线程池中我们聊到线程池&#xff0c;而线程池中包含阻塞队列 这篇文章我们主要聊聊并发包下的阻塞队列 阻塞队列 什么是队列&#xff1f; 队列的实现可以是数组、也可以是链表&#xff0c;可以实现先进先出的顺序队列&#xff0c;…

【矩阵分解】PCA - 主成分分析中的数学原理

前言 本文主要对PCA主成分分析中的数学原理进行介绍&#xff0c;将不涉及或很少涉及代码实现或应用&#xff0c;阅读前请确保已了解基本的机器学习相关知识。 文章概述 PCA主成分分析属于矩阵分解算法中的入门算法&#xff0c;通过分解特征矩阵来实现降维。 本文主要内容&a…

【PowerQuery】Excel 一分钟以内刷新PowerQuery数据

当需要进行刷新的周期如果小于一分钟,采用数据自动刷新就无法实现自动刷新的目标。那就没有办法了吗?当然不是,这里就是使用VBA来实现自动刷新。这里实现VBA刷新的第一步就是将当前的Excel 保存为带有宏的Excel 文件,如果不带宏则无法运行带有宏代码的Excel文件,保存过程如…

JAVA中的String类中的一些常用方法

目录 字符串比较方法&#xff1a; boolean equals(Object anObject)&#xff1a; int compareTo(String s)&#xff1a; int compareToIgnoreCase(String str) 字符串查找方法&#xff1a; char charAt(int index)&#xff1a; int indexOf(int ch)&#xff1a; int inde…