数据结构:AVL树讲解(C++)

AVL树

    • 1.AVL树的概念
    • 2.平衡因子
    • 3.节点的定义
    • 4.插入操作
    • 5.旋转操作(重点)
      • 5.1左单旋
      • 5.2右单旋
      • 5.3左右双旋
      • 5.4右左双旋
    • 6.一些简单的测试接口
    • 7.完整代码

1.AVL树的概念

普通二叉搜索树:二叉搜索树

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

AVL树也是二叉搜索树,但有以下特点:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
  • 如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)。



2.平衡因子

AVL树的实现有很多种,本文引入平衡因子来维持高度稳定。
本文平衡因子的定义:右子树高度 - 左子树的高度

依据每个节点的平衡因子,我们可以判断树的情况:

  • 平衡因子在(-1, 0, 1),当前节点所在子树是稳定的。
  • 平衡因子为2或-2,当前节点所在子树是不稳定的。

插入节点后平衡因子的更新:

  • 插入节点在右子树,平衡因子加一
  • 插入节点在左子树,平衡因子减一

插入节点后平衡因子的不同情况(重点):

  • 当前节点所在子树平衡因子为0,子树高度不变,不需要更新
    (原来一边高一边低,新插入在低一方,变成完全平衡)。
  • 当前节点所在子树平衡因子为1或-1,子树高度变化,需要向上更新。
    (原来完全平衡,现在一边高,子树整体高度加1,会影响到祖先的平衡,故需要向上更新看祖先所在子树是否平衡)
  • 当前节点所在子树平衡因子为2或-2,子树高度变化且不平衡,无需向上更新,对当前子树进行旋转操作。
    (当前子树已不平衡,向上更新没有意义,旋转操作是AVL树的核心,可以降低当前子树的高度且不影响上面的树结构)

在这里插入图片描述



3.节点的定义

节点除了需要增加一个平衡因子,还需要增加一个父亲指针,方便我们进行平衡因子的向上更新和旋转操作。

template<class K, class V>
struct ALVTreeNode
{
	ALVTreeNode<K, V>* _left;
	ALVTreeNode<K, V>* _right;
	ALVTreeNode<K, V>* _parent;
	pair<K, V> _kv;  //存储键值对
	int _bf;  //平衡因子
	ALVTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_bf(0)
		,_kv(kv)
	{}
};



4.插入操作

PS:因为多加了一个父亲指针,所以插入时要注意更新父亲指针,平衡因子更新按前面的分析来还是比较简单的,旋转操作后面单独讲。

bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr) //一开始为空树,直接插入即可
	{
		_root = new Node(kv);
		return true;
	}

	//找插入位置加插入
	Node* cur = _root;  //记录插入位置
	Node* parent = nullptr;  //待插入位置的父亲
	while (cur)
	{
		if (kv.first > cur->_kv.first)  //待插入节点在右子树
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)  //待插入节点在左子树
		{
			parent = cur;
			cur = cur->_left;
		}
		else  //待插入节点已存在
		{
			return false;
		}
	}
	cur = new Node(kv);
	if (kv.first > parent->_kv.first)  //插入在父亲的右边
	{
		parent->_right = cur;
	}
	else  //插入在父亲的左边
	{
		parent->_left = cur;
	}
	cur->_parent = parent;  //注意更新父亲指针

	//调整平衡因子
	while (parent)  //是有可能调整到根部的
	{
		if (cur == parent->_right)  //如果新插入的在右子树
		{
			parent->_bf++;
		}
		else if (cur == parent->_left)  //如果新插入的在左子树
		{
			parent->_bf--;
		}

		if (parent->_bf == 0) //插入后高度不变
		{
			break;
		}
		else if (parent->_bf == 1 || parent->_bf == -1)  //插入后高度变化,但当前子树依然平衡,需要向上更新
		{
			parent = parent->_parent;
			cur = cur->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)  //插入后高度变化,并且当前子树已经不平衡,旋转
		{
			//旋转操作(先省略)
			break;
		}
		else //存在大于2小于-2的情况,树原来就不平衡,应该报错
		{
			assert(false);
		}
	}
	return true;
}



5.旋转操作(重点)

5.1左单旋

主体思想:

在这里插入图片描述


平衡因子的调整:
在这里插入图片描述


代码加细节处理:

在这里插入图片描述

void RotateL(Node* parent)  //左单旋,rotate->旋转
{
	Node* SubR = parent->_right;
	Node* SubRL = SubR->_left;  //这个有可能为空
	Node* ppnode = parent->_parent;  //原来父亲的父亲

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

	SubR->_left = parent;
	parent->_parent = SubR;

	if (ppnode == nullptr)  //旋转的是整颗树
	{
		_root = SubR;
		SubR->_parent = nullptr;
	}
	else  //旋转的是部分
	{
		if (ppnode->_left == parent) //是左子树
		{
			ppnode->_left = SubR;
		}
		else  //是右子树
		{
			ppnode->_right = SubR;
		}
		SubR->_parent = ppnode;
	}
	//最后更新平衡因子
	parent->_bf = SubR->_bf = 0;
}

5.2右单旋

PS:右单旋和左单旋类似,细节处理也差不多,这里只讲主体思路。
主体思路:
在这里插入图片描述

平衡因子的调整:
在这里插入图片描述

代码:

void RotateR(Node* parent)  //右单旋细节处理和左单旋差不多
{
	Node* SubL = parent->_left;
	Node* SubLR = SubL->_right;  //这个有可能为空
	Node* ppnode = parent->_parent;

	parent->_left = SubLR;
	if(SubLR)  SubLR->_parent = parent;

	SubL->_right = parent;
	parent->_parent = SubL;

	if (ppnode == nullptr)  //旋转的是整颗树
	{
		_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;
}

5.3左右双旋

PS:双旋其实就是两次单旋(复用即可),有前面的基础很好理解,重点在于旋转后平衡因子的更新

旋转思路:
在这里插入图片描述

平衡因子的调整:
想知道平衡因子调整是那种情况,我们需要在旋转前记录SubRL的平衡因子bf

  • bf为0是第一种情况。
  • bf为1是第二种情况。
  • bf为-1是第三种情况。

在这里插入图片描述

代码:

void RotateLR(Node* parent)  //左右双旋
{
	Node* SubL = parent->_left;
	Node* SubLR = SubL->_right;
	int bf = SubLR->_bf;

	RotateL(SubL);
	RotateR(parent);

	if (bf == 1) //插入的是右边
	{
		SubLR->_bf = 0;
		SubL->_bf = -1;
		parent->_bf = 0;
	}
	else if (bf == -1) //插入的是左边
	{
		SubLR->_bf = 0;
		SubL->_bf = 0;
		parent->_bf = 1;
	}
	else if (bf == 0) //刚好原来parent的左边就两个节点
	{
		SubLR->_bf = SubL->_bf = parent->_bf = 0;
	}
	else  //原来就不是平衡树,出现问题
	{

		assert(false);
	}
}

5.4右左双旋

PS:右左双旋和左右双旋思路是差不多的,重点还是在旋转后平衡因子的更新

旋转思路:
在这里插入图片描述

平衡因子的调整:
和前面一样,旋转前确认SubRL的平衡因子bf即可
在这里插入图片描述

代码:

void RotateRL(Node* parent)  //右左双旋
{
	Node* SubR = parent->_right;
	Node* SubRL = SubR->_left;
	int bf = SubRL->_bf;

	RotateR(SubR);
	RotateL(parent);

	if (bf == 1)  //插入的是右边
	{
		SubRL->_bf = 0;
		SubR->_bf = 0;
		parent->_bf = -1;
	}
	else if (bf == -1) //插入的是左边
	{
		SubRL->_bf = 0;
		parent->_bf = 0;
		SubR->_bf = 1;
	}
	else if (bf == 0)  //原来parent的右边就两个节点
	{
		SubRL->_bf = SubR->_bf = parent->_bf = 0;
	}
	else //原来就有问题
	{
		assert(false);
	}
}



6.一些简单的测试接口

int Height()
{
	return _Height(_root);
}

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 IsBalance()
{
	return IsBalance(_root);
}

//看当前树是不是平衡树
//(1)看每个子树是否满足左右子树高度差不超过一
//(2)看平衡因子和所求的左右子树高度差是否一致
bool IsBalance(Node* root)
{
	if (root == nullptr)
		return true;

	int leftHight = _Height(root->_left);
	int rightHight = _Height(root->_right);

	if (rightHight - leftHight != root->_bf)
	{
		cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
		return false;
	}

	return abs(rightHight - leftHight) < 2
		&& IsBalance(root->_left)
		&& IsBalance(root->_right);
}



7.完整代码

#pragma once
#include <iostream>
#include <assert.h>
#include <utility>
using namespace std;

template<class K, class V>
struct ALVTreeNode
{
	ALVTreeNode<K, V>* _left;
	ALVTreeNode<K, V>* _right;
	ALVTreeNode<K, V>* _parent;
	pair<K, V> _kv;  //存储键值对
	int _bf;
	ALVTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_bf(0)
		,_kv(kv)
	{}
};

template<class K, class V>
class AVLTree
{
public:
	typedef ALVTreeNode<K, V> Node;

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr) //一开始为空树,直接插入即可
		{
			_root = new Node(kv);
			return true;
		}
		Node* cur = _root;  //记录插入位置
		Node* parent = nullptr;  //待插入位置的父亲
		while (cur)
		{
			if (kv.first > cur->_kv.first)  //待插入节点在右子树
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first)  //待插入节点在左子树
			{
				parent = cur;
				cur = cur->_left;
			}
			else  //待插入节点已存在
			{
				return false;
			}
		}
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)  //插入在父亲的右边
		{
			parent->_right = cur;
		}
		else  //插入在父亲的左边
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//调整平衡因子
		while (parent)  //是有可能调整到根部的
		{
			if (cur == parent->_right)  //如果新插入的是右子树
			{
				parent->_bf++;
			}
			else if (cur == parent->_left)  //如果新插入的是左子树
			{
				parent->_bf--;
			}

			if (parent->_bf == 0) //插入后高度不变
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)  //插入后高度变化,但当前子树依然平衡,需要向上更新
			{
				parent = parent->_parent;
				cur = cur->_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;
	}

	/
	
	///
	int Height()
	{
		return _Height(_root);
	}

	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 IsBalance()
	{
		return IsBalance(_root);
	}

	//看当前树是不是平衡树
	//(1)看每个子树是否满足左右子树高度差不超过一
	//(2)看平衡因子和所求的左右子树高度差是否一致
	bool IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftHight = _Height(root->_left);
		int rightHight = _Height(root->_right);

		if (rightHight - leftHight != root->_bf)
		{
			cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
			return false;
		}

		return abs(rightHight - leftHight) < 2
			&& IsBalance(root->_left)
			&& IsBalance(root->_right);
	}

private:
	void RotateL(Node* parent)  //左单旋,rotate->旋转
	{
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;  //这个有可能为空
		Node* ppnode = parent->_parent;  //原来父亲的父亲

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

		SubR->_left = parent;
		parent->_parent = SubR;

		if (ppnode == nullptr)  //旋转的是整颗树
		{
			_root = SubR;
			SubR->_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;  //这个有可能为空
		Node* ppnode = parent->_parent;

		parent->_left = SubLR;
		if(SubLR)  SubLR->_parent = parent;

		SubL->_right = parent;
		parent->_parent = SubL;

		if (ppnode == nullptr)  //旋转的是整颗树
		{
			_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 RotateLR(Node* parent)  //左右双旋
	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;
		int bf = SubLR->_bf;

		RotateL(SubL);
		RotateR(parent);

		if (bf == 1) //插入的是右边
		{
			SubLR->_bf = 0;
			SubL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == -1) //插入的是左边
		{
			SubLR->_bf = 0;
			SubL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 0) //刚好原来parent的左边就两个节点
		{
			SubLR->_bf = SubL->_bf = parent->_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)  //插入的是右边
		{
			SubRL->_bf = 0;
			SubR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1) //插入的是左边
		{
			SubRL->_bf = 0;
			parent->_bf = 0;
			SubR->_bf = 1;
		}
		else if (bf == 0)  //原来parent的右边就两个节点
		{
			SubRL->_bf = SubR->_bf = parent->_bf = 0;
		}
		else //原来就有问题
		{
			assert(false);
		}
	}

	Node* _root = nullptr;
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

操作系统·操作系统引论

1.1 操作系统的目标和作用 1.目前常见操作系统&#xff1a; 微软&#xff1a;Windows系列&#xff08;以前MS-DOS&#xff09; UNIX&#xff1a;Solaris, AIX, HP UX, SVR4, BSD, ULTRIX 自由软件&#xff1a;Linux, freeBSD, Minix IBM: AIX, zOS(OS/390), OS/2, OS/400, PC…

壹[1],QT自定义控件创建(QtDesigner)

1&#xff0c;环境 Qt 5.14.2 VS2022 原因&#xff1a;厌烦了控件提升的繁琐设置&#xff0c;且看不到界面预览显示。 2&#xff0c;QT制作自定义控件 2.1&#xff0c;New/其他项目/Qt4 设计师自定义控件 2.2&#xff0c;设置项目名称 2.3&#xff0c;设置 2.4&#xff0c;设…

智能安全帽功能-EIS智能防抖摄像头4G定位视频语音气体检测

智能安全帽是一种集成多种智能功能的产品&#xff0c;例如实时定位、语音对讲、健康监测和AI智能预警等。这些丰富的功能能够更好地帮助工人开展工作&#xff0c;并提升安全保障水平。智能安全帽在各个行业中的应用越来越广泛。尤其在工程建设领域&#xff0c;项目管理和工作安…

京东店铺所有商品数据接口(JD.item_search_shop)

京东店铺所有商品数据接口是一种允许开发者在其应用程序中调用京东店铺所有商品数据的API接口。利用这一接口&#xff0c;开发者可以获取京东店铺的所有商品信息&#xff0c;包括商品标题、SKU信息、价格、优惠价、收藏数、销量、SKU图、标题、详情页图片等。 通过京东店铺所有…

嵌入式Linux和stm32区别? 之间有什么关系吗?

嵌入式Linux和stm32区别? 之间有什么关系吗&#xff1f; 主要体现在以下几个方面&#xff1a; 1.硬件资源不同 单片机一般是芯片内部集成flash、ram&#xff0c;ARM一般是CPU&#xff0c;配合外部的flash、ram、sd卡存储器使用。最近很多小伙伴找我&#xff0c;说想要一些嵌…

四阶龙格库塔与元胞自动机

龙格库塔法参考&#xff1a; 【精选】四阶龙格库塔算法及matlab代码_四阶龙格库塔法matlab_漫道长歌行的博客-CSDN博客 龙格库塔算法 Runge Kutta Method及其Matlab代码_龙格库塔法matlab_Lzh_023016的博客-CSDN博客 元胞自动机参考&#xff1a; 元胞自动机&#xff1a;森林…

小仙女必备,1分钟就能做出精美的电子相册

不知道大家有没有这样的困惑&#xff0c;手机里的照片太多&#xff0c;长久以来很多照片都容易被忘记。这个时候我们就可以将照片制作成电子相册&#xff0c;方便我们随时回味那些照片里的故事。如何制作呢&#xff1f; 制作电子相册只需要一个简单实用的制作工具就可以轻松完成…

【文献分享】NASA JPL团队CoSTAR一大力作:直接激光雷达里程计:利用密集点云快速定位

论文题目&#xff1a;Direct LiDAR Odometry: Fast Localization With Dense Point Clouds 中文题目&#xff1a;直接激光雷达里程计:利用密集点云快速定位 作者&#xff1a;Kenny Chen, Brett T.Lopez, Ali-akbar Agha-mohammadi 论文链接&#xff1a;https://arxiv.org/pd…

在 CelebA 数据集上训练的 PyTorch 中的基本变分自动编码器

摩西西珀博士 一、说明 我最近发现自己需要一种方法将图像编码到潜在嵌入中&#xff0c;调整嵌入&#xff0c;然后生成新图像。有一些强大的方法可以创建嵌入或从嵌入生成。如果你想同时做到这两点&#xff0c;一种自然且相当简单的方法是使用变分自动编码器。 这样的深度网络不…

学习LevelDB架构的检索技术

目录 一、LevelDB介绍 二、LevelDB优化检索系统关键点分析 三、读写分离设计和内存数据管理 &#xff08;一&#xff09;内存数据管理 跳表代替B树 内存数据分为两块&#xff1a;MemTable&#xff08;可读可写&#xff09; Immutable MemTable&#xff08;只读&#xff0…

力扣370周赛 -- 第三题(树形DP)

该题的方法&#xff0c;也有点背包的意思&#xff0c;如果一些不懂的朋友&#xff0c;可以从背包的角度去理解该树形DP 问题 题解主要在注释里 //该题是背包问题树形dp问题的结合版&#xff0c;在树上解决背包问题 //背包问题就是选或不选当前物品 //本题求的是最大分数 //先转…

京东商品详情API接口(PC端和APP端),京东详情页,商品属性接口,商品信息查询

京东开放平台提供了API接口来访问京东商品详情。通过这个接口&#xff0c;您可以获取到商品的详细信息&#xff0c;如商品名称、价格、库存量、描述等。 以下是使用京东商品详情API接口的一般步骤&#xff1a; 注册并获取API权限&#xff1a;您需要在京东开放平台上注册并获取…

arcgis pro模型构建器

如果你不想部署代码包环境来写arcpy代码&#xff0c;还想实现批量或便携封装的操作工具&#xff0c;那么使用模型构建器是最好的选择。1.简介模型构建器 1.1双击打开模型构建器 1.2简单模型构建步骤 先梳理整个操作流程&#xff0c;在纸上绘制在工具箱中找到所需工具拖进来把…

LangChain+LLM实战---实用Prompt工程讲解

原文&#xff1a;Practical Prompt Engineering 注&#xff1a;本文中&#xff0c;提示和prompt几乎是等效的。 这是一篇非常全面介绍Prompt的文章&#xff0c;包括prompt作用于大模型的一些内在机制&#xff0c;和prompt可以如何对大模型进行“微调”。讲清楚了我们常常听到的…

搭建二维码系统,轻松实现固定资产的一物一码管理

固定资产管理中普遍存在盘点难、家底不清、账实不一致、权责不清晰等问题&#xff0c;可以在草料上搭建固定资产管理系统&#xff0c;通过组合功能模块实现资产信息展示、领用登记、出入库管理、故障报修等功能&#xff0c;对固定资产进行一物一码规范化管理。 比如张掖公路事业…

创建基于多任务的并发服务器

有几个请求服务的客户端&#xff0c;我们就创建几个子进程。 这个过程有以下三个阶段&#xff1a; 这里父进程传递的套接字文件描述符&#xff0c;实际上不需要传递&#xff0c;因为子进程会复制父进程拥有的所有资源。 #include <stdio.h> #include <stdlib.h>…

票务营销数字化:景区增收利器

身处数字化时代&#xff0c;景区门票销售早已插上数字化翅膀&#xff0c;通过一站式的票务管理、精准的营销策略等为景区带来了数字化增长。票务营销系统如何帮助景区增收&#xff1f; l 提高工作效率&#xff1a;传统的景区售票方式往往需要大量的人工操作&#xff0c;不仅耗时…

微信小程序overflow-x超出部分样式不渲染

把display:flex改成display:inline-flex&#xff0c; 将对象作为内联块级弹性伸缩盒显示&#xff0c; 类似与是子元素将父元素撑开&#xff0c;样式就显示出来了

纺织布料行业小程序开发

随着互联网的发展&#xff0c;越来越多的消费者通过线上渠道购买纺织布料产品。为了满足市场需求&#xff0c;越来越多的纺织布料企业选择开发小程序&#xff0c;以提高销售效率、拓宽销售渠道和提升用户体验。下面是开发纺织布料行业小程序的具体步骤&#xff1a; 1. 登录乔拓…

Flume从入门到精通一站式学习笔记

文章目录 什么是FlumeFlume的特性Flume高级应用场景Flume的三大核心组件Source&#xff1a;数据源channelsink Flume安装部署Flume的使用案例&#xff1a;采集文件内容上传至HDFS案例&#xff1a;采集网站日志上传至HDFS 各种自定义组件例如&#xff1a;自定义source例如&#…