【数据结构高阶】AVL树

上期博客我们讲解了set/multiset/map/multimap的使用,下面我们来深入到底层,讲解其内部结构:

目录

一、AVL树的概念

二、AVL树的实现

2.1 节点的定义

2.2 数据的插入

2.2.1 平衡因子的调整

2.2.1.1 调整平衡因子的规律

2.2.2 子树的旋转调整

2.2.2.1 左单旋

2.2.2.2 右单旋

2.2.2.3 左右双旋

2.2.2.4 右左双旋

2.3 AVL树的检查验证

2.4 测试代码

三、AVL树实现的完整代码


一、AVL树的概念

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

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

● 它的左右子树都是AVL树

● 左右子树高度之差(简称平衡因子,不是每种AVL树都有平衡因子,平衡因子只是AVL树实现的一种方式)的绝对值不超过1(下图的平衡因子计算公式为:节点的右子树高度-左子树高度)

下图是一个AVL树:

二、AVL树的实现

2.1 节点的定义

这次我们直接实现K-V模型的二叉树:

template<class Key,class Val>
class AVLTreeNode
{
public:AVLTreeNode<Key, Val>* _left;AVLTreeNode<Key, Val>* _right;AVLTreeNode<Key, Val>* _parent;//多一个指针指向其父节点,方便我们的后续操作pair<Key, Val> _kv;int _bf;//balance factor(平衡因子)AVLTreeNode(pair<Key, Val> kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}
};

2.2 数据的插入

AVL树的数据插入需要遵循二叉搜索树的规律:

template<class Key, class Val>
class AVLTree
{
public:typedef AVLTreeNode<Key, Val> Node;bool Insert(const pair<Key,Val>& kv){Node* cur = _root, * parent = nullptr;while (cur)//找到合适的位置{if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{cout << "插入的值重复" << endl;return false;}}cur = new Node(kv);cur->_parent = parent;//将插入的节点连接上二叉树if (parent == nullptr){_root = cur;}else if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}return true;}private:AVLTreeNode<Key, Val>* _root = nullptr;
};

但是成功插入数据后就结束了吗?在AVL树中可没有这么简单,我们需要更新其插入节点的父节点的平衡因子,来保证这课树的整体结构还是一个AVL树。

2.2.1 平衡因子的调整

下面我们来讨论一下更新父节点平衡因子的操作:

我们看到上面的二叉树,现在向其中插入一个键值为8的数据:

插入后我们发现其插入节点的父节点平衡因子需要修改,那就要修改一下:

修改其父节点后,我们发现其父节点的父节点的平衡因子也需要调整,那再调整一下吧:

但是本次调整过后,平衡因子出现了2这个数值,但是平衡因子只能是1/0/-1,说明这时该节点下的子树已经不满足AVL树的结构了,这时要对其子树进行旋转来调整该树的结构(至于怎么旋转我们在下面会详细讲解),经过旋转过后子树肯定是满足AVL树的结构的,所以就并不再检查进行旋转的节点父节点平衡因子了:

那我们再看看另一种情况,我们向下面的二叉树中插入键值为6的数据:

插入后,我们发现需要修改其父节点的平衡因子: 

修改后,我们发现其父节点的平衡因子为0,为0时就意味着子树的高度没有发生变化,我们就没有必要再向上更新其父节点的平衡因子了: 

2.2.1.1 调整平衡因子的规律

所以总结一下上述规律,我们插入节点后肯定是要调整其父节点的平衡因子的,那调整父节点的平衡因子后,什么时候要向上调整其父节点的父节点(爷爷节点)的平衡因子呢?

这里有三种情况:

当父节点的平衡因子调整过后为1或-1时,此时说明其节点所在的子树的高度发生了变化(变为1或-1前,平衡因子只可能是0,说明插入之前子树两边的高度是相同的,这时在插入一个节点会导致其中一颗子树的高度发生变化),这时需要再继续向上调整其父节点的平衡因子

当父节点的平衡因子调整过后为2或-2时,该节点的子树不平衡,需要处理这颗旋转处理子树,旋转实质是是将该节点所在的子树的高度降1,所以旋转过后不影响子树的高度变化,此时就不需要再向上更新其父节点的平衡因子了

当父节点的平衡因子调整过后为0时(变为0前,平衡因子只可能是1或-1,说明插入之前一边高,一边低,但是插入节点是在矮的那边,其子树的高度不变),说明所在的子树高度不变,不用继续向上更新

我们用代码来实现一下平衡因子的调整:

template<class Key, class Val>
class AVLTree
{
public:typedef AVLTreeNode<Key, Val> Node;bool Insert(const pair<Key,Val>& kv){Node* cur = _root, * parent = nullptr;while (cur)//找到合适的位置{if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{cout << "插入的值重复" << endl;return false;}}cur = new Node(kv);cur->_parent = parent;//将插入的节点连接上二叉树if (parent == nullptr){_root = cur;}else if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}//向上调整各节点的平衡因子while (parent){if (parent->_left == cur){--parent->_bf;//更新平衡因子}else{++parent->_bf;//更新平衡因子}//根据父节点的平衡因子的情况决定师傅继续向上调整各节点的平衡因子if (parent->_bf == 1 || parent->_bf == -1)//平衡因子为1或1继续向上更新{parent = parent->_parent;cur = cur->_parent;}else if (parent->_bf == 2 || parent->_bf == -2)//平衡因子为2或-2,树的结构不平衡,旋转parent节点所在子树{//旋转parent所在的子树}else if (parent->_bf == 0)//平衡因子为0,插入结束{break;}else//出现其他的情况说明树的根据有问题,报错退出{cout << "树的结构出错了" << endl;assert(0);exit(-1);}}return true;}private:AVLTreeNode<Key, Val>* _root = nullptr;
};

2.2.2 子树的旋转调整

当某个节点的平衡因子变为2或-2时,我们需要对其所在的子树进行旋转调整

下面我们分情况来讨论:

2.2.2.1 左单旋

下面的图代表的是一棵AVL树,其中52和60表示的两个节点,A、B、C分别为高度为h的AVL子树:

下面向C子树中插入数据使其高度发生变化:

现在键值为52的节点平衡因子变为2,下面我们要将这棵子树旋转:

我们可以看到旋转的过程是这样的:

B子树变成52的右子树->52变成60的左子树->60变成整颗树的根

上面这种需要旋转的树的根节点平衡因子为2,并且其根节点右孩子节点的平衡因子为1;这种情况下的旋转我们将其称为左单旋

下面我们就用代码实现一下左单旋:

void RotateL(Node* parent)//左单旋
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;//更新parent的右节点if (subRL)//防止该节点为空{subRL->_parent = parent;//更新subRL的父节点}parent->_parent = subR;//更新parent的父节点subR->_left = parent;//subR的左子树置为parentsubR->_parent = pparent;//更新subR的父节点if (pparent == nullptr)//旋转的是整棵树{_root = subR;//更新根节点}else//将旋转后的子树链接上整个二叉树{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}}subR->_bf = parent->_bf = 0;//更新平衡因子
}

下图是画出了代码所表示的节点: 

2.2.2.2 右单旋

再来看到另一种情况:

 下面向子树A中插入数据使其高度发生变化:

现在键值为60的节点平衡因子变为-2,下面我们要将这棵子树旋转: 

我们可以看到旋转的过程是这样的:

B子树变成60的左子树->60变成52的右子树->52变成整颗树的根

上面这种需要旋转的树的根节点平衡因子为-2,并且其根节点左孩子节点的平衡因子为-1;这种情况下的旋转我们将其称为右单旋

下面我们用代码实现一下右单旋:

void RotateR(Node* parent)//右单旋
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;//更新parent的左节点if (subLR)//防止该节点为空{subLR->_parent = parent;//更新subLR的父节点}parent->_parent = subL;//更新parent的父节点subL->_right = parent;//subL的右子树置为parentsubL->_parent = pparent;//更新subL的父节点if (pparent == nullptr)//旋转的是整棵树{_root = subL;//更新根节点}else//将旋转后的子树链接上整个二叉树{if (pparent->_left == parent){pparent->_left = subL;}else{pparent->_right = subL;}}subL->_bf = parent->_bf = 0;//更新平衡因子
}

下图画出了代码所表示的节点: 

2.2.2.3 左右双旋

接着来看到稍微复杂一点的情况,下面的图代表的是一棵AVL树,其中99、66和88表示的是三个节点,A、D分别为高度为h的AVL子树,B、C分别为高度为h-1的AVL子树:

现在我们向B子树中插入数据使其高度发生变化:

现在键值为99的节点平衡因子变为-2,下面我们要将这棵子树旋转: 

下面先将66所在子树进行左单旋:

再让99所在子树右单旋:

上面这种需要旋转的树的根节点平衡因子为-2,并且其根节点左孩子节点的平衡因子为1;这种情况下的两次旋转我们将其称为左右双旋

但是我们要注意的是,当新增节点在C子树上时,通过左右双旋调整后的结果又不一样:

 

我们可以看到因为新增节点的位置发生了变化,最终导致了各节点的平衡因子出现了差异

除了这两种情况,还有一种当h为0的极端情况:

所以我们在用代码实现的时要注意

void RotateLR(Node* parent)//左右双旋
{Node* subL = parent->_left;Node* subLR = subL->_right;int subLR_bf = subLR->_bf;//记录该节点的平衡因子来做旋转结束后的修改平衡因子的依据RotateL(subL);//调用左单旋RotateR(parent);//调用右单旋if (subLR_bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (subLR_bf == 0){parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else if (subLR_bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else{assert(false);}
}

2.2.2.4 右左双旋

最后我们只剩一种情况了:需要旋转的树的根节点平衡因子为-2,并且其根节点右孩子节点的平衡因子为-1

下面我们来分析:

我们向上面这棵树中的C子树插入数据使其高度发生变化:

现在键值为66的节点平衡因子变为2,下面我们要将这棵子树旋转:

下面先将99所在子树进行右单旋:

再将66所在子树进行左单旋:

这样先右旋再左旋的情况我们将其称做:右左双旋

和左右双旋一样新增节点的位置发生变化,会导致各节点的平衡因子出现差异,当新增节点在B子树上时,通过左右双旋调整后的结果又不一样:

另外还有h为0的特殊情况:

好了,分析到这里,使用代码实现也就不是问题了:

void RotateRL(Node* parent)//右左双旋
{Node* subR = parent->_right;Node* subRL = subR->_left;int subRL_bf = subRL->_bf;//记录该节点的平衡因子来做旋转结束后的修改平衡因子的依据RotateR(subR);//调用右单旋RotateL(parent);//调用左单旋if (subRL_bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (subRL_bf == 0){parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else if (subRL_bf == 1){parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else{assert(false);}
}

2.3 AVL树的检查验证

下面我们来实现一个函数来检查自己所创建的AVL树是否符合规则:

int _CountHeight(Node* root)//计算树的高度
{if (root == nullptr)return 0;int leftnum = _CountHeight(root->_left);int rightnum = _CountHeight(root->_right);return leftnum > rightnum ? leftnum + 1 : rightnum + 1;
}
bool _IsBalance(Node* root)
{if (root == nullptr)return true;int leftH = _CountHeight(root->_left);int rightH = _CountHeight(root->_right);if (rightH - leftH != root->_bf)//检查当前节点平衡因子是否正确{cout << root->_kv.first << "节点平衡因子异常" << endl;return false;}return abs(leftH - rightH) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);//检查左右子树高度之差的绝对值是否小于2
}
bool IsBalance()//检查是否为AVL树
{return _IsBalance(_root);
}

2.4 测试代码

void Test_AVLTree()
{const size_t N = 5000;AVLTree<int, int> t;for (size_t i = 0; i < N; ++i){size_t x = rand() + i;t.Insert(make_pair(x, x));}t.InOrder();cout << t.IsBalance() << endl;cout << t.CountHeight() << endl;
}

运行效果:

三、AVL树实现的完整代码

#pragma once
#include<iostream>
#include<cassert>using namespace std;template<class Key,class Val>
class AVLTreeNode
{
public:AVLTreeNode<Key, Val>* _left;AVLTreeNode<Key, Val>* _right;AVLTreeNode<Key, Val>* _parent;//多一个指针指向其父节点,方便我们的后续操作pair<Key, Val> _kv;int _bf;//balance factor(平衡因子)AVLTreeNode(const pair<Key, Val>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}
};template<class Key, class Val>
class AVLTree
{typedef AVLTreeNode<Key, Val> Node;public:bool Insert(const pair<Key,Val>& kv){Node* cur = _root, * parent = nullptr;while (cur)//找到合适的位置{if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{cout << "插入的值重复" << endl;return false;}}cur = new Node(kv);cur->_parent = parent;//将插入的节点连接上二叉树if (parent == nullptr){_root = cur;}else if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}//向上调整各节点的平衡因子while (parent){if (parent->_left == cur){--parent->_bf;//更新平衡因子}else{++parent->_bf;//更新平衡因子}//根据父节点的平衡因子的情况决定师傅继续向上调整各节点的平衡因子if (parent->_bf == 1 || parent->_bf == -1)//平衡因子为1或1继续向上更新{parent = parent->_parent;cur = cur->_parent;}else if (parent->_bf == 2 || parent->_bf == -2)//平衡因子为2或-2,树的结构不平衡,旋转parent节点所在子树{//旋转parent所在的子树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 if (parent->_bf == 0)//平衡因子为0,插入结束{break;}else//出现其他的情况说明树的根据有问题,报错退出{cout << "树的结构出错了" << endl;assert(0);exit(-1);}}return true;}void InOrder()//中序遍历{_InOrder(_root);cout << endl;}int CountHeight()//计算树的高度{return _CountHeight(_root);}bool IsBalance()//检查是否为AVL树{return _IsBalance(_root);}private:void RotateL(Node* parent)//左单旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;//更新parent的右节点if (subRL)//防止该节点为空{subRL->_parent = parent;//更新subRL的父节点}parent->_parent = subR;//更新parent的父节点subR->_left = parent;//subR的左子树置为parentsubR->_parent = pparent;//更新subR的父节点if (pparent == nullptr)//旋转的是整棵树{_root = subR;//更新根节点}else//将旋转后的子树链接上整个二叉树{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}}subR->_bf = parent->_bf = 0;//更新平衡因子}void RotateR(Node* parent)//右单旋{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;//更新parent的左节点if (subLR)//防止该节点为空{subLR->_parent = parent;//更新subLR的父节点}parent->_parent = subL;//更新parent的父节点subL->_right = parent;//subL的右子树置为parentsubL->_parent = pparent;//更新subL的父节点if (pparent == nullptr)//旋转的是整棵树{_root = subL;//更新根节点}else//将旋转后的子树链接上整个二叉树{if (pparent->_left == parent){pparent->_left = subL;}else{pparent->_right = subL;}}subL->_bf = parent->_bf = 0;//更新平衡因子}void RotateLR(Node* parent)//左右双旋{Node* subL = parent->_left;Node* subLR = subL->_right;int subLR_bf = subLR->_bf;//记录该节点的平衡因子来做旋转结束后的修改平衡因子的依据RotateL(subL);//调用左单旋RotateR(parent);//调用右单旋if (subLR_bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if(subLR_bf == 0){parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else if (subLR_bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else{assert(false);}}void RotateRL(Node* parent)//右左双旋{Node* subR = parent->_right;Node* subRL = subR->_left;int subRL_bf = subRL->_bf;//记录该节点的平衡因子来做旋转结束后的修改平衡因子的依据RotateR(subR);//调用右单旋RotateL(parent);//调用左单旋if (subRL_bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (subRL_bf == 0){parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else if (subRL_bf == 1){parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else{assert(false);}}void _InOrder(Node* root){if (root == NULL)//如果是空树就直接结束{return;}_InOrder(root->_left);//先递归遍历其左子树cout << root->_kv.first << " ";//再遍历其根节点_InOrder(root->_right);//最后递归遍历其右子树}int _CountHeight(Node* root)//计算树的高度{if (root == nullptr)return 0;int leftnum = _CountHeight(root->_left);int rightnum = _CountHeight(root->_right);return leftnum > rightnum ? leftnum + 1 : rightnum + 1;}bool _IsBalance(Node* root){if (root == nullptr)return true;int leftH = _CountHeight(root->_left);int rightH = _CountHeight(root->_right);if (rightH - leftH != root->_bf)//检查当前节点平衡因子是否正确{cout << root->_kv.first << "节点平衡因子异常" << endl;return false;}return abs(leftH - rightH) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);//检查左右子树高度之差的绝对值是否小于2}
private:AVLTreeNode<Key, Val>* _root = nullptr;
};

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

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

相关文章

JavaEE 多线程

JavaEE 多线程 文章目录 JavaEE 多线程引子多线程1. 特性2. Thread类2.1 概念2.2 Thread的常见构造方法2.3 Thread的几个常见属性2.4 启动一个线程2.5 中断一个线程2.6 等待一个线程2.7 获取当前线程引用2.8 休眠当前线程 3. 线程状态 引子 当进入多线程这一块内容时&#xff…

2023-12-03 LeetCode每日一题(可获得的最大点数)

2023-12-03每日一题 一、题目编号 1423. 可获得的最大点数二、题目链接 点击跳转到题目位置 三、题目描述 几张卡牌 排成一行&#xff0c;每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。 每次行动&#xff0c;你可以从行的开头或者末尾拿一张卡牌&#x…

【Python/Java/C++三种语言】20天拿下华为OD笔试之【哈希表】2023B-单词接龙【欧弟算法】全网注释最详细分类最全的华为OD真题题解

文章目录 题目描述与示例题目描述输入描述输出描述示例一输入输出说明 示例二输入输出说明 解题思路代码PythonJavaC时空复杂度 华为OD算法/大厂面试高频题算法练习冲刺训练 题目描述与示例 题目描述 单词接龙的规则是&#xff1a; 可用于接龙的单词首字母必须要前一个单词的…

虚拟机扩容磁盘/dev/mapper/centos-root

虚拟机扩容磁盘/dev/mapper/centos-root 1. 在虚拟机管理界面增加磁盘大小或添加磁盘 2. 扩容操作 lsblk -l # 下面/dev/sdb 每个人的可能不同&#xff0c;有/dev/sda 或 /dev/sdc&#xff0c;根据实际更改 fdis /dev/sdb m n p 1 回车 回车 wfdisk -l pvdisplay pvcrea…

Vue.js中v-if 和 v-show 的区别

1、v-if: v-if 指令是 Vue.js 中的一个条件渲染指令。 当 v-if 的条件为 false 时&#xff0c;元素及其内容将从 DOM 中完全移除。 适用于你期望大部分时间条件为 false&#xff0c;或者当你希望通过完全移除元素来节省资源时。 2、v-show: v-show 指令同样也是一个条件渲染…

信息流广告行为兴趣定向底层逻辑算法

行为兴趣定向 1: 行为兴趣的背后是计划的数据 行为是用户在平台的动作&#xff1a;点赞、评论、分享、点击、下单、成交等&#xff0c;用户发生过的标签 兴趣不一定发生&#xff0c;我有打高尔夫的兴趣&#xff0c;但是从来没打过&#xff0c;因为穷 系统会根据用户的行为标…

C++STL中的string容器

string 容器基本概念 C 风格字符串 ( 以空字符结尾的字符数组 ) 太过复杂难于掌握&#xff0c;不适合大程序的开发&#xff0c; 所以 C 标准库定义了一种 string 类&#xff0c;定义在头文件。 String 和 c 风格字符串对比&#xff1a; u Char*是一个指针&#xff0c; …

【C++ Primer Plus学习记录】第5章编程练习

1.编写一个要求用户输入两个整数的程序。该程序将计算并输出这两个整数之间&#xff08;包括这两个整数&#xff09;所有整数的和。这里假设先输入较小的整数。例如&#xff0c;如果用户输入的是2和9&#xff0c;则程序将指出2~9之间所有整数的和为44。 //5.9 1 #if 1 #includ…

使用OpenMVS重建模型

1、数据格式转换 首先将生成的稠密点云以及图片信息转换成openmvs支持的.mvs文件。在openmvs_sample中的bin文件内打开终端 作者&#xff1a;舞曲的小水瓶 https://www.bilibili.com/read/cv25019877/ 出处&#xff1a;bilibili interfaceCOLMAP.exe -i D:\desktop\test\toy\…

【Linux服务器Java环境搭建】05 Node JS安装及环境变量配置

【Linux服务器Java环境搭建】01购买云服务器以及在服务器中安装Linux系统 【Linux服务器Java环境搭建】02 通过xftp和xshell远程连接云服务器 【Linux服务器Java环境搭建】03 Git工具安装 【Linux服务器Java环境搭建】04 JDK安装&#xff08;JAVA环境安装&#xff09; 【Linux服…

flink源码分析 - 命令行参数解析-CommandLineParser

flink版本: flink-1.11.2 调用位置: org.apache.flink.runtime.entrypoint.StandaloneSessionClusterEntrypoint#main 代码位置: flink核心命令行解析器: org.apache.flink.runtime.entrypoint.parser.CommandLineParser /** Licensed to the Apache Software Foundati…

基于OpenAPI工具包以及LSTM的CDN网络流量预测

基于LSTM的CDN网络流量预测 本案例是基于英特尔CDN以及英特尔 OpenAPI Intel Extension for TensorFlow* Intel oneAPIDPC Library 的网络流量预测&#xff0c;CDN是构建在现有网络基础之上的智能虚拟网络&#xff0c;目的是将源站内容分发至最接近用户的节点&#xff0c;使用…

unity学习笔记17

一、动画组件 Animation Animation组件是一种更传统的动画系统&#xff0c;它使用关键帧动画。你可以通过手动录制物体在时间轴上的变换来创建动画。 一些重要的属性&#xff1a; 1. 动画&#xff08;Animation&#xff09;&#xff1a; 类型&#xff1a; Animation组件允许…

java为什么要设计8个基本数据类型的封装类型?

Java中的基本数据类型包括byte、short、int、long、float、double、boolean和char。然而&#xff0c;这些基本数据类型并非对象&#xff0c;他们只是简单的数值&#xff0c;无法调用方法。 为了能在Java这种面向对象的语言中更好地操作这些数值&#xff0c;Java设计了对应的8个…

换股解套策略

在股市中&#xff0c;投资者难免会遇到被套的情况。面对这种情况&#xff0c;如何进行换股策略以降低损失并寻求反弹的机会呢&#xff1f;本文将为您详细解析。 一、了解被套的原因 在进行换股策略之前&#xff0c;首先要了解被套的原因。一般来说&#xff0c;被套的原因有以下…

使用Prometheus监控Padavan路由器

Prometheus监控Padavan路由器 1、背景 近期在Synology&#xff08;群辉&#xff09;中安装一套Prometheus监控程序&#xff0c;目前已经监控Synology&#xff0c;然后家中有有路由器&#xff08;Padavan&#xff09;型号&#xff0c;也准备使用PrometheusGrafan进行监控。 ‍…

1、STM32F407 LED Demo

#ifndef、#define、#endif格式条件编译&#xff0c;作用是避免头文件内容比重复定义 main.c #include "stm32f4xx.h" #include "led.h" #include "delay.h" //CPU主时钟168MHz int main(void) {delay_init(168);LED_Init();while(1){GPIO_SetB…

Python 读取电子发票PDF 转成Excel

Python 读取电子发票PDF 转成Excel 目录 0.前提 1.python相关的处理PDF的库 2.实际好用的 3.实际代码 4.思考 0.前提 只识别普通电子发票PDF&#xff0c;提取其中某些关键内容到excel中。 1.python相关的处理PDF的库 如下4个库是经常更新维护的&#xff01; pyP…

采集工具-免费采集器下载

在当今信息时代&#xff0c;互联网已成为人们获取信息的主要渠道之一。对于研究者和开发者来说&#xff0c;如何快速准确地采集整个网站数据是至关重要的一环。以下将从九个方面详细探讨这一问题。 确定采集目标 在着手采集之前&#xff0c;明确目标至关重要。这有助于确定采集…

企业数字化的思考

1. 企业信息化 1.1 从0到1构建信息系统 随着it基础的不断成熟与在企业业务中的应用&#xff0c;企业构建专业化的信息系统已不再需要太多的讨论&#xff0c;基本都在基于自身的阶段构建各种各样的业务支撑系统&#xff0c;从OA\CRM\财务系统\HR\ERP\SAP等到类似更为专项的合同…