目录

一、概念

二、AVL树的模拟实现

2.1 AVL树节点定义

2.2 AVL树的基本结构 

2.3 AVL树的插入

1. 插入步骤

2. 调节平衡因子

3. 旋转处理 

 4. 开始插入

2.4 AVL树的查找

2.5 AVL树的删除

1. 删除步骤

2. 调节平衡因子

3. 旋转处理

4. 开始删除

结语


一、概念

二叉搜索树查找效率为O(logN),但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率变为O(N),效率低下。

因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新节点后,如果能保证每个节点的左右子树高度之差的绝对值不超过1(超过则需要对树中的节点进行调整),即可降低树的高度,从而减少平均搜索长度。

AVL树具有以下特点:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/01)

平衡因子的取值由自己规定,在这里为了模拟实现,规定左子树的高度在根结点中为负值,右子树的高度在根结点中为正值,即平衡因子= 右子树高度 - 左子树高度

二、AVL树的模拟实现

2.1 AVL树节点定义

template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;    // 该节点的左孩子AVLTreeNode<K, V>* _right;   // 该节点的右孩子AVLTreeNode<K, V>* _parent;  // 该节点的父结点pair<K, V> _kv;int _bf;                     // 该节点的平衡因子AVLTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}
};

2.2 AVL树的基本结构 

template <class K, class V>
class AVLTree
{typedef AVTreeNode<K, V> Node;
public:bool Insert(const std::pair<K, V>& kv);bool Find(const K& key);bool Erase(const K& key);private:Node* _root = nullptr;
};

2.3 AVL树的插入

1. 插入步骤

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

2. 调节平衡因子

插入节点后,会影响该节点到根节点这一条路径上节点的平衡因子,因此需要进行迭代,将这一条路径上的平衡因子更新。

步骤:

第一步: 对插入的节点的父节点进行更新,会发生两种情况:

  1. 如果插入的是根节点,则无父节点,不用更新。
  2. 如果插入的是父节点的左节点,则父节点的平衡因子-1;如果插入的是父节点的右节点,则父节点的平衡因子+1。

第二步: 父节点的平衡因子在插入节点后会有三种变化,也对应着三种措施:

  1. 父节点的平衡因子变为0,则说明在插入前两颗子树高度相差1,插入后整体的高度没有发生变化,不需要向上继续调整。
  2. 父节点的平衡因子变为-1或1,则说明在插入前两颗子树高度相等,插入后整体的高度+1,需要对父节点的父节点的平衡因子进行更新,这样就回到了步骤一。
  3. 父节点的平衡因子变为-2或2,则说明在插入前两个子树高度相差1,且插入的节点为高度较高的那颗子树的叶子节点,这导致了高度相差变为2,需要进行旋转处理,降低树的高度,从而达到平衡整颗树的要求。

图解:

第一种情况:

第二种情况:

可以看到这里在更新平衡因子的时候,只会影响节点到根节点路径上的节点的平衡因子,其它的节点不需要考虑。

第三种情况:

更新平衡因子的时候,发现不满足规则后,直接停止更新,转而进行旋转处理。

3. 旋转处理 

旋转有四种情况:

第一种情况:右单旋

新节点插入到较高左子树的左侧,左侧的高度变高需要将右边的调整下来,因此叫做右单旋。

具体操作:

让subL的右孩子成为parent的左孩子,然后让parent成为subL的右孩子,最后把两个节点的平衡因子修改为0。

 图解:

实现代码:

void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;// 1.先让把subL的右边作为parent的左边parent->_left = subLR;// 2.如果subLR不为空,就让subLR的父指针指向parentif (subLR) subLR->_parent = parent;// 3.记录parent的父节点的位置,然后把parent作为subL的右边Node* pParent = parent->_parent;subL->_right = parent;// 4.parent的父指针指向subLparent->_parent = subL;// 5.如果ppNode为空,说明subR现在是根节点,就让subL的父指针指向nullptr//   不是根节点就把subL的父指针指向parent的父节点,parent的父节点(左或右)指向subLif (pParent == nullptr){// 更新根节点_root = subL;subL->_parent = nullptr;}else{// 判断parent是pparent的左还是右if (pParent->_left == parent)pParent->_left = subL;elsepParent->_right = subL;subL->_parent = pParent;}// 6.把parent和subL的平衡因子更新为0subL->_bf = parent->_bf = 0;
}

第二种情况:左单旋 

新节点插入到较高右子树的右侧,右侧的高度变高需要将左边的调整下来,因此叫做右单旋。

 具体操作:

让subR的左孩子成为parent的右孩子,然后让parent成为subR的左孩子,最后把两个节点的平衡因子修改为0。

图解:

实现代码:

void RotateL(Node* parent)
{		Node* subR = parent->_right;Node* subRL = subR->_left;// 1.先让把subR的左边作为parent的右边parent->_right = subRL;// 2.如果subRL不为空,就让subRL的父指针指向parentif (subRL) subRL->_parent = parent;// 3.先记录parent的父节点的位置,然后把parent作为subR的左边 Node* pParent = parent->_parent;subR->_left = parent;// 4.parent的父指针指向subRparent->_parent = subR;// 5.如果ppNode为空,说明subR现在是根节点,就让subR的父指针指向nullptr//   不是根节点就把subR的父指针指向parent的父节点,parent的父节点(左或右)指向subRif (pParent == nullptr){// 更新根节点_root = subR;subR->_parent = nullptr;}else{// 判断parent是ppNode的左还是右if (pParent->_left == parent)pParent->_left = subR;elsepParent->_right = subR;subR->_parent = pParent;}// 6.把parent和subR的平衡因子更新为0subR->_bf = parent->_bf = 0;
}

第三种情况:左右双旋 

新节点插入在较高左子树右侧,这里和第一种情况的区别就是前者是直线,后者是折线

具体操作:

先对subL进行一个左单旋,然后对parent进行右单旋,修改平衡因子,有三种改法。三个节点从左至右的三个节点一次是:subL、subLR和parent。
如果subLR的平衡因子为0,就将它们依次改为0,0, 0;
如果subLR的平衡因子为1,就将它们依次改为-1,0, 0;
如果subLR的平衡因子为-1,就将它们依次改为0,0, 1。

图解:(这里只画出了一种情况,剩下的类似)

实现代码:

void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;// 保留subLR的平衡因子的值,方便知道新插入的节点是在subLR的左子树还是右子树// 旋转 先对subL进行左旋转,再对parent进行右旋转RotateL(subL);RotateR(parent);// 从左到右 subL subLR parentif (bf == -1)// subLR的左子树  bf: 0 0 1{subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else if (bf == 1)// subLR的右子树 bf: -1 0 0{subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}
}

第四种情况:右左单旋 

新节点插入在较高右子树左侧,这里和第二种情况的区别就是前者是直线,后者是折线

 具体操作:

先对subR进行一个右单旋,然后对parent进行左单旋,修改平衡因子,有三种改法。三个节点从左至右的三个节点一次是:parent、subRL和subR。
如果subRL的平衡因子为0,就将它们依次改为0,0, 0;
如果subRL的平衡因子为1,就将它们依次改为-1,0, 0;
如果subRL的平衡因子为-1,就将它们依次改为0,0, 1。

图解:(这里只画出了一种情况,剩下的类似)

实现代码:

void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;// 保留subRL的平衡因子的值,方便知道新插入的节点是在subRL的左子树还是右子树// 旋转 先对subR进行右旋转,再对parent进行左旋转RotateR(subR);RotateL(parent);// 从左到右 parent subRL subRif (bf == -1)// subRL的左子树  bf: 0 0 1{parent->_bf = 0;subRL->_bf = 0;subR->_bf = 1;}else if (bf == 1)// subRL的右子树 bf: -1 0 0{parent->_bf = -1;subRL->_bf = 0;subR->_bf = 0;}else if (bf == 0){parent->_bf = 0;subRL->_bf = 0;subR->_bf = 0;}
}

 4. 开始插入

bool Insert(const pair<K, V>& kv)
{// 先按照二叉搜索数一样插入元素// 无节点是插入if (_root == nullptr){_root = new Node(kv);return true;}// 有节点时插入Node* parent = nullptr;Node* cur = _root;while (cur){parent = cur;// 小于往左走if (kv.first < cur->_kv.first){cur = cur->_left;}// 大于往右走else if (kv.first > cur->_kv.first){cur = cur->_right;}else{// 找到了,就返回falsereturn false;}}cur = new Node(kv);// 判断cur应该插在parent的左还是右 // 小于在左,大于在右		if (cur->_kv.first < parent->_kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}// 更新parent的平衡因子// 节点的插入只会影响cur的祖先的平衡因子(不是所有的,是一部分,分情况)while (parent){// 更新parent的平衡因子// cur在parent的左,parent->_bf--// cur在parent的右,parent->_bf++if (cur == parent->_left)parent->_bf--;elseparent->_bf++;// bf 可能为 -2、-1、0、1、2if (parent->_bf == 0){// 对上层无影响,退出break;}else if (parent->_bf == -1 || parent->_bf == 1){// 对上层有影响,迭代更新cur = parent;parent = parent->_parent;}else{// 平衡树出现了问题,需要调整// 1.右边高,左旋转调整if (parent->_bf == 2){// 如果是一条直线==>左旋转即可// 如果是一条折线==>右左旋转if (cur->_bf == 1)RotateL(parent);else if (cur->_bf == -1)RotateRL(parent);}// 2.左边高,右旋转调整else if (parent->_bf == -2){// 如果是一条直线==>右旋转即可// 如果是一条折线==>左右旋转if (cur->_bf == -1)RotateR(parent);else if (cur->_bf == 1)RotateLR(parent);}// 调整后是平衡树,bf为0,不需要调整了break;}}return bool;
}

2.4 AVL树的查找

与二叉搜索树的过程一致,这里就不多解释了,直接上代码:

bool Find(const K& key)
{if (_root == nullptr)return false;Node* cur = _root;while (cur){// 小于往左走if (key < cur->_kv.first){cur = cur->_left;}// 大于往右走else if (key > cur->_kv.first){cur = cur->_right;}else{// 找到了return true;}}return false;
}

2.5 AVL树的删除

1. 删除步骤

  1. 我们先按照二叉搜索树树删除节点的方式,删除节点。
  2. 根据对应删除情况更新平衡因子,更新平衡因子的方法与插入的更新方法是相反的。

2. 调节平衡因子

步骤:

第一步:

  1. 删除节点后,如果删除的节点为根节点,就结束。
  2. 如果删除节点是父节点的左孩子,那么父节点的平衡因子加1;删除节点是父节点的右孩子,那么父节点的平衡因子减1。

第二步:父节点的平衡因子在插入节点后会有三种变化,也对应着三种措施:

  1. 父节点的平衡因子变为0,则说明删除前父亲的平衡因子为1或-1,多出一个左节点或右节点,删除节点后,左右高度相等,整体高度减1,对上层有影响,需要继续调节
  2. 父节点的平衡因子变为-1或1,则说明删除前父亲的平衡因子为0,左右高度相等,删除节点后,少了一个左节点或右节点,但是整体高度不变,对上层无影响,不需要继续调节
  3. 父节点的平衡因子变为-2或2,则说明删除前父亲的平衡因子为-1或1,多了一个左节点或一个右节点,删除了一个右节点或左节点,此时多了两个左节点和右节点,这棵子树一边已经被拉高了,此时这棵子树不平衡了,需要旋转处理

3. 旋转处理

这里的旋转与插入时旋转的没有区别,就不详细解答了,直接上代码。

4. 开始删除

bool Erase(const K& key)
{if (_root == nullptr)return false;// 有节点时插入Node* parent = nullptr;Node* cur = _root;while (cur){// 小于往左走if (key < cur->_kv.first){parent = cur;cur = cur->_left;}// 大于往右走else if (key > cur->_kv.first){parent = cur;cur = cur->_right;}else{// 找到了// 1.左边为空,把parent指向cur的右// 2.右边为空,把parent指向cur的左// 3.左右都不为空,用右子树的最左边的节点的值替换要删除的节点,然后转换为用1的情况删除该节点if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;delete cur;break;}else{if (parent->_left == cur){parent->_left = cur->_right;parent->_bf++;}else{parent->_right = cur->_right;parent->_bf--;}}if (parent->_bf != -1 && parent->_bf != 1) UpdateBf(parent);delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;delete cur;break;}else{if (parent->_left == cur){parent->_left = cur->_left;parent->_bf++;}else{parent->_right = cur->_left;parent->_bf--;}}if (parent->_bf != -1 && parent->_bf != 1) UpdateBf(parent);delete cur;}else{Node* rightMinParent = cur;Node* rightMin = cur->_right;// 先去右子树while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;// 一种往左走}cur->_kv = rightMin->_kv;// 替代删除// 删除minNode  第一种情况:左节点为空if (rightMinParent->_left == rightMin){rightMinParent->_left = rightMin->_right;rightMinParent->_bf++;}else{rightMinParent->_right = rightMin->_right;rightMinParent->_bf--;}if (rightMinParent->_bf != -1 && rightMinParent->_bf != 1)                 UpdateBf(rightMinParent);delete rightMin;}return true;}}return false;
}void UpdateBf(Node* parent)
{if (parent == nullptr)return;Node* cur = parent;goto first;while (parent){// 更新parent的平衡因子// cur在parent的左,parent->_bf++// cur在parent的右,parent->_bf--if (cur == parent->_left)parent->_bf++;elseparent->_bf--;// bf 可能为 -2、-1、0、1、2first:if (parent->_bf == 0){// 对上层有影响,迭代更新// 如果parent是根节点就结束if (parent->_parent == nullptr)break;cur = parent;parent = parent->_parent;}else if (parent->_bf == -1 || parent->_bf == 1){// 对上层无影响,退出break;}else{// 平衡树出现了问题,需要调整// 1.右边高,左旋转调整if (parent->_bf == 2){// 如果是一条直线==>左旋转即可// 如果是一条折线==>右左旋转if (parent->_right->_bf == 1){RotateL(parent);cur = parent->_parent;parent = cur->_parent;continue;}else if (parent->_right->_bf == -1)// 调整后 p sL s  如果sL是1或-1可以退出 {Node* s = parent->_right;Node* sL = s->_left;RotateRL(parent);// 不平衡向上调整if (sL->_bf != 1 && sL->_bf != -1){cur = sL;parent = cur->_parent;continue;}}else if (parent->_right->_bf == 0)    // 平衡因子修改{RotateL(parent);parent->_bf = 1;parent->_parent->_bf = -1;}}// 2.左边高,右旋转调整else if (parent->_bf == -2){// 如果是一条直线==>右旋转即可// 如果是一条折线==>左右旋转if (parent->_left->_bf == -1){RotateR(parent);cur = parent->_parent;parent = cur->_parent;continue;    //parent不是-1或1就继续}else if (parent->_left->_bf == 1)// 调整后 s sR p  如果sL是1或-1可以退出{Node* s = parent->_left;Node* sR = s->_right;RotateLR(parent);// 不平衡向上调整 为0时如果parent为根//if (sR->_bf != 1 && sR->_bf != -1){cur = sR;parent = cur->_parent;continue;}}else if (parent->_left->_bf == 0)    // 平衡因子修改{RotateR(parent);parent->_parent->_bf = 1;parent->_bf = -1;}}// 调整后是平衡树,bf为1或-1,不需要调整了break;}}
}

结语

上面这些是AVL树的大致内容,其中旋转是有些难的地方,但是面试会考察,需要着重掌握,而删除是二叉搜索树的删除加上旋转的叠加,难度更上一层了,这里如果没能理解,可以自己画一画图,并且配合着插入的图来分析,应该会有所帮助。

下一篇将会介绍二叉搜索树的另一种改良:红黑树,有兴趣的朋友可以关注一下。

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

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

相关文章

char32_t、char16_t、wchar_t 用于 c++ 语言里存储 unicode 编码的字符,给出它们的具体定义

&#xff08;1&#xff09; #include <iostream> #include <string>int main() { std::u16string s u"C11 引入 char16_t"; // 定义 UTF-16 字符串for (char16_t c : s) // 遍历输出每个 char16_t 的值std::cout << std::hex << (…

redis数据类型-基数统计HyperLogLog

redis数据类型-基数统计HyperLogLog 文档 redis单机安装redis常用的五种数据类型redis数据类型-位图bitmap 说明 官网操作命令指南页面&#xff1a;https://redis.io/docs/latest/commands/?nameget&groupstringHyperLogLog介绍页面&#xff1a;https://redis.io/docs…

逻辑思维:从混沌到秩序的理性推演在软件开发中的应用

引言 在软件开发的过程中&#xff0c;逻辑思维就像是开发者的“GPS导航”&#xff0c;帮助我们从混沌的需求中找到清晰的解决方案。想象一下&#xff0c;如果没有逻辑思维&#xff0c;我们可能会在需求的海洋中迷失方向&#xff0c;最终写出一堆“看似聪明但毫无意义”的代码。…

Spring AI Alibaba Graph基于 ReAct Agent 的天气预报查询系统

1、在本示例中&#xff0c;我们仅为 Agent 绑定了一个天气查询服务&#xff0c;接收到用户的天气查询服务后&#xff0c;流程会在 AgentNode 和 ToolNode 之间循环执行&#xff0c;直到完成用户指令。示例中判断指令完成的条件&#xff08;即 ReAct 结束条件&#xff09;也很简…

HCIP(综合实验2)

1.实验拓补图 2.实验要求 1.根据提供材料划分VLAN以及IP地址&#xff0c;PC1/PC2属于生产一部员工划分VLAN10,PC3属于生产二部划分VLAN20 2.HJ-1HJ-2交换机需要配置链路聚合以保证业务数据访问的高带宽需求 3.VLAN的放通遵循最小VLAN透传原则 4.配置MSTP生成树解决二层环路问题…

使用 rebase 轻松管理主干分支

前言 最近遇到一个技术团队的 dev 环境分支错乱&#xff0c;因为是多人合作大家各自提交信息&#xff0c;导致出现很多交叉合并记录&#xff0c;让对应 log 看起来非常混乱&#xff0c;难以阅读。 举例说明 假设我们有一个项目&#xff0c;最初develop分支有 3 个提交记录&a…

使用openssl为localhost创建自签名

文章目录 自签名生成命令安装安装证书浏览器证书管理器 自签名 生成命令 使用openssl生成私钥和证书。 openssl req -x509 -newkey rsa:4096 -nodes -days 365 -subj "/CNlocalhost" -addext "subjectAltNameDNS:localhost" -keyout cert.key -out cer…

AI编程助手Cline之快速介绍

Cline 是一款深度集成在 Visual Studio Code&#xff08;VSCode&#xff09; 中的开源 AI 编程助手插件&#xff0c;旨在通过结合大语言模型&#xff08;如 Claude 3.5 Sonnet、DeepSeek V3、Google Gemini 等&#xff09;和工具链&#xff0c;为开发者提供自动化任务执行、智能…

1.微服务拆分与通信模式

目录 一、微服务拆分原则与策略 业务驱动拆分方法论 • DDD&#xff08;领域驱动设计&#xff09;中的限界上下文划分 • 业务功能正交性评估&#xff08;高内聚、低耦合&#xff09; 技术架构拆分策略 • 数据层拆分&#xff08;垂直分库 vs 水平分表&#xff09; • 服务粒…

Element Plus表格组件深度解析:构建高性能企业级数据视图

一、架构设计与核心能力 Element Plus的表格组件&#xff08;el-table&#xff09;基于Vue 3的响应式系统构建&#xff0c;通过声明式配置实现复杂数据渲染。其核心设计理念体现在三个层级&#xff1a; 数据驱动&#xff1a;通过data属性绑定数据源&#xff0c;支持动态更新与…

07前端项目----面包屑

面包屑 效果实现代码全局事件总线-$bus 效果 实现代码 上节searchParams中参数categoryName是表示一二三级分类所点击的列表名 <!--bread面包屑--> <div class"bread"><ul class"fl sui-breadcrumb"><li><a href"#"…

kafka jdbc connector适配kadb数据实时同步

测试结论 源端增量获取方式包括&#xff1a;bulk、incrementing、timestamp、incrementingtimestamp&#xff08;混合&#xff09;&#xff0c;各种方式说明如下&#xff1a; bulk: 一次同步整个表的数据 incrementing: 使用严格的自增列标识增量数据。不支持对旧数据的更新…

基于Hadoop的音乐推荐系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 本毕业生数据分析与可视化系统采用B/S架构&#xff0c;数据库是MySQL&#xff0c;网站的搭建与开发采用了先进的Java语言、爬虫技术进行编写&#xff0c;使用了Spring Boot框架。该系统从两个对象&#xff1a;由管理员和用户来对系统进行设计构建。主要功能包括&#xff…

CentOS的安装以及网络配置

CentOS的下载 在学习docker之前&#xff0c;我们需要知道的就是docker是运行在Linux内核之上的&#xff0c;所以我们需要Linux环境的操作系统&#xff0c;当然了你也可以选择安装ubuntu等操作系统&#xff0c;如果你不想在本机安装的话还可以考虑买阿里或者华为的云服务器&…

【条形码识别改名工具】如何批量识别图片条形码,并以条码内容批量重命名,基于WPF和Zxing的开发总结

批量图片条形码识别与重命名系统 (WPF + ZXing)开发总结 项目适用场景 ​​电商商品管理​​:批量处理商品图片,根据条形码自动分类归档​​图书馆系统​​:扫描图书条形码快速建立电子档案​​医疗档案管理​​:通过药品条形码整理医疗图片资料​​仓储管理​​:自动化识…

RAGFlow安装+本地知识库+踩坑记录

RAGFlow是一种融合了数据检索与生成式模型的新型系统架构&#xff0c;其核心思想在于将大规模检索系统与先进的生成式模型&#xff08;如Transformer、GPT系列&#xff09;相结合&#xff0c;从而在回答查询时既能利用海量数据的知识库&#xff0c;又能生成符合上下文语义的自然…

android liveData observeForever 与 observe对比

LiveData 是一个非常有用的组件,用于在数据变化时通知观察者。LiveData 提供了两种主要的观察方法:observe 和 observeForever。这两种方法在使用场景、生命周期感知以及内存管理等方面有所不同。 一、observe 方法​​ ​​1. 基本介绍​​ ​​生命周期感知​​:observe…

web-ssrfme

一、题目源码 <?php highlight_file(__file__); function curl($url){ $ch curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_HEADER, 0);echo curl_exec($ch);curl_close($ch); }if(isset($_GET[url])){$url $_GET[url];if(preg_match(/file…

企业AI应用模式解析:从本地部署到混合架构

在人工智能快速发展的今天&#xff0c;企业如何选择合适的大模型应用方式成为了一个关键问题。本文将详细介绍六种主流的企业AI应用模式&#xff0c;帮助您根据自身需求做出最优选择。 1. 本地部署&#xff08;On-Premise Deployment&#xff09; 特点&#xff1a;将模型下载…

OpenCV 图形API(49)颜色空间转换-----将 NV12 格式的图像数据转换为 BGR 颜色空间函数NV12toBGR()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将图像从NV12&#xff08;YUV420p&#xff09;颜色空间转换为BGR。 该函数将输入图像从NV12颜色空间转换为RGB。Y、U和V通道值的常规范围是0到25…