zoukankan      html  css  js  c++  java
  • 二叉树的简单实战 → 一起温故下二叉树的遍历

    开心一刻

      一天,有个男粉丝跟我述苦

      粉丝:我喜欢一个女人,那个女人也喜欢我

      我:你们都是多大

      粉丝:我今年23,她今年26

      我:女大三,抱金砖,我觉得可以呀,年龄不是问题

      粉丝:她家里不同意

      我:她家里为什么不同意,嫌你条件不好,还是嫌你年龄太小

      粉丝:她老公不同意

      我:她老公... 你给我滚

    前情回顾

      对二叉树的遍历还不了解的,先去看看:二叉树的遍历 → 不用递归,还能遍历吗

      简单来说,深度遍历用  辅助实现,广度遍历用 队列 辅助实现

      不管是递归(系统栈)实现,还是 栈 + 迭代 实现,深度遍历的额外空间复杂度都是:O(n)

      那有没有额外空间复杂度 O(1) 的方法来实现二叉树的深度遍历呢?(O(1) 是指常数级别,而非字面 1 的意思)

      还真有:morris traversal,只是遍历过程会破坏二叉树的结构,所以存在恢复二叉树结构的过程,具体实现可查看:Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)

      不是很好理解,大家结合二叉树样本结构,去逐行 debug 代码,看看二叉树的遍历、结构变化,慢慢的就有感觉了

    实战案例

      当我们对二叉树的遍历有了一定的了解之后,我们就可以尝试解决一些二叉树的问题了

      最大宽度

      从根节点开始,一层一层往下统计,最大宽度即是节点数最多的那一(些)层的节点数量,例如

      最大宽度就是 3

      很明显,这是一个宽度(广度)遍历,那就需要用到 队列 来辅助实现

      光实现宽度遍历还不够,还需要统计每一层的节点数,然后找出最大宽度;那如何统计每一层的节点数?

      我们可以先用哈希表记录每个节点所处的层次,实现如下

      相信大家都能看懂这个代码,就是在宽度遍历的基础上,对每个节点进行层次标记

      标记完之后,再遍历 levelMap ,完成层次的个数统计?

      我们知道哈希表一般是无序的,再遍历 levelMap 进行层次的个数统计,并没那么简单;非要较劲,也是可以实现的,但没比较

      宽度遍历本身就是逐层进行的,当进行到下一层时,上一层肯定全部遍历完了,所以当遍历下一层的时候,上一层的节点数就应该统计出来

      我们来看完整代码

      简单点来说,就是在宽度遍历的基础上加入了统计的逻辑,所以重点是宽度遍历

      只要能够理解宽度遍历,上述代码就很容易理解

      我们再延伸下,如果不用哈希表,还能实现吗?

      哈希表的作用看似是记录每个节点所在的层次,实际就是用来判断当前层次是否处理完,基于此我们可以改造下

      用两个节点变量( curEnd 、 nextEnd )分别记录当前层的最后一个节点和下一层的最后一个节点;遍历当前层的时候,移动 nextEnd 

      当遍历完当前层节点( cur == curEnd ), nextEnd 刚好来到来到下一层的最后一个节点,将 nextEnd 赋值给 curEnd ( curEnd = nextEnd; ),开始下一层的遍历与统计

      如此反复,找到最大宽度;代码如下

      是不是很有意思?

      我们再来延伸下,找出最大宽度的同时,找出最大宽度所在的层(可能多层),该如何实现?

      这个就交给你们自己去实现了

      路径总和

      LeeCode 113

      给定二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径(叶子节点 是指没有子节点的节点)

      示例:

      先序遍历,找出所有路劲,过滤出路径上节点之和等于 targetSum 的路径

      比较简单,直接看代码

      有两个注意点:1、为什么不直接将 curPath 添加到 allPath,而是 copy 一份之后将新的添加到 allPath;2、为什么要回溯

      第 1 点,正是由于回溯,导致 curPath 中的元素会变化,如果 allPath 直接添加 curPath(allPath.add(curPath)),那么 allPath 中的元素也会随着递归的出栈而变化

      所以这两个注意点可以归纳为一点:为什么要回溯

      不理解为什么要回溯的小伙伴,可以先去查查回溯的相关资料

      在这里,回溯的作用是还原现场,保证我们能够正确的找到所有路径

      折纸问题

      把纸条竖着放在桌⼦上,然后从纸条的下边向上⽅对折,压出折痕后再展开,此时有 1 条折痕,突起的方向指向纸条的背⾯,这条折痕叫做“凹”折痕 ;突起的⽅向指向纸条正⾯的折痕叫做“凸”折痕

      如果每次都从下边向上方对折,对折 N 次,请从上到下打印出所有折痕的方向

      我们用纸条去实操下,就会发现规律,这就是一个二叉树的中序遍历(严格来时,是满二叉树的中序遍历)

      很简单,直接看代码

      这题很容易,只要你去实操折纸,找到了规律,代码实现就是手到擒来

      最低公共祖先

      求同一棵二叉树中两个节点的最低公共祖先节点

      什么是最低公共祖先,节点往上向根节点移动,两个节点最先汇聚的节点则是这两个节点的最低公共祖先,例如

      10 和 4 的最低公共祖先就是 3

      简单的做法是借助哈希表

      先遍历一次二叉树,记录所有节点的父节点(HashMap),然后找出其中某个节点(n1)的所有祖先节点(存放到 HashSet 中)

      再从另一个节点(n2)开始,从 HashMap 中逐个找 n2 的祖先节点的同时,判断 n2 的当前祖先节点是否在 HashSet 中

      一旦找到,这直接返回该节点,就是 n1 与 n2 的最低公共祖先

      我们来看代码

      很好理解,也很好实现,就是有点费空间

      还有一种方式,实现非常简单,但却不好理解,是前辈们反复提炼之后得到的一种解法

      大家千万不要只盯着代码看,需要结合具体的二叉树结构、n1与n2的关系,逐行代码去模拟,去找感觉,来理解这种方式

      这可是前辈们反复提炼之后的方法,如果你一眼就看懂了,那岂不是太过分了?

    总结

      1、二叉树的遍历是解二叉树题的基础,基础一定要打牢

        相信大家已从上述几个案例中感觉到了

        基础不牢,地动山摇,你们懂的

      2、举的案例有限,目的也仅仅只是给大家找找感觉

        有了一定的感觉,就可以力扣走起:二叉树

  • 相关阅读:
    在线程中使用OpenFileDialog
    Log4net附加
    SQL表间列根据条件复制(赋值)
    DataGridView使用
    Latex使用总结及备忘
    Windows获取文件状态
    TabControl取消首次定位焦点
    C#跨线程调用
    电子词典的查寻程序,发送和接收应答程序
    电子词典的相关子函数db.c程序
  • 原文地址:https://www.cnblogs.com/youzhibing/p/15759068.html
Copyright © 2011-2022 走看看