• 力扣刷题第二十五天--二叉树


    前言

    二叉树的第一天,掌握前序中序后序遍历,及对应的递归迭代,morris写法。难度一个比一个高是吧。。。

    内容

    一、二叉树的前序遍历

    144.二叉树的前序遍历

    给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

    递归

    每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!

    1. 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。

    2. 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。

    3. 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。

    1. /**
    2. * Definition for a binary tree node.
    3. * type TreeNode struct {
    4. * Val int
    5. * Left *TreeNode
    6. * Right *TreeNode
    7. * }
    8. */
    9. var res []int
    10. func preorderTraversal(root *TreeNode) []int {
    11. res=[]int{}
    12. dfs(root)
    13. return res
    14. }
    15. func dfs(root *TreeNode){
    16. if root!=nil{
    17. res=append(res,root.Val)
    18. dfs(root.Left)
    19. dfs(root.Right)
    20. }
    21. }
    22. // func preorderTraversal(root *TreeNode) (vals []int) {
    23. // var preorder func(*TreeNode)//定义了一个名为preorder的变量,它的类型是一个函数,这个函数接收一个指向TreeNode类型的指针node作为参数
    24. // preorder = func(node *TreeNode) {
    25. // if node == nil {
    26. // return
    27. // }
    28. // vals = append(vals, node.Val)
    29. // preorder(node.Left)
    30. // preorder(node.Right)
    31. // }
    32. // preorder(root)
    33. // return
    34. // }
    迭代

    递归的时候隐式地维护了一个栈,迭代的时候需要显式地将这个栈模拟出来

    1. func preorderTraversal(root *TreeNode)[]int{
    2. var res []int
    3. var stack []*TreeNode
    4. for len(stack)>0||root!=nil{
    5. for root!=nil{
    6. res=append(res,root.Val)
    7. stack=append(stack,root.Right)
    8. root=root.Left
    9. }//循环结束后,即当前节点为空时
    10. root=stack[len(stack)-1]
    11. stack=stack[:len(stack)-1]
    12. }
    13. return res
    14. }

    递归迭代两种写法: 

    时间复杂度:O(n),其中 n 是二叉树的节点数。每一个节点恰好被遍历一次。

    空间复杂度:O(n),为迭代过程中显式栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。

     Morris 遍历

    参考文章Morris遍历详解

    Morris 遍历的核心思想是利用树的大量空闲指针,实现空间开销的极限缩减。

    时间复杂度:O(n),其中 n 是二叉树的节点数。没有左子树的节点只被访问一次,有左子树的节点被访问两次。

    空间复杂度:O(1)。只操作已经存在的指针(树的空闲指针),因此只需要常数的额外空间

    如果在遍历一棵树时严令禁止修改树的结构,那么Morris遍历就用不了

    1. func preorderTraversal(root *TreeNode) (vals []int) {
    2. var p1,p2 *TreeNode=root,nil
    3. for p1!=nil{
    4. p2=p1.Left
    5. if p2!=nil{
    6. for p2.Right!=nil&&p2.Right!=p1{
    7. p2=p2.Right
    8. }
    9. if p2.Right==nil{
    10. vals=append(vals,p1.Val)
    11. p2.Right=p1
    12. p1=p1.Left
    13. continue
    14. }
    15. p2.Right=nil
    16. }else{
    17. vals=append(vals,p1.Val)
    18. }
    19. p1=p1.Right
    20. }
    21. return
    22. }
    二、 二叉树的中序遍历

    94.二叉树的中序遍历

    给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。

    递归
    1. func inorderTraversal(root *TreeNode) (res []int) {
    2. var inorder func(*TreeNode)
    3. inorder=func(node *TreeNode){
    4. if node==nil{
    5. return
    6. }
    7. inorder(node.Left)
    8. res=append(res,node.Val)
    9. inorder(node.Right)
    10. }
    11. inorder(root)
    12. return
    13. }
    迭代
    1. func inorderTraversal(root *TreeNode) (res []int) {
    2. stack:=[]*TreeNode{}
    3. for root!=nil||len(stack)>0{
    4. for root!=nil{
    5. stack=append(stack,root)
    6. root=root.Left
    7. }
    8. root=stack[len(stack)-1]
    9. stack=stack[:len(stack)-1]
    10. res=append(res,root.Val)
    11. root=root.Right
    12. }
    13. return
    14. }
    Morris遍历
    1. func inorderTraversal(root *TreeNode) (res []int) {
    2. for root!=nil{
    3. if root.Left!=nil{
    4. // predecessor 节点表示当前 root 节点向左走一步,然后一直向右走至无法走为止的节点
    5. p:=root.Left
    6. for p.Right!=nil&&p.Right!=root{
    7. // 有右子树且没有设置过指向 root,则继续向右走
    8. p=p.Right
    9. }
    10. if p.Right==nil{
    11. // 将 predecessor 的右指针指向 root,这样后面遍历完左子树 root.Left 后,就能通过这个指向回到 root
    12. p.Right=root
    13. // 遍历左子树
    14. root=root.Left
    15. }else{// predecessor 的右指针已经指向了 root,则表示左子树 root.Left 已经访问完了
    16. res=append(res,root.Val)
    17. p.Right=nil// 恢复原样
    18. root=root.Right// 遍历右子树
    19. }
    20. }else{// 没有左子树
    21. res=append(res,root.Val)
    22. // 若有右子树,则遍历右子树
    23. // 若没有右子树,则整颗左子树已遍历完,root 会通过之前设置的指向回到这颗子树的父节点
    24. root=root.Right
    25. }
    26. }
    27. return
    28. }
    三、二叉树的后序遍历

    145.二叉树的后序遍历

    给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 

    递归
    1. func postorderTraversal(root *TreeNode) []int {
    2. var res []int
    3. var postorder func(*TreeNode)
    4. postorder=func(node *TreeNode){
    5. if node==nil{
    6. return
    7. }
    8. postorder(node.Left)
    9. postorder(node.Right)
    10. res=append(res,node.Val)
    11. }
    12. postorder(root)
    13. return res
    14. }
    迭代
    1. func postorderTraversal(root *TreeNode)[]int{
    2. var res []int
    3. var stack []*TreeNode
    4. for len(stack)>0||root!=nil{
    5. for root!=nil{
    6. res=append(res,root.Val)
    7. stack=append(stack,root.Left)
    8. root=root.Right
    9. }//循环结束后,即当前节点为空时
    10. root=stack[len(stack)-1]
    11. stack=stack[:len(stack)-1]
    12. }
    13. reverse(res)
    14. return res
    15. }
    16. func reverse(a []int) {
    17. l, r := 0, len(a) - 1
    18. for l < r {
    19. a[l], a[r] = a[r], a[l]
    20. l++
    21. r--
    22. }
    23. }
    24. //先序 中左右 调整为中右左 反转 左右中
    1. func postorderTraversal(root *TreeNode)(res []int){
    2. stack:=[]*TreeNode{}
    3. var prev *TreeNode
    4. for root!=nil||len(stack)>0{
    5. for root!=nil{
    6. stack=append(stack,root)
    7. root=root.Left
    8. }
    9. root=stack[len(stack)-1]
    10. stack=stack[:len(stack)-1]
    11. if root.Right==nil||root.Right==prev{
    12. res=append(res,root.Val)
    13. prev=root
    14. root=nil
    15. }else{
    16. stack=append(stack,root)
    17. root=root.Right
    18. }
    19. }
    20. return
    21. }
    Morris遍历
    1. func postorderTraversal(root *TreeNode) (res []int) {
    2. addPath:=func(node *TreeNode){
    3. resSize:=len(res)
    4. for ;node!=nil;node=node.Right{
    5. res=append(res,node.Val)
    6. }
    7. reverse(res[resSize:])
    8. }
    9. p1:=root
    10. for p1!=nil{
    11. if p2:=p1.Left;p2!=nil{
    12. for p2.Right!=nil&&p2.Right!=p1{
    13. p2=p2.Right
    14. }
    15. if p2.Right==nil{
    16. p2.Right=p1
    17. p1=p1.Left
    18. continue
    19. }
    20. p2.Right=nil
    21. addPath(p1.Left)
    22. }
    23. p1=p1.Right
    24. }
    25. addPath(root)
    26. return
    27. }
    28. func reverse(a []int){
    29. for i,n:=0,len(a);i2;i++{
    30. a[i],a[n-i-1]=a[n-i-1],a[i]
    31. }
    32. }

    最后

    迭代和Morris没理解透,看后续训练吧。

  • 相关阅读:
    万界星空科技电子机电行业MES系统,2000元/年起
    Python中四个不常见的小技巧
    Linux系统安全配置
    【kubernetes篇】使用Harbor仓库管理kubernetes镜像
    章鱼网络进展月报 | 2022.8.1-8.31
    Spring Boot集成WebSocket以及基本使用
    Linux操作系统之基础IO
    如何使用chatGPT写好Prompt提示词: Few-shots
    SpringBoot + Redis的Bitmap实现活跃用户统计
    【全国30米DEM、全国路网矢量、100万全国基础地理数据、高德行政区划数据】一次分享
  • 原文地址:https://blog.csdn.net/m0_62786673/article/details/134478676