AVL树的模拟实现

news2024/11/8 19:27:26

我们上期提到了二叉搜索树,只是简单的讲了一下原理,那么今天我们就讲一下AVL树。

目录

  • AVL树的概念
  • AVL树的实现
    • AVL树的架构
    • insert插入
      • 引用pair对象
      • 引进parent指针
      • 仅插入数据
      • 调节平衡因子
        • 情况1:插入在父亲的右边,父亲的平衡因子++后为0
        • 情况2:插入在父亲的左边,父亲的平衡因子--后为0
        • 情况3:插入左或者右,恰巧父亲不是0,是-1/1
        • 情况4:当父亲的平衡因子==-2/2,不需要在更新了,证明不平衡了,需要旋转。
        • 左边高,右旋
        • 右边高,左旋
        • 双旋转
          • 右左旋转:先右旋然后左旋。
          • 左右双旋:先左旋,在右旋。
    • 中序遍历
    • 判断是否平衡
    • AVL树整体代码

AVL树的概念

其实大家应该很奇怪,难道二叉搜索树不能存储数据吗?为什么要有AVL树呢?二叉搜索树有可能会有畸形的情况。像下图,数据比较分散的话,这棵树很正常,如果我们插入的数相对有序就会变成右边那样畸形的树。
在这里插入图片描述
这个时候就需要人工干预,这里的AVL数就可以更好的控制这个情况,它有自己的平衡规则:左右子树高度之差(平衡因子)绝对不超过1(-1,0,1)。如果不满足这个规则,那么我们就旋转。

AVL树的实现

因为AVL树也是二叉搜索树的一种,所以他也要满足二叉搜索树的条件,然后在满足他自己的平衡规则

AVL树的架构

其实数的节点架构和整体架构并没有变,只是多了一个bf(平衡因子),用来调节平衡。

struct AVLTreeNode
{
	AVLTreeNode(const pair<K, V> kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
	AVLTreeNode* _left;
	AVLTreeNode* _right;
	AVLTreeNode* _parent;
	pair<K, V> _kv;
	int _bf;
};
template<class K,class V>
class AVLTree
{
	public:
		typedef AVLTreeNode<K,V> Node;
	private:
	Node* _root=nullptr;
}

insert插入

引用pair对象

这里与原来不同,这里我们引入了一个pair对象,那么pair是什么?我们用pair来实现KV结构,在库中的map也是用pair也完成KV结构的,所以这里我们就用这pair。pair对象的first是K值,second是V值。
在这里插入图片描述

引进parent指针

这里引用父指针是因为我们将来要旋转,要不断向上调节平衡因子,因为当我们插入某个值有可能引起平衡因子失衡。

仅插入数据

其实插入部分和我们之前写的一样,只不过要注意的是,我们的值存的是pair对象,要像拿到K值需要 _kv.first拿到K值。一定要遵循二叉搜索树的规律,左子树比根小,右子树比根大

	if (_root == nullptr)
	{
		//插入第一个值
		Node* newNode = new Node(kv);
		_root = newNode;
		return true;
	}
	Node* newNode = new Node(kv);
	Node* cur = _root;
	Node* parent = cur;
	while (cur)
	{
		if (cur->_kv.first < newNode->_kv.first)
		{
			//大于在右边
			parent = cur;
			cur = cur->_right;

		}
		else if (cur->_kv.first > newNode->_kv.first)
		{
			//小于在左边
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			//等于,二叉搜索树不允许冗余,所以直接返回false。
			return false;
		}
	}
	if (parent->_kv.first > newNode->_kv.first)
	{
		//在左边
		parent->_left = newNode;
	}
	else
	{
		//在右边
		parent->_right = newNode;
	}
	newNode->_parent = parent;

调节平衡因子

情况1:插入在父亲的右边,父亲的平衡因子++后为0

红色是我插入的数,插入后,它的parent:12从-1加加后变成了0,我们还需要向上更新吗?答案是不需要向上更新,为什么?0表示左右子树的高度差为0,也就说高度没有变,所以我不需要再向上更新
解释:平衡因子原来是1/-1都表示这个树缺了一个节点,当我们插入之后正好填上了这个节点,但是高度并不变。看图!我把15插入,右子树这个高度没有变。
在这里插入图片描述

情况2:插入在父亲的左边,父亲的平衡因子–后为0

当我插入在左边,那我们就需要给父亲的因子bf–。
在这里插入图片描述

情况3:插入左或者右,恰巧父亲不是0,是-1/1

父亲的平衡因子==1/-1,父亲所在子树高度变了。高度变了继续向上更新。
在这里插入图片描述

情况4:当父亲的平衡因子==-2/2,不需要在更新了,证明不平衡了,需要旋转。

这个时候就不满足AVL树的规则了,就需要旋转。
在这里插入图片描述

左边高,右旋

这个树就是左边高的情况,你会发现parent为-2,cur为-1,这个情况就是左边比右边要高,那么我们需要右旋。
在这里插入图片描述
右旋的口诀:把cur的右边给parent的左边,cur的右边链接parent,在让parent的parent链接cur。会发现我们把原来的parent变成了cur的右边。并且现在是平衡的。

其实为什么要把cur的右边给parent呢?是因为cur的右边时当前树最大的值,cur给了parent链接到左边,依然不会破坏二叉搜索树的规则。
在这里插入图片描述

void RotateR(Node* parent )
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;
	subL->_right = parent;
	Node* ppNode = parent->_parent;
	parent->_parent = subL;
	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}
		subL->_parent = ppNode;
	}
	parent->_bf = subL->_bf = 0;
}
右边高,左旋

当我们插入红色节点的时候就会导致右边过高,那么我们就需要左旋。
在这里插入图片描述

左旋的口诀:让cur的左边给parent的右边链接,然后让父亲变成cur的左边
解释:为什么要把cur左边的值给parent的右边?cur当前位置是parent的右边,cur的左边也是比parent大的值,给parent的右边依然不影响二叉搜索树的规则。
在这里插入图片描述

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

	parent->_right = subRL;
	if(subRL)
	subRL->_parent = parent;

	subR->_left = parent;

	Node* pphead = parent->_parent;

	parent->_parent = subR;
	if (parent == _root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (pphead->_parent == parent)
		{
			pphead->_right = subR;
		}
		else
		{
			pphead->_left = subR;
		}
		subR->_parent = pphead;
	}
	subR->_bf = parent->_bf = 0;
}
双旋转

还有一种情况,它并不是单纯的一边高,用单旋并不能解决问题。当我们仅仅只是单旋会发现他有变成了形态不同,但是问题一样的情况,这个时候就需要双旋
在这里插入图片描述

右左旋转:先右旋然后左旋。

既然我们单旋解决不了问题,我们可以把他变成一边高,然后进行单旋。
如这个图,我们可以对subR进行右旋后,在对parent左旋就可以了。
在这里插入图片描述
右左双旋后:
在这里插入图片描述
但是有个问题,平衡因子我们应该如何更新?你可能会说这种情况不是正好满足左旋/右旋后平衡因子变成0吗?但是如果其他位置都有节点呢?接下来我们抽象图,给大家演示一下。
当我们插入节点的位置不同,你会发现每一个平衡因子也不一样。如图:我插在了右边。这个时候我通过右左双旋就会得到下图。如最右图的红色平衡因子,应该变成这个情况。所以说插入的位置不同,平衡因子也会不同
在这里插入图片描述
如图:假如我插在了左边。会发现平衡因子又不一样了。所以我们并不能用一个例子来概括所有。
在这里插入图片描述
那么我们应该怎么做呢?其实从我们画图,你会发现我们改变的只不过是parent 、subR、sunRL这三个节点的因子,其他的并没有改变。并且,跟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 == 0)
			{
				//新插入的
				parent->_bf =0;
				subR->_bf = 0;
				subRL->_bf = 0;
			}
			else if (bf == 1)
			{
				//右边插入的
				subR->_bf = 0;
				subRL->_bf = 0;
				parent->_bf = -1;
			}
			else if(bf==-1)
			{
				//在左边插入的
				parent->_bf = 0;
				subR->_bf = 1;
				subRL->_bf = 0;
			}
			else
			{
				assert(false);
			}

		}
左右双旋:先左旋,在右旋。

图下是:左右双旋。
在这里插入图片描述

当然了,我们依然需要分析平衡因子,所以我们依然要分析插入在哪个位置。
如图:当插入在右边。
在这里插入图片描述

如图:当我们插在左边。
在这里插入图片描述

		void RotateLR(Node* parent)
		{
			//左右双旋
			//平衡因子需要单独调节
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			int bf = subLR->_bf;
			//先左旋在右旋
			RotateL(parent->_left);
			RotateR(parent);
			//重新计算调节因子
			if (bf == 0)
			{
				//当且节点就是新插的
				subL->_bf = 0;
				parent->_bf = 0;
				subLR->_bf = 0;
			}
			else if(bf==1)
			{
				 //在当前节点的右边插入的
				parent->_bf = 0;
				subL->_bf = -1;
				subLR->_bf = 0;
			}
			else if(bf == -1)
			{
				subL->_bf = 0;
				parent->_bf = 1;
				subLR->_bf = 0;
			}
			else
			{
				assert(false);

			}
		}

中序遍历

因为二叉搜索树我们都是中序遍历,因为中序遍历更接近有序。

void _Inorder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}
	_Inorder(root->_left);
	cout << root->_kv.first << ":" << root->_kv.second<<endl;
	_Inorder(root->_right);
}

判断是否平衡

什么时候不平衡?是不是因子大于等于2的时候,所以我们需要算左右树的高度相减,如果超过2,那就是不平衡。

		bool _isBalance(Node* root)
		{
			if (root == nullptr)
			{
				return true;
			}
			int left = _Height(root->_left);
			int right = _Height(root->_right);
			if (abs(left - right) >= 2) return false;//因子大于等于2的时候不平衡
			return _isBalance(root->_left) && _isBalance(root->_right);
		}
		int _Height(Node* root)
		{
			if (root == nullptr)
				return 0;
			int left = _Height(root->_left);
			int right = _Height(root->_right);
			return max(left, right) + 1;
		}

AVL树整体代码

以下是整个AVL树所有的代码?

问题:为什么没有像库一样写删除呢?答:我们模拟实现其实是为了了解底层,并不是要超过底层,因为现有的库已经很好了,我们没必要写一个。二叉搜索树很少用删除接口。所以这里没有实现

#pragma once
#include<iostream>
#include<vector>
#include<string>
#include<assert.h>

using namespace std;
namespace KV
{
	template<class K,class V>
	struct AVLTreeNode
	{
		AVLTreeNode(const pair<K, V> kv)
			:_left(nullptr)
			,_right(nullptr)
			,_parent(nullptr)
			,_kv(kv)
			,_bf(0)
		{}
		AVLTreeNode* _left;
		AVLTreeNode* _right;
		AVLTreeNode* _parent;
		pair<K, V> _kv;
		int _bf;
	};
	template<class K,class V>
	class AVLTree
	{
	public:
		typedef AVLTreeNode<K,V> Node;
		bool insert(const pair<K, V> kv)
		{
			if (_root == nullptr)
			{
				//插入第一个值
				Node* newNode = new Node(kv);
				_root = newNode;
				return true;
			}
			Node* newNode = new Node(kv);
			Node* cur = _root;
			Node* parent = cur;
			while (cur)
			{
				if (cur->_kv.first < newNode->_kv.first)
				{
					//大于在右边
					parent = cur;
					cur = cur->_right;

				}
				else if (cur->_kv.first > newNode->_kv.first)
				{
					//小于在左边
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					//等于,二叉搜索树不允许冗余,所以直接返回false。
					return false;
				}
			}
			if (parent->_kv.first > newNode->_kv.first)
			{
				//在左边
				parent->_left = newNode;
			}
			else
			{
				//在右边
				parent->_right = newNode;
			}
			newNode->_parent = parent;
			cur = newNode;
			while (parent)
			{
				if (parent->_left == cur)
				{
					cur->_parent->_bf--;
				}
				else if (parent->_right == cur)
				{
					cur->_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)
					{
						RotateR(parent);
					}
					//右边高,左旋转
					else if (parent->_bf == 2 && cur->_bf == 1)
					{
						RotateL(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == 1)
					{
						//左右双旋
						RotateLR(parent);
					}
					else if (parent->_bf == 2 && cur->_bf == -1)
					{
						//右左双旋
						RotateRL(parent);
					}
					else
					{
						assert(false);
					}
				}
				else
				{
					//按道理说不可能有这种情况,但是保不准会有bug
					assert(false);
					break;
				}
			}
			return true;
		}

		void Inorder()
		{
			_Inorder(_root);
		}
		int Height()
		{
			return _Height(_root);
		}
		bool isBalance()
		{
			return _isBalance(_root);
		}
	private:
		int _Height(Node* root)
		{
			if (root == nullptr)
				return 0;
			int left = _Height(root->_left);
			int right = _Height(root->_right);
			return max(left, right) + 1;
		}
		bool _isBalance(Node* root)
		{
			if (root == nullptr)
			{
				return true;
			}
			int left = _Height(root->_left);
			int right = _Height(root->_right);
			if (abs(left - right) >= 2) return false;
			return _isBalance(root->_left) && _isBalance(root->_right);
		}
		void RotateRL(Node* parent)
		{
			//右左双旋
			//平衡因子需要自己调节
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			int bf = subRL->_bf;
			RotateR(subR);
			RotateL(parent);

			if (bf == 0)
			{
				//新插入的
				parent->_bf =0;
				subR->_bf = 0;
				subRL->_bf = 0;
			}
			else if (bf == 1)
			{
				//右边插入的
				subR->_bf = 0;
				subRL->_bf = 0;
				parent->_bf = -1;
			}
			else if(bf==-1)
			{
				//在左边插入的
				parent->_bf = 0;
				subR->_bf = 1;
				subRL->_bf = 0;
			}
			else
			{
				assert(false);
			}

		}
		void RotateLR(Node* parent)
		{
			//左右双旋
			//平衡因子需要单独调节
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			int bf = subLR->_bf;
			//先左旋在右旋
			RotateL(parent->_left);
			RotateR(parent);
			//重新计算调节因子
			if (bf == 0)
			{
				//当且节点就是新插的
				subL->_bf = 0;
				parent->_bf = 0;
				subLR->_bf = 0;
			}
			else if(bf==1)
			{
				 //在当前节点的右边插入的
				parent->_bf = 0;
				subL->_bf = -1;
				subLR->_bf = 0;
			}
			else if(bf == -1)
			{
				subL->_bf = 0;
				parent->_bf = 1;
				subLR->_bf = 0;
			}
			else
			{
				assert(false);

			}
		}
		void RotateR(Node* parent )
		{
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			parent->_left = subLR;
			if (subLR)
				subLR->_parent = parent;
			subL->_right = parent;
			Node* ppNode = parent->_parent;
			parent->_parent = subL;
			if (parent == _root)
			{
				_root = subL;
				subL->_parent = nullptr;
			}
			else
			{
				if (ppNode->_left == parent)
				{
					ppNode->_left = subL;
				}
				else
				{
					ppNode->_right = subL;
				}
				subL->_parent = ppNode;
			}
			parent->_bf = subL->_bf = 0;
		}
		void RotateL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			parent->_right = subRL;
			if(subRL)
			subRL->_parent = parent;

			subR->_left = parent;

			Node* pphead = parent->_parent;

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

		void _Inorder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_Inorder(root->_left);
			cout << root->_kv.first << ":" << root->_kv.second<<
				" 因子:" <<root->_bf<< endl;
			_Inorder(root->_right);
		}
		Node* _root=nullptr;
	};
}

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

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

相关文章

一种最大重叠离散小波包特征提取和支持向量机的ECG心电信号分类方法(MATLAB 2018)

目前小波分析算法常采用Mallat快速算法。该算法由与滤波器卷积、隔点采样和隔点插零等三个环节组成。由于实际使用的滤波器并不具有理想频域特性&#xff0c;使得在标准二进小波算法中存在着频率混叠和小波系数失真等缺点&#xff0c;在标准二进小波包算法中还存在频带错乱现象…

docker制作高版本jdk17镜像踩坑

1、创建目录并下载jdk上传到服务器中 从jdk官网下载jdk17镜像&#xff0c;提示&#xff1a;下载到本地用xftp上传到服务器&#xff08;速度会快点&#xff09; jdk官网&#xff1a;https://www.oracle.com/java/technologies/downloads/#graalvmjava21 创建目录&#xff0c;将…

MATLAB分类与判别模型算法:基于Fisher算法的分类程序【含Matlab源码 MX_002期】

算法思路介绍&#xff1a; 费舍尔线性判别分析&#xff08;Fishers Linear Discriminant Analysis&#xff0c;简称 LDA&#xff09;&#xff0c;用于将两个类别的数据点进行二分类。以下是代码的整体思路&#xff1a; 生成数据&#xff1a; 使用 randn 函数生成随机数&#x…

P10-P11【重载,模板,泛化和特化】【分配器的实现】

三类模板&#xff08;类模板&#xff09;&#xff08;函数模板&#xff09;&#xff08;成员函数模板&#xff09; 特化 偏特化&#xff1a;模板参数个数/模板范围 定义的分配器 以上分配器的性能和内存管理有很大不足&#xff08;在分配内存时&#xff0c;会产生很大的内存开…

UE5中绘制饼状图

饼状图 使用UE绘制前提完整的创建过程123456678 附录代码.h代码.c代码 使用UE绘制前提 EPIC Game使用的版本是Unreal Engine 5.0.3。 没有使用其他额外的插件&#xff0c;使用的是C和Ui共同绘制。 C编译器使用的是VS2019。 完整的创建过程 1 首先在UE中随意一种项目的白色。…

博物馆三维实景vr展示

VR技术应用到地产行业的优势不言而喻&#xff0c;随着购房政策的进一步放宽&#xff0c;购房刚需者借助VR商铺样板间展示系统看房&#xff0c;远比之前跑楼盘更便捷高效。那么VR商铺全景展示具体有哪些好处呢? VR技术与商铺的结合&#xff0c;为客户带来了前所未有的购房体验。…

python基础知识总结(第一节)

一、python简介&#xff1a; Python是一种解释型&#xff0c;面向对象的高级语言。 Pyhton的语法和动态类型&#xff0c;以及解释性语言的本质&#xff0c;使它一跃成为多数平台上写脚本和快速开发应用的编程语言。 python语言百度百科介绍 二、Python基础语法&#xff1a;…

国产操作系统上apt命令详解 _ 统信 _ 麒麟 _ 中科方德

原文链接&#xff1a;国产操作系统上apt命令详解 | 统信 | 麒麟 | 中科方德 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇在国产操作系统上使用apt命令的详解文章。apt&#xff08;Advanced Package Tool&#xff09;是Debian及其衍生发行版&#xff08;如统信UOS…

宁盾身份域管与天翼云电脑完成兼容互认证

近日&#xff0c;宁盾身份域管与天翼云电脑完成兼容互认证&#xff01;这是继中兴、深信服、升腾威讯云桌面/云电脑后&#xff0c;宁盾对接的第4个国产云桌面品牌。企业在引入国产云桌面时&#xff0c;同时会考虑微软AD目录的替代方案。宁盾国产化身份域管对接天翼云电脑从终端…

521源码-免费手游下载-【烽火中原H5】深度体验:横版网页国战手游及WIN学习手工端

【烽火中原H5】深度体验&#xff1a;横版网页国战手游及WIN学习手工端全面解析,烽火中原H5】横板网页国战手游WIN学习手工端语音视频教程营运后台CDK授权后台, 喜欢国战手游的玩家们&#xff0c;你们期待已久的【烽火中原H5】现已上线&#xff01;这款游戏以横版网页的形式呈现…

[LitCTF 2023]yafu (中级) (素数分解)

题目&#xff1a; from Crypto.Util.number import * from secret import flagm bytes_to_long(flag) n 1 for i in range(15):n *getPrime(32) e 65537 c pow(m,e,n) print(fn {n}) print(fc {c})n 152412082177688498871800101395902107678314310182046454156816957…

电商推荐系统+电影推荐系统【虚拟机镜像分享】

电商推荐系统电影推荐系统【虚拟机镜像分享】 所有组件部署好的镜像下载&#xff08;在下面&#xff09;&#xff0c;仅供参考学习。&#xff08;百度网盘&#xff0c;阿里云盘…&#xff09; 博主通过学习尚硅谷电商推荐电影推荐项目&#xff0c;将部署好的虚拟机打包成ovf文…

横截面分位数回归

一、分位数回归简介 分位数回归&#xff08;英语&#xff1a;Quantile regression&#xff09;是回归分析的方法之一。最早由Roger Koenker和Gilbert Bassett于1978年提出。一般地&#xff0c;传统的回归分析研究自变量与因变量的条件期望之间的关系&#xff0c;相应得到的回归…

[vue3后台管理二]首页和登录测试

[vue3后台管理二]首页和登录测试 1 修改main.js import ./assets/main.cssimport { createApp } from vue import App from ./App.vue import router from ./router createApp(App).use(router).mount(#app)2 路由创建 import {createRouter, createWebHistory} from vue-ro…

双指针+前缀和,蓝桥云课 近似gcd

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 0近似gcd - 蓝桥云课 (lanqiao.cn) 二、解题报告 1、思路分析 考虑近似gcd的子数组的特点&#xff1a;不为g的倍数的数字个数小于等于1 我们用前缀和pre[]来存储不为g的倍数的数字个数 那么枚举左端点l&a…

计算机网络学习笔记——运输层(b站)

目录 一、 运输层概述 二、运输层端口号、复用与分用的概念 三、UDP和TCP的对比 四、TCP的流量控制 五、TCP的拥塞控制 六、TCP超时重传时间的选择 七、TCP可靠传输的实现 八、TCP报文段的首部格式 一、 运输层概述 物理层、数据链路层、网络层实现了主机到主机的通信…

Qt 基于FFmpeg的视频转换器 - 转GIF动图

Qt 基于FFmpeg的视频转换器 - 转GIF动图 引言一、设计思路二、核心源码三、参考链接 引言 gif格式的动图可以通过连续播放一系列图像或视频片段来展示动态效果&#xff0c;使信息更加生动形象&#xff0c;可以很方便的嵌入到网页或者ppt中。上图展示了视频的前几帧转为gif动图的…

大摩“凑热闹”:当前氧化铝紧平衡,任何供给冲击都将导致价格急剧波动

大摩认为&#xff0c;全球有590万吨氧化铝供应受阻&#xff0c;相等于扣除中国后全球供应约一成&#xff0c;而氧化铝需求可能有所上升&#xff0c;同时氧化铝库存缓冲有限&#xff0c;因此任何供给冲击都将导致价格急剧波动。 文章内容 今年以来氧化铝期货价格一路上行&#…

STM32-13-MPU

STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32-11-电容触摸按键 STM32-12-OLED模块 文章目录 STM32-12-MPU1. 内存保护单元MPU1. M…

JVM之性能优化

1.JVM优化什么 由博客JVM之垃圾回收-CSDN博客我们已经了解到了数据存储是在方法区和堆区&#xff0c;而堆区的使用更为频繁。堆区有什么呢?老年代、新生代、GC。因此JVM性能优化&#xff0c;优化什么&#xff1f; 我们猜想一下&#xff0c;新生代的大小设置&#xff1b;老年代…