ffmpeg 二叉树代码测试及分析 - 详解

news/2026/1/22 22:59:02/文章来源:https://www.cnblogs.com/gccbuaa/p/19519230

author: hjjdebug
date : 2025年 11月 20日 星期四 08:57:46 CST
description: ffmpeg 二叉树代码测试及分析


文章目录

  • 1 二叉树的概念.
  • 2 平衡的二叉树.
  • 3 考察3个结点的二叉树
    • 3.1 平衡的二叉树
    • 3.2 不平衡的二叉树
    • 3.3 不平衡2叉数的调整
  • 4 ffmpeg 二叉树测试实例
    • 4.1 执行结果:
  • 5 ffmpeg 二叉树算法代码.
    • 5.1 av_tree_insert() 函数调用参数
    • 5.2 程序技巧

1 二叉树的概念.

二叉树每一个结点有2个孩子结点, 直到叶子结点.
叶子结点有2个指针为空的孩子结点.
左支的孩子结点,其key值小于父结点,右值的孩子结点,其key值大于父结点.

2 平衡的二叉树.

对所有结点,左右子树高度差不超过1 的二叉树叫平衡的二叉树.

3 考察3个结点的二叉树

考察3个结点构成的二叉树. 是的,树的结点数可以很多, 但3个结点是它的基本结构,
搞清楚了3个结点, 就基本搞清楚了树.

3.1 平衡的二叉树

3个结点, 平衡的2叉树结构只有一种. 它们每个结点高度差为0,如图示:
在这里插入图片描述

3.2 不平衡的二叉树

不平衡的2叉数有4种, 如图示. 它们高度差为2,所以是不平衡的.
根据路径,可称之为LL,LR,RL,RR, 其中LL与RR 对称, LR 与 RL 对称
在这里插入图片描述

它们的共同特点是, 首个结点不是中结点,二是大结点或者小结点,不符合平衡树第一个结点是中结点的特点.

3.3 不平衡2叉数的调整

由于LL与RR 对称, LR 与 RL 对称, 所以4种只要搞懂了2种就够了.
LL 型的调整. 只要把中结点向上提一级,让大结点做它的右孩子就可以了, 看起来就像把顶部的大结点向右旋转了一下.
LR 型的调整. 此时的中结点是新加入的结点,它需要提高2级到第1结点位置,然后把小节点作为左支,把大节点作为右支.
看起来已不是简单的旋转了, 不过也仍然只是简单的调整.

4 ffmpeg 二叉树测试实例

ffmpeg 中实现了一种平衡的2叉树(AVL树), 我们看看它的使用方法
它的调用参数有点奇特,是为了减少接口所特意设计的.
测试用例如下. 测试代码把av_tree_insert()函数挪到了本地方便注释,调试

$ cat main.c
#include "libavutil/mem.h"
#include "libavutil/tree.h"
#include <stdint.h>#include <stdio.h>//技巧高能够使代码简洁, 可是阅读起来就比较吃力了, 写代码还是应该以易读为主要原则!!//不过技巧好的也应该阅读typedef struct AVTreeNode{struct AVTreeNode* child[2]; //child[2] 是二叉树节点的标准形式void* elem; // 这里的elem 指针保留的是传入的 data,10,20,30之类的数据int state; // state 保存的是右子树减左子树的差值,层高差值} AVTreeNode;static void print(AVTreeNode* t, int depth){int i;// depth 负责打印缩进,可以分清父节点和子节点for (i = 0; i < depth * 4; i++)printf(" ");if (t){printf("Node %p %2d %ld\n", t, t->state, (long int)t->elem);print(t->child[0], depth + 1);print(t->child[1], depth + 1);}else //节点指针为空时,打印NULLprintf("NULL\n");}// 这里的指针a, 指针b, 就是传入的数据. b是待插入的节点即key 值static int cmp(const void* a, const void* b){return (const uint8_t*)a - (const uint8_t*)b;}void* av_tree_insert(AVTreeNode** tp, void* key,int (*cmp)(const void* key, const void* b), AVTreeNode** next){// *tp, t 是顶部节点指针AVTreeNode* t = *tp;if (t) // top节点不为空的情况,要继续查找位置{ // 例如考虑v 比较为负值,向右child插入的情景unsigned int v = cmp(t->elem, key);void* ret;if (!v) //比较结果为相等{if (*next) // 如果*next 有值,就不用插入了,直接返回t->elem值return t->elem;else if (t->child[0] || t->child[1]){  //如果该节点下有子结点,int i = !t->child[0];//child[0]不为空,则i=0,从左支查找,否则,i=1从右支查找void* next_elem[2];av_tree_find(t->child[i], key, cmp, next_elem);//这里修改了key 和 t->elem,把自己伪装成下一个元素, 后续执行insert从而把该节点删除,并调整了树平衡key = t->elem = next_elem[i];v = -i;}else{ // *next 为空,是要删除包含key的节点,那就把节点返回去*next = t;*tp = NULL; //把节点指针变成NULL,这是节点没有左右子节点的情况return NULL;}}// v>>31 v=正数,v>>31=0, v=负数, v>>31=1, v是比较的返回值// 先考察v为负值,向右边找的情景,递归会找到空子节点ret = av_tree_insert(&t->child[v >> 31], key, cmp, next); // 这里递归调用// 下面代码时递归返回的代码if (!ret){ // 返回NULL 是新节点已插入int i = (v >> 31) ^ !!*next; // 比较为负值且next为空,右子树在增高i=1AVTreeNode** child = &t->child[i]; // child 是我们关注的那个childt->state += 2 * i - 1; // 更新top节点高度差,可能加1或者减1// 判定top 节点是否需要调整if (!(t->state & 1)) // 当t->state 为偶数时{if (t->state)// 当t->state为真时, 例如t->state==2, 就需要调整节点了{//调整节点,我宁愿分四种情况写, 可是它按2种情况混合着写,代码短了点,但很难读//因为首先要确定这个i//问题2: (*child)->state是什么时候改变的?也是在71行t->state处改变的// child 也会在递归中做顶部节点,递归函数好难调试,算法混在一块也不清晰,只是为了简洁if ((*child)->state * 2 == -t->state) //例如RL类型, t->state==2,(*child)->state==-1{  //2叉树是对指针操作的考验!*tp = (*child)->child[i ^ 1]; //child 的左支做顶层,提升2级(*child)->child[i ^ 1] = (*tp)->child[i];(*tp)->child[i] = *child;  //把child 做顶层右支*child = (*tp)->child[i ^ 1]; //顶层左支送child(*tp)->child[i ^ 1] = t; //旧顶层做新顶层左支//调整新顶层及其2个child 的层高差. 写法有点难读!(*tp)->child[0]->state = -((*tp)->state > 0);(*tp)->child[1]->state = (*tp)->state < 0;(*tp)->state = 0;}else{ // 例如单调递增代码,i为1, RR调整//						例如10,20,30中,10为t,20为child,30为child[1], *tp为新top,指向20*tp = *child; // child 提高1层, 送新top*child = (*child)->child[i ^ 1]; // child 左支送child(*tp)->child[i ^ 1] = t; // 原来的top送新top(*tp) 的左支//更新stateif ((*tp)->state) // 根据新tp, 调整原始top的statet->state = 0;elset->state >>= 1;(*tp)->state = -t->state; // 设置新top的state}}}//比较的是(*tp)->state 布尔值与 *next 布尔值相同返回1,例如state==0,*next==0if (!(*tp)->state ^ !!*next)return key; // 从这里返回,代表什么? 代表不用调整节点}return ret; // 这个ret 是递归 av_tree_insert 的 ret; 如果为空需要计算层差并判断是否需要调整平衡}else // 递归后总能找到空子节点 *tp==0{*tp = *next; // 新节点接到它的空节点处*next = NULL; //*next 指针赋空表示已使用,并返回NULLif (*tp){(*tp)->elem = key; // 并把key值赋值给top节点, 如果不是一招2用,本来上层应该直接赋值的.return NULL;}elsereturn key; // 返回是树中节点保留的数值}}// 传入整数数据, 把数据挂到树上, 保持树的平衡.void testTree(int* data, int length){AVTreeNode *root = NULL, *node = NULL;int i;for (i = 0; i < length; i++){printf("inserting  %4d\n", data[i]);// 分配一个结点if (!node)node = av_tree_node_alloc();if (!node){printf("Memory allocation failure.\n");return;}// 把数据插入到结点上// 这个接口有点怪! 它没有直接把data[i] 赋值给node->element, 而是分开传.反人类.// 为什么这样呢? 因为这个insert 也是delete接口, 当node=NULL时, 要delete 数值是data[i]的节点av_tree_insert(&root, (void*)(long)data[i], cmp, &node);// 第一次root为空, 插完第一个节点, root 指针改变为第一个节点地址print(root, 0); // 打印整个树}av_free(node);av_tree_destroy(root);}void testRemove(){AVTreeNode *root = NULL;AVTreeNode *node = av_tree_node_alloc();int i=50;av_tree_insert(&root, (void*)(long)i, cmp, &node);if(node==NULL)node=av_tree_node_alloc();int j=100;printf("adding %4d\n", j);av_tree_insert(&root, (void *)(long)(j), cmp, &node);printf("removing %4d\n", i);AVTreeNode *node2 = NULL;av_tree_insert(&root, (void *)(long)(i), cmp, &node2);void *  k = av_tree_find(root, (void *)(long)i, cmp, NULL);if (k)printf("removal failure %d\n", j);av_free(node2);}int RR_data[] = { 10, 20, 30, 40, 50 }; // 数据是精心准备的,这是单调递增,必需要调整树,左旋使平衡int RR_data_size = sizeof(RR_data) / sizeof(RR_data[0]);int RL_data[] = { 10, 100, 20, 150, 110 }; // 数据是精心准备的,这是波浪递增,必需要调整树使平衡int RL_data_size = sizeof(RL_data) / sizeof(RL_data[0]);int main(){testTree(RR_data, RR_data_size); // 测试单调递增//    testTree(RL_data, RL_data_size); //测试波浪递增//	testRemove();return 0;}

4.1 执行结果:

单调递增测试:

$ ./t2
inserting    10
Node 0x5da73a56e040  0 10
NULL
NULL
inserting    20
Node 0x5da73a56e040  1 10
NULL
Node 0x5da73a56e0c0  0 20
NULL
NULL
inserting    30
Node 0x5da73a56e0c0  0 20
Node 0x5da73a56e040  0 10
NULL
NULL
Node 0x5da73a56e140  0 30
NULL
NULL
inserting    40
Node 0x5da73a56e0c0  1 20
Node 0x5da73a56e040  0 10
NULL
NULL
Node 0x5da73a56e140  1 30
NULL
Node 0x5da73a56e1c0  0 40
NULL
NULL
inserting    50
Node 0x5da73a56e0c0  1 20
Node 0x5da73a56e040  0 10
NULL
NULL
Node 0x5da73a56e1c0  0 40
Node 0x5da73a56e140  0 30
NULL
NULL
Node 0x5da73a56e240  0 50
NULL
NULL

波浪递增测试:

./t2
inserting    10
Node 0x57ec61470040  0 10
NULL
NULL
inserting   100
Node 0x57ec61470040  1 10
NULL
Node 0x57ec614700c0  0 100
NULL
NULL
inserting    20
Node 0x57ec61470140  0 20
Node 0x57ec61470040  0 10
NULL
NULL
Node 0x57ec614700c0  0 100
NULL
NULL
inserting   150
Node 0x57ec61470140  1 20
Node 0x57ec61470040  0 10
NULL
NULL
Node 0x57ec614700c0  1 100
NULL
Node 0x57ec614701c0  0 150
NULL
NULL
inserting   110
Node 0x57ec61470140  1 20
Node 0x57ec61470040  0 10
NULL
NULL
Node 0x57ec61470240  0 110
Node 0x57ec614700c0  0 100
NULL
NULL
Node 0x57ec614701c0  0 150
NULL
NULL

5 ffmpeg 二叉树算法代码.

ffmpeg 中的代码简洁干练,所以我特别标注了它的insert算法. 其它函数比较简单
把av_tree_insert() 挪到了用户的代码空间, 方便调试和注释.

5.1 av_tree_insert() 函数调用参数

void* av_tree_insert(AVTreeNode** tp, void* key,
int (cmp)(const void key, const void* b), AVTreeNode** next)

第一参数: top顶层node指针的地址, 意味着执行完插入,这个地址可能会改变.
第二参数: 插入的数据,
第三参数: 用户自定义的比较算法,传参a,是内部节点的数据, 传参b,就是用户待插入的数据key了.
第四参数: 这是个容易误解的东西. 如果next 为真, 是把key 插入到树,
如果
next为NULL,是把包含key值的节点从树中删除.

5.2 程序技巧

  1. 删除节点时, 如果该节点有子节点, 它把自己的值先改成叶子结点的值,把key值也改成叶子节点的值,
    继续执行av_tree_insert, 引起比较返回0值,从而删除该节点并调整了树平衡.
  2. 维持树平衡代码它把4种情况按2种情况来写,用变量i来区分左右,代码简洁但逻辑有点难读.
    除了看代码注释还是要调试才能理解代码. 标注信息都写在测试代码中了,就不再这里赘述了.

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

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

相关文章

Java毕设选题推荐:基于springboot的企业智慧知识产权资产运营平台企业内部知识产权管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

2026执业药师考试培训前十机构测评:通关攻略与避坑指南

2026执业药师考试培训前十机构测评:通关攻略与避坑指南一、引言:为什么选择专业机构备考执业药师? 在医药行业中,执业药师资格证的重要性不言而喻,它就像是一块 “黄金敲门砖”,为从业者开启了更广阔的职业发展大…

Java毕设项目推荐-基于springboot高校学生就业信息推送系统springboot的面向大学生的职业兴趣评估与就业指导平台【附源码+文档,调试定制服务】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

亿可达×飞书:一键搞定定时群通知,告别人工重复提醒

有没有过这样的职场日常&#xff1f; 每天下午临近下班&#xff0c;都要特意定个闹钟提醒自己&#xff1a;“别忘了发例会通知”“记得同步今日工作小结到飞书群”&#xff1b;每周一早上&#xff0c;总要专门抽5分钟&#xff0c;在部门群推送本周任务清单&#xff1b;甚至节假…

Java毕设选题推荐:基于springboot大学生就业服务平台springboot的面向大学生的职业兴趣评估与就业指导平台【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

Langchain 快速入门(一)

简介 langchain专门用于构建LLM大语言模型,其中提供了大量的prompt模板,和组件,通过chain(链)的方式将流程连接起来,操作简单,开发便捷。 环境配置 安装langchain框架 pip install langchain langchain-community…

2026最新版!微信小程序SaaS模板平台前十排名报告

2026年,小程序已成为商业经营的“标配”,但平台选择却愈发令人困惑。市场上工具繁多,宣传各异,企业主和创业者面临三大核心痛点: 第一,价格迷雾——低价入门后是否隐藏持续费用?第二,增长天花板——发展壮大后…

深入解析:Rust 练习册 :Matching Brackets与栈数据结构

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2026 年 AI PPT 工具深度评测:拒绝 “科技与狠活”,寻找真正能解决职场痛点的生产力工具

在 2026 年&#xff0c;AI PPT 已然成为职场效率的关键变量。不同的 AI PPT 工具带来的效率提升有天壤之别&#xff0c;这就形成了一条“效率分水岭”&#xff0c;站在分水岭两侧的职场人&#xff0c;工作效率和成果有着显著差异。经过深度实测&#xff0c;并结合本土化场景评估…

2026年专业深度测评:淘宝代运营公司排名前五权威榜单

2026年专业深度测评:淘宝代运营公司排名前五权威榜单 随着电商行业竞争进入存量精细化运营阶段,品牌方对专业、高效、数据驱动的淘宝代运营服务需求持续攀升。为帮助品牌方精准决策,本测评基于行业公开数据、服务案…

Java毕设项目:基于springboot的食品安全管理系统(源码+文档,讲解、调试运行,定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

【计算机毕业设计案例】基于Java的在线食品安全信息平台基于springboot的食品安全管理系统(程序+文档+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

AI代码质检员:如何用大模型提前揪出软件缺陷?

深夜两点,某大型电商平台的代码仓库悄悄合入了一个看似普通的促销模块更新。三天后的618大促当天,系统却在订单峰值时意外崩溃——事后发现,原来是一个边界条件检查遗漏造成的并发问题。这种故事在软件工程领域反复上演,直到AI开始介入这个传统上依赖人工经验的领域。 想象…

Linux命令创意

比赛背景与意义介绍Linux命令组合的灵活性与强大功能创意组合大赛的目标&#xff1a;激发开发者探索命令行的高效用法比赛对提升Shell脚本编写能力的价值比赛规则与参赛要求www.yunshengzx.com参赛作品需基于Linux命令行工具组合允许使用管道&#xff08;|&#xff09;、重定向…

湖州职业技术学院:Wi-Fi 7全覆盖,打造智慧校园“湖职样本”

“西塞山前白鹭飞&#xff0c;桃花流水鳜鱼肥。”千年之前&#xff0c;唐代诗人张志和所作的这首《渔歌子》&#xff0c;就勾勒出一幅绝美的江南风景长卷。如今&#xff0c;在湖州西塞山北麓&#xff0c;有着“最美山地大学”美称的湖州职业技术学院&#xff08;以下称“湖州职…

:计算机Java毕设实战-基于springboot的食品安全管理系统食品安全档案管理系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

Vue3底层原理——keep-alive

一、keep-alive 概述keep-alive​ 不是缓存 DOM,而是缓存「组件 VNode + 组件实例(但 VNode 里持有组件实例)」,它通过“劫持组件卸载流程”,把 destroy 变成 deactivate源码位置: packages/runtime-core/src/co…

:vtkBooleanOperationPolyDataFilter 布尔运算全解析

VTK实战&#xff1a;vtkBooleanOperationPolyDataFilter 布尔运算全解析 引言 在三维几何处理领域&#xff0c;布尔运算是实现模型合并、裁剪、相交等核心操作的基础能力。VTK&#xff08;Visualization Toolkit&#xff09;作为开源的三维可视化与图形处理库&#xff0c;提供了…

2026年拼多多代运营服务商专业深度测评:排名前五权威榜单

2026年拼多多代运营服务商专业深度测评:排名前五权威榜单 随着电商精细化运营趋势的深化,品牌方对拼多多代运营的专业化、数据化及全链路服务需求持续攀升。为帮助品牌方精准决策,我们基于多维度量化评估,发布本年…

APS1604M-SQR-SN核心性能特点及应用

品牌&#xff1a;爱普&#xff08;AP Memory&#xff09;型号&#xff1a;APS1604M-SQR-SN容量&#xff1a;16Mb, 2M x 8bits产品类型&#xff1a;PSRAM (Pseudo SRAM)接口类型&#xff1a;并行接口。它使用标准的SRAM-like接口&#xff0c;包括地址线、数据线、片选、读/写使能…