【C++技能树】手撕AVL树 --插入与旋转详解

news2024/11/24 9:34:32

在这里插入图片描述
Halo,这里是Ppeua。平时主要更新C++,数据结构算法,Linux与ROS…感兴趣就关注我bua!

文章目录

  • 0.平衡搜索二叉树概念
    • 0.1 平衡因子
  • 1.插入
    • 1.1 普通插入操作
    • 1.2更新平衡因子
  • 2.旋转
    • 2.1 左单旋
    • 2.2 右单旋
    • 2.3 右左双旋
    • 2.4 左右双旋
  • 3. 旋转判定
  • 4. 验证是否为AVL树
  • 5.完整源码(AVL插入旋转)

在这里插入图片描述

0.平衡搜索二叉树概念

为什么在会了搜索二叉树之后,还需要学习平衡搜索二叉树呢?在搜索二叉树部分,并没有要求树保持平衡,仅需遵循左小右大即可.

若往树中插入有序或者接近有序的值,会出现下面这种情况,退化为单支树.

f5e6a0c761ccc48519e7d23748aaeea

这样的搜索树就完全没有效率可言,他的时间复杂度为o(N).所以我们需要一棵正常(平衡)的树来解决这个问题.

否则当map和set用这样的树岂不是效率非常的低,所以这就是平衡二叉搜索树的意义.

将子树的左右平衡高度差维持在(-1/0/1)之间,若超过了这个范围,则对树进行高度调整,从而减少搜索长度.

上面的那棵树可以变化成这个样子,这样搜索的效率就变成了o(logN),大大提高了效率.

52a0ba283e5ccbecd31c8b27f781b32

这同样是一棵满二叉树.其节点个数为 2 h − 1 2^h-1 2h1,可以将其看作一颗特殊的平衡二叉搜索树.

所以平衡二叉搜索树的节点个数的公式为: 2 h − X 2^h-X 2hX,X的范围介于[1, 2 ( h − 1 ) − 1 2^{(h-1)}-1 2(h1)1]之间.

这里可以这样理解.二叉树的最大深度差为1,此时的最后一层最差的情况为只有一个节点,那么缺少的节点数为前面所有层的节点.

忽略掉常数,可以看到h可以近似等于logN.

0.1 平衡因子

上文提到,二叉搜索树需要计算其平衡因子来稳定树的平衡度.那么平衡因子怎么算呢?

左子树的最大高度为负,右子树的最大高度为正.两值相加,体现在根上的即为这棵树的平衡因子

72d1ee6e5abfb6956df96f1d6a2bf42

因为其对树的结构调整,需要大量访问parent,也就是子树的根,所以在定义这棵树的时候.我们直接将其parent存起来,方便后续查找.

template<typename K,typename V>
struct TreeNode
{	
	pair<K, V>_val;
	TreeNode<K,V>* _left;
	TreeNode<K,V>* _right;
	TreeNode<K,V>* _parent;
	int _bf;

	TreeNode(const pair<K,V> val)
		:_val(val),
		_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_bf(0)
	{}
};

1.插入

我们先写一个最简单的二叉树插入操作,然后在上面加上平衡因子的更新与旋转即可.

1.1 普通插入操作

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

1.2更新平衡因子

当我们插入一个新的节点的时候,**新增在左则平衡因子减减,新增在右则平衡因子加加.**此时有以下这几种情况:

  1. 当更新完平衡因子,parent的平衡因子为0时,说明并没有引起这棵子树的高度变化.则不需要继续向上更新平衡因子

    d1daeeb2b4993bb965ff2d28091bd8a

  2. 当更新完平衡因子,parent的平衡因子为-1/1时,说明引起了这棵树的高度变化,需要继续更新其祖先的平衡因子

    e5a850f2ac5487b61031878fcdeb83a

  3. 当更新完平衡因子,parent的平衡因子为-2/2时,说明这棵树需要进行旋转来维持平衡

    e5a850f2ac5487b61

​ 关于旋转我们稍后再说,我们在代码里加上更新平衡因子的这几步操作.

while (parent)
{
    if (parent->_left == cur)
    {
        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)
    {
        //旋转
    }
}

2.旋转

旋转根据实际的情况,可以分为四种情况:左单旋,右单旋,左右单旋,右左单旋

旋转的目的是:在不破坏AVL的前提下,降低这棵树的高度,使其重新成为AVL树

2.1 左单旋

当parent节点的平衡因子为2时,需要进行左单旋来降低整棵树的高度.

99f00fa3799b2eb8388f04c146961d8

这是最简单的情况,即让parent->right=cur->left cur->left=parent

e51ff0434783fd26331ced235bc266a

当需要旋转的是一颗子树的时候,核心操作不变,但需要更新下parent 与 pparent的关系,将pparent指向cur

95086d741a40aaa01aa14227dee4ba0

所以左单旋的代码也可以写出来了

void RotateL(Node* parent)
{
    Node* cur = parent->_right;
    Node* curleft = cur->_left;
    if (curleft)
        curleft->_parent = parent;
    parent->_right = curleft;
    cur->_left = parent;

    Node* pparent = parent->_parent;
    parent->_parent = cur;
    if (_root == parent)
    {
        cur->_parent = nullptr;
        _root = cur;
    }
    else
    {
        if (pparent->_left == parent)
        {
            pparent->_left = cur;
        }
        else pparent->_right = cur;
        cur->_parent = pparent;
    }
    parent->_bf =cur->_bf= 0;
}

2.2 右单旋

0204c9ebc93ddd12b6a1fc6aa9e9904

38eb9e421d098c02a98833aeff6a835

其旋转核心为:**parent->left=cur->right cur->right=parent **

所以右旋转的代码为:

void RotateR(Node* parent)
{
    Node* cur = parent->_left;
    Node* curleft = cur->_left;
    if (curleft)
    {
        curleft->_parent = parent;
    }
    parent->_left = curleft;
    if (parent == _root)
    {
        cur->_left = parent;
    }
    else
    {
        Node* pparent = parent->_parent;
        if (pparent->_left = parent)
        {
            pparent->_left = cur;
        }
        else
            pparent->_right = cur;
        cur->_parent = pparent;

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

}

2.3 右左双旋

其抽象模型为:

77c0d0a19775931dbca542aafecfe67

这个模型实在有点抽象,我们分以下几种情况来讨论.

当h0时,此时60为插入的节点(60->bf0)

deb3c319d89ed5b7e1b7ab7947f59f6

旋转完成后,parent->bf cur->bf均为0

当h==1时,此时可以分为在60的左边插入或者在60的右边插入

  1. 在左边插入(curleft->bf==-1)

1cc1e4b1c03dfbb74949479a83b359e

旋转完成后,parent->bf=0 cur->bf=1 curleft->bf=0

  1. 在右边插入(curleft->bf==1)

27bd9f72cfc3de5bd1d4600090a200b

旋转完成后,parent->bf=-1 cur->bf=0 curleft->bf=0

当h==2时,此时也可以分为在左边插入以及在右边插入

  1. 在左边插入(curleft->bf==-1)

    8d3fce21b6c729a3d6bcdc24db09a4c

旋转完成后,parent->bf=0 cur->bf=1 curleft->bf=0

  1. 在右边插入(curleft->bf==1)

02483a63e12eca2b1a06d74d751ac62

旋转完成后,parent->bf=-1 cur->bf=0 curleft->bf=0

可以看出,引发右左双旋的原因为:cur->bf==-1,parent->bf==2

所以 其代码可以复用之前的旋转,但之前的旋转改变了平衡因子,我们需要依照上面的规律再特殊处理一下

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

    if (bf == 0)
    {
        parent->_bf = 0;
        cur->_bf = 0;
        curright->_bf = 0;
    }
    if (bf == -1)
    {
        parent->_bf = 0;
        cur->_bf = 1;
        curright->_bf = 0;
    }
    if (bf == 1)
    {
        parent->_bf = -1;
        cur->_bf = 0;
        curright->_bf = 0;
    }
}

2.4 左右双旋

其抽象模型为:

ea71377250854e9e30ddcce8fc42f3b

这个模型实在有点抽象,我们分以下几种情况来讨论.

h0 此时60为插入的节点(curright->bf0)

d30d04a914ff74ea0017aadfec25d01

旋转完成后,parent->bf=cur->bf=curright->bf=0

当h==1时,此时可以分为在60的左边插入或者在60的右边插入

  1. 在左边插入(curright->bf==-1)

e0bd4568684f60c958743920758317e

旋转完成后,parent->bf=1 cur->bf=0 curleft->bf=0

  1. 在右边插入(curright->bf==1)

960b1d4215886847c0cebaf7d0d44ce

旋转完成后,parent->bf=0 cur->bf=-1 curright->bf=0

当h==2时,此时也可以分为在左边插入以及在右边插入

  1. 在左边插入(curright->bf==-1)

    a02e3047b7bf065be4859070dab9b4b

旋转完成后,parent->bf=1 cur->bf=0 curright->bf=0

  1. 在右边插入(60->bf==1)

79eeeda5389aad2f9cd95dbc7796ccb

旋转完成后,parent->bf=0 cur->bf=-1 curright->bf=0

可以看出,引发左右双旋的原因为:cur->bf1,parent->bf-2

void RotaLR(Node* parent)
{
    Node* cur = parent->_left;
    Node* curright = cur->right;
    int bf = curright->_bf;
    RotateL(cur);
    RotateR(parent);
    if (bf == 0)
    {
        parent->_bf = 0;
        cur->_bf = 0;
        curright->_bf = 0;
    }
    if (bf == -1)
    {
        parent->_bf = 1;
        cur->_bf = 0;
        curright->_bf = 0;
    }
    if (bf == 1)
    {
        parent->_bf = 0;
        cur->_bf = -1;
        curright->_bf = 0;
    }
}

3. 旋转判定

旋转的判定为:同号单旋,异号双旋(将cur旋转为parent同号)

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)
    {
        RotaRL(parent);
    }
    else if (parent->_bf == -2 && cur->_bf == 1)
    {
        RotaLR(parent);
    }
    break;
}

4. 验证是否为AVL树

bool IsAVLTree()
{
    return IsAVLTree(_root);
}
bool IsAVLTree(Node* root)
{
    if (root == nullptr)return true;
    int leftheight = Height(root->_left);
    int rightheight= Height(root->_right);
    if (rightheight - leftheight != root->_bf)
    {
        cout << "异常" << rightheight<< " " << leftheight<< " "<<root->_bf<<endl;
        return false;
    }
    return abs(rightheight - leftheight) < 2 && IsAVLTree(root->_left) && IsAVLTree(root->_right);
}
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;
}

5.完整源码(AVL插入旋转)

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
template<typename K,typename V>
struct TreeNode
{	
	pair<K, V>_val;
	TreeNode<K,V>* _left;
	TreeNode<K,V>* _right;
	TreeNode<K,V>* _parent;
	int _bf;

	TreeNode(const pair<K,V> val)
		:_val(val),
		_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_bf(0)
	{}
};
template<typename K,typename V>
class AVLBSTree {
	typedef TreeNode<K,V> Node;
public:
	bool Insert(const pair<K, V> kv)
	{
		
		Node* parent = _root;
		Node* cur = _root;
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		while (cur)
		{
			if (kv.first > cur->_val.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_val.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else return false;;
		}
		cur = new Node(kv);
		if (parent->_val.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
			
		}
		cur->_parent = parent;
		while (parent)
		{
			if (parent->_left == cur)
			{
				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)
				{
					RotaRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotaLR(parent);
				}
				break;
			}
			else assert(false);
		}     
		return true;


	}
	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		if (curleft)
			curleft->_parent = parent;
		parent->_right = curleft;
		cur->_left = parent;
		
		Node* pparent = parent->_parent;
		parent->_parent = cur;
		if (_root == parent)
		{
			cur->_parent = nullptr;
			_root = cur;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = cur;
			}
			else pparent->_right = cur;
			cur->_parent = pparent;
		}
		parent->_bf =cur->_bf= 0;
	}
	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		if (curright)
		{
			curright->_parent = parent;
		}
		parent->_left = curright;
		cur->_right = parent;
		if (parent == _root)
		{
			cur->_left = parent;
		}
		else
		{
			Node* pparent = parent->_parent;
			if (pparent->_left == parent)
			{
				pparent->_left = cur;
			}
			else
				pparent->_right = cur;
			cur->_parent = pparent;

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

	}

	void RotaRL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		int bf = curleft->_bf;
		RotateR(parent->_right);
		RotateL(parent);
		
		if (bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
			curleft->_bf = 0;
		}
		if (bf == -1)
		{
			parent->_bf = 0;
			cur->_bf = 1;
			curleft->_bf = 0;
		}
		if (bf == 1)
		{
			parent->_bf = -1;
			cur->_bf = 0;
			curleft->_bf = 0;
		}
	}
	void RotaLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		int bf = curright->_bf;
		RotateL(cur);
		RotateR(parent);
		if (bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		if (bf == -1)
		{
			parent->_bf = 1;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		if (bf == 1)
		{
			parent->_bf = 0;
			cur->_bf = -1;
			curright->_bf = 0;
		}
	}
	bool IsAVLTree()
	{
		return IsAVLTree(_root);
	}
	bool IsAVLTree(Node* root)
	{
		if (root == nullptr)return true;
		int leftheight = Height(root->_left);
		int rightheight= Height(root->_right);
		if (rightheight - leftheight != root->_bf)
		{
			cout << "异常" << rightheight<< " " << leftheight<< " "<<root->_bf<<endl;
			return false;
		}
		return abs(rightheight - leftheight) < 2 && IsAVLTree(root->_left) && IsAVLTree(root->_right);
	}
	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;
	}
private:
	Node* _root = nullptr;
};

image-20230905164632777
{
if (root == nullptr)return true;
int leftheight = Height(root->_left);
int rightheight= Height(root->_right);
if (rightheight - leftheight != root->_bf)
{
cout << “异常” << rightheight<< " " << leftheight<< " "<_bf<<endl;
return false;
}
return abs(rightheight - leftheight) < 2 && IsAVLTree(root->_left) && IsAVLTree(root->_right);
}
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;
}
private:
Node* _root = nullptr;
};


[外链图片转存中...(img-4uSXVW1J-1694524567600)]

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

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

相关文章

量化:Fama-French五因子模型复现

文章目录 参考三因子模型概述策略设计 五因子模型概述 参考 掘金-fama三因子 b站-fama三因子 知乎-fama五因子 因子溢价、因子暴露及用途 三因子模型 概述 在CAPM模型的基础上加入了两个因子提出了三因子模型&#xff0c;三因子分别为 市场因子MKT规模因子SMB&#xff08;S…

收货已完成,删除采购订单没有任何提示

收货已完成或发票已校验&#xff0c;此时删除订单系统是不允许的&#xff0c;正常会报错06115&#xff0c; 现在问题是生产机不报这个消息&#xff0c;直接删除了订单行&#xff0c;查了一下资料&#xff0c;都说这个配置是系统写死的&#xff0c;通过增加06115的消息号 也不起…

算法:经典贪心算法--跳一跳[2]

1、题目&#xff1a; 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 返回到达 nums[n - 1] 的最小跳跃次数。生…

Maven 安装配置

Maven 安装配置 文章目录 Maven 安装配置一、下载 Maven二、解压Maven核心程序三、指定本地仓库四、配置阿里云镜像仓库4.1 将原有的例子配置注释掉4.2 加入新的配置 五、配置 Maven 工程的基础 JDK 版本六、配置环境变量6.1 检查 JAVAHOME 配置是否正确6.2 配置 MAVENHOME6.3 …

一个综合资产收集和漏洞扫描工具

Komo 介绍 Komo是一个综合资产收集和漏洞扫描工具&#xff0c;并且支持进度记录&#xff0c;通过多种方式对子域进行获取&#xff0c;收集域名&#xff0c;邮箱&#xff0c;子域名存活探测&#xff0c;域名指纹识别&#xff0c;域名反查ip&#xff0c;ip端口扫描&#xff0c;w…

Python 图形化界面基础篇:添加按钮( Button )到 Tkinter 窗口

Python 图形化界面基础篇&#xff1a;添加按钮&#xff08; Button &#xff09;到 Tkinter 窗口 引言什么是 Tkinter 按钮&#xff08; Button &#xff09;&#xff1f;步骤1&#xff1a;导入 Tkinter 模块步骤2&#xff1a;创建 Tkinter 窗口步骤3&#xff1a;创建按钮&…

2023年9月12日

实现一个图形类&#xff08;Shape&#xff09;&#xff0c;包含受保护成员属性&#xff1a;周长、面积&#xff0c; 公共成员函数&#xff1a;特殊成员函数书写 定义一个圆形类&#xff08;Circle&#xff09;&#xff0c;继承自图形类&#xff0c;包含私有属性&#xff1a;半…

【C++】STL之string

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言string类的内部成员变量&#xff1a; string的模拟实现**六个默认成员函数****iterator****capacity****modify**element access**String operations**Non-memb…

分布式id的概述与实现

文章目录 前言一、分布式id技术选型二、雪花算法三、在项目中集成雪花算法 前言 随着业务的增长&#xff0c;数据表可能要占用很大的物理存储空间&#xff0c;为了解决该问题&#xff0c;后期使用数据库分片技术。将一个数据库进行拆分&#xff0c;通过数据库中间件连接。如果…

C语言顺序表

文章目录 前言线性表顺序表静态顺序表动态顺序表 接口实现 前言 我们先补一下上篇博客落下的知识点&#xff1a; 首先说一下斐波那契的时间复杂度和空间复杂度&#xff1a; long long Fac(size_t N) {if(0 N)return 1;return Fac(N-1)*N; }还是说一下size_t代表的类型是unsi…

C#,数值计算——伽马微分(Gammadev)的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { public class Gammadev : Normaldev { private double alph { get; set; } private double oalph { get; set; } private double bet { get; set; } private double a1 { g…

2023年第十届中文自修杯汉字小达人比赛安排、常见问题和试卷题型

好消息&#xff01;面向上海市小学生的2023年第十届中文自修杯汉字小达人比赛开始了&#xff01; 这个活动从2014年开始举办第一届&#xff0c;迄今已经举办了九届&#xff0c;活动的影响力越来越大&#xff0c;深受上海市的小学生们欢迎。而且&#xff0c;有一些外省市的学校…

软件测评报告需要提交什么材料?

软件测评报告 软件测评/软件测试的经典定义是在规定条件下对程序进行操作&#xff0c;以发现错误&#xff0c;对软件质量进行评估。因为软件是由文档、数据以及程序组成的&#xff0c;所以软件测试的对象也就不仅仅是程序本身&#xff0c;而是包括软件形成过程的文档、数据以及…

Liunx系统下载安装Nginx下载安装

目录 版本介绍 Liunx下安装步骤 补充&#xff1a;Docker安装nginx 版本介绍 Nginx开源版 http://nginx.org/en/ 官方原始的Nginx版本 Nginx plus商业版 开箱即用&#xff0c;集成了大量功能 Open Resty https://openresty.org/cn/ OpenResty是一个基于Nginx与 Lua 的高性…

ModuleNotFoundError: No module named ‘transformers.modeling_bert‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

【LeetCode题目详解】第九章 动态规划part17 647. 回文子串 ● 516.最长回文子序列(day57补)

本文章代码以c为例&#xff01; 一、力扣第647题&#xff1a;回文子串 题目&#xff1a; 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具…

html5学习笔记21-css简略学习

CSS https://www.runoob.com/css/css-tutorial.html CSS (Cascading Style Sheets&#xff0c;层叠样式表&#xff09;&#xff0c;是一种用来为结构化文档&#xff08;如 HTML 文档或 XML 应用&#xff09;添加样式&#xff08;字体、间距和颜色等&#xff09;的计算机语言&a…

Ngnix封禁IP与ip段

Ngnix IP封禁以及实现自动封禁IP 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.创建文件&#xff08;被封禁的ip写里面&#xff09; 在ngnix的conf目录下创建一个blockip.conf文件&#xff0c;里面放需要封禁的IP与ip段&#xff0c;格式如下&#xff08;deny是禁用…

C++ 将off格式文件转换成ply格式存储

文章目录 引言off格式文件ply格式文件C标准库实现off转ply 引言 三维模型是计算机图形学中的一个重要概念&#xff0c;它是由一系列三维坐标点构成的点云或多边形网格。 OFF格式是一种用于描述三维模型的文件格式&#xff0c;它可以描述点云和多边形网格等不同类型的三维模型。…

抖音小程序开发教学系列(5)- 抖音小程序数据交互

第五章&#xff1a;抖音小程序数据交互 5.1 抖音小程序的网络请求5.1.1 抖音小程序的网络请求方式和API介绍5.1.2 抖音小程序的数据请求示例和错误处理方法 5.2 抖音小程序的数据缓存和本地存储5.2.1 抖音小程序的数据缓存机制和使用方法5.2.2 抖音小程序的本地存储和数据持久化…