当前位置: 首页 > news >正文

二叉树—中序遍历—非递归

 

初始状态

假设当前从根节点 b 开始,此时栈为空 。

第一步:处理根节点 b 的左子树

  • 调用 goAlongLeftBranch 函数,从节点 b 开始,因为 b 有左子树(节点 a ),将 b 入栈,此时栈:[b] ;继续沿着左分支,a 没有左子树了,将 a 入栈 ,此时栈:[a, b] 。
  • 回到主循环,栈非空,执行 x = S.pop() ,a 出栈并访问 a ;然后 x 指向 a 的右子树,a 右子树为空,回到主循环继续判断栈状态。

第二步:处理根节点 b

  • 此时栈顶是 b ,执行 x = S.pop() ,b 出栈并访问 b ;然后 x 指向 b 的右子树(节点 f )。

第三步:处理 b 右子树(以 f 为根的子树 )

  • 调用 goAlongLeftBranch 函数,从节点 f 开始,因为 f 有左子树(节点 d ),将 f 入栈,此时栈:[f] ;继续沿着左分支,d 有左子树(节点 c ),将 d 入栈 ,此时栈:[d, f] ;c 没有左子树了,将 c 入栈 ,此时栈:[c, d, f] 。
  • 回到主循环,栈非空,执行 x = S.pop() ,c 出栈并访问 c ;然后 x 指向 c 的右子树,c 右子树为空,回到主循环继续判断栈状态。

第四步:继续处理 f 左子树剩余部分

  • 此时栈顶是 d ,执行 x = S.pop() ,d 出栈并访问 d ;然后 x 指向 d 的右子树(节点 e )。
  • 将 e 入栈,此时栈:[e, f] ,执行 x = S.pop() ,e 出栈并访问 e ;e 右子树为空,回到主循环继续判断栈状态。

第五步:处理根节点 f

  • 此时栈顶是 f ,执行 x = S.pop() ,f 出栈并访问 f ;然后 x 指向 f 的右子树(节点 g )。
  • 将 g 入栈,此时栈:[g] ,执行 x = S.pop() ,g 出栈并访问 g ;g 右子树为空,此时栈为空,满足 S.empty() 条件,break 跳出循环,中序遍历结束。

 

 

时间复杂度的证明:

 

简要分析:

关键操作包括:入栈( goAlongLeftBranch 函数)和出栈节点访问(主循环)

入栈 

goAlongLeftBranch 函数:沿着二叉树的左分支不断将节点入栈。最坏情况下,二叉树退化为一条单链(eg:所有节点只有左孩子 ),此时对于一个具有 n 个节点的二叉树,调用 goAlongLeftBranch 函数时  需将 n 个节点依次入栈,每次入栈操作可视为 O(1) ,所以单次 goAlongLeftBranch 调用  在最坏情况下的时间复杂度为 Ω(n) 。

出栈及节点访问

主循环中,每次迭代会执行一次出栈并访问出栈节点。每个节点只会入栈一次且出栈一次(每个节点在沿着左分支时入栈,出栈访问后不会再次入栈 )。出栈和访问节点都是O(1) 。由于二叉树共有 n 个节点,所以出栈及节点访问操作的总时间复杂度为 O(n) 。

分摊

虽然单次 goAlongLeftBranch 调用可能花费 Ω(n) 时间(如二叉树为单链时 ),但从整个遍历过程来看,每个节点入栈和出栈操作总共最多各一次。

例如,当沿着左分支集中进行多次入栈(耗时较长 )时,后续这些入栈节点会依次出栈并被访问。把前面集中入栈的较长时间开销分摊到后续这些节点出栈及访问的过程中。从整体 n 个节点的处理来看,平均每个节点的操作时间是常数级别的。所以整个中序遍历算法的时间复杂度为 O(n) 。

 

具体分析:

template <typename T>
static void goAlongLeftBranch( BinNodePosi(T) x, Stack <BinNodePosi(T)> &S ) {while ( x ) { S.push( x ); x = x->lc; } // 反复地入栈,沿左分支深入
}

作用:沿当前节点 x 的左分支不断将节点入栈。每次循环执行 S.push(x)(入栈,时间复杂度为 O(1))和 x=x−>lc(指针移动,时间复杂度为 O(1))。

对于每个节点,它只会从左分支方向被访问一次并入栈。整个二叉树共有 n 个节点,因此所有节点入栈的总次数为 n,总时间复杂度为 O(n)。

 

template <typename T, typename V> 
void travIn_I1( BinNodePosi(T) x, V& visit ) { Stack <BinNodePosi(T) > S; // 辅助栈while ( true ) { // 反复地goAlongLeftBranch( x, S ); // 从当前节点出发,逐批入栈if ( S.empty() ) break; // 直至所有节点处理完毕x = S.pop(); // x的左子树或为空,或已遍历(等效于空),故可以visit( x->data ); // 立即访问之x = x->rc; // 再转向其右子树(可能为空,留意处理手法)}
}

 

出栈x = S.pop() 为出栈,时间复杂度为 O(1)。每个节点入栈后仅出栈一次,总出栈次数为 n,总时间复杂度为 O(n)。

访问节点visit( x->data ) 用于访问节点数据,每个节点仅被访问一次,时间复杂度为 O(1),n 个节点总时间复杂度为 O(n)。

转向右子树x = x->rc 是指针移动,时间复杂度为 O(1)。每个节点处理完后转向右子树(即使右子树为空也会执行一次该操作),总操作次数为 n,总时间复杂度为 O(n)。

虽然 goAlongLeftBranch 函数单次调用可能沿长左链做多次入栈(如树退化为左单链时),但从全局看,每个节点的入栈、出栈、访问及转向右子树操作均为 O(1) 且仅执行一次。

总时间复杂度为各部分时间复杂度之和:O(n)+O(n)+O(n)+O(n)=O(n)。

此二叉树中序遍历代码的时间复杂度为 O(n),每个节点都被且仅被处理一次,所有操作的总耗时与节点数 n 呈线性关系。

 

 

1.入栈

goAlongLeftBranch 函数中 while (x) { S.push(x); x = x->lc; } 会沿左分支将节点入栈。每个节点仅入栈一次,总入栈次数为 n(n 为节点总数),入栈总时间为 O(n)。

2. 外层循环

  • 出栈x = S.pop() 每个节点仅出栈一次,总出栈次数为 n,时间为 O(n)。
  • 访问节点visit(x->data) 每个节点仅访问一次,时间为 O(n)。
  • 转向右子树x = x->rc 每个节点处理后转向右子树(即使为空也执行一次),总操作次数为 n,时间为 O(n)。

虽然有循环嵌套,但每个节点的入栈、出栈、访问、转向右子树操作均为 O(1) 且仅执行一次。所有操作的总时间复杂度为各部分之和:

O(n)(入栈)+O(n)(出栈)+O(n)(访问)+O(n)(转向右子树)=O(n)


外层循环,每次出栈一个节点,处理它的右子树。每个节点出栈一次,访问一次。虽然外层是 while (true),但每次循环都处理一个节点,并且每个节点的入栈和出栈都是一次。

时间复杂度看的是每个操作的总次数。入栈总次数是 n,出栈总次数是 n,每个节点处理一次。所以总的时间复杂度是 O (n)

一句话:循环嵌套看似为n^2,但在while(true)中每个节点只被处理一次,没有重复处理,所以是 O (n)

DSACPP

 

相关文章:

  • centos的根目录占了大量空间怎么办
  • 大语言模型(LLM)领域,有几项显著的进展和技术突破
  • 如何用Java读取PDF
  • 自然语言处理之情感分析:ALBERT在社交媒体的应用技术教程
  • 家庭宽带IP与IDC机房IP
  • ϵ-prediction和z0-prediction是什么意思
  • Day17 聚类算法(K-Means、DBSCAN、层次聚类)
  • HarmonyOS 5.0 低时延音视频开发​​
  • 【硬核数学】0. 序章:万丈高楼平地起,AI数学筑基之旅《从零构建机器学习、深度学习到LLM的数学认知》
  • 数字图像相关法在薄板变形测量中的实践
  • Apache Velocity代码生成简要介绍
  • 单例模式都有哪些?有什么区别?
  • 求组合数【递推+快速幂+卢卡斯+线性筛】
  • 单例模式的实现方法
  • Android数据库全栈开发实战:Room+SQLCipher+Hilt企业级应用构建
  • 解决Centos连不上网
  • 自定义一个 Spring Boot Starter -笔记
  • 广州华锐视点邀您参与2025广交会VRAR展【5月10-12日】
  • 大数据产品销售数据分析:基于Python机器学习产品销售数据爬虫可视化分析预测系统设计与实现
  • 20250506格式化NanoPi NEO开发板使用Ubuntu core16.04系统的TF启动卡
  • 外交部介绍中国赞比亚共同举办人工智能能力建设主题活动情况
  • 上海国际电影节特设“走进大卫·林奇的梦境”单元
  • 市场监管总局发布《城镇房屋租赁合同(示范文本)》
  • 发表“男性患子宫肌瘤”论文的杂志一年发文三千余篇,中介称可提供代写
  • 赵心童世锦赛历史性夺冠,你今天打斯诺克很可能订不到位
  • 媒体:不能让追求升学率,成为高中不双休的借口