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

前言

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

内容

一、二叉树的前序遍历

144.二叉树的前序遍历

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

递归

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

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

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

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

Go 复制代码
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var res []int
func preorderTraversal(root *TreeNode) []int {
    res=[]int{}
    dfs(root)
    return res
}

func dfs(root *TreeNode){
    if root!=nil{
        res=append(res,root.Val)
        dfs(root.Left)
        dfs(root.Right)
    }
}
// func preorderTraversal(root *TreeNode) (vals []int) {
//     var preorder func(*TreeNode)//定义了一个名为preorder的变量,它的类型是一个函数,这个函数接收一个指向TreeNode类型的指针node作为参数
//     preorder = func(node *TreeNode) {
//         if node == nil {
//             return
//         }
//         vals = append(vals, node.Val)
//         preorder(node.Left)
//         preorder(node.Right)
//     }
//     preorder(root)
//     return
// }
迭代

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

Go 复制代码
func preorderTraversal(root *TreeNode)[]int{
    var res []int
    var stack []*TreeNode

    for len(stack)>0||root!=nil{
        for root!=nil{
            res=append(res,root.Val)
            stack=append(stack,root.Right)
            root=root.Left
        }//循环结束后,即当前节点为空时
       root=stack[len(stack)-1]
       stack=stack[:len(stack)-1]
    }
    return res
}

递归迭代两种写法:

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

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

Morris 遍历

参考文章Morris遍历详解

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

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

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

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

Go 复制代码
func preorderTraversal(root *TreeNode) (vals []int) {
  var p1,p2 *TreeNode=root,nil
     for p1!=nil{
         p2=p1.Left
         if p2!=nil{
             for p2.Right!=nil&&p2.Right!=p1{
                 p2=p2.Right
             }

             if p2.Right==nil{
                 vals=append(vals,p1.Val)
                 p2.Right=p1
                 p1=p1.Left
                 continue
             }
             p2.Right=nil
         }else{
             vals=append(vals,p1.Val)
         }
        p1=p1.Right
     }
     return
}
二、 二叉树的中序遍历

94.二叉树的中序遍历

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

递归
Go 复制代码
func inorderTraversal(root *TreeNode)  (res []int) {
     var inorder func(*TreeNode)
     inorder=func(node *TreeNode){
         if node==nil{
             return
         }
         inorder(node.Left)
         res=append(res,node.Val)
         inorder(node.Right)
     }
     inorder(root)
     return
}
迭代
Go 复制代码
func inorderTraversal(root *TreeNode)  (res []int) {
     stack:=[]*TreeNode{}
     for root!=nil||len(stack)>0{
         for root!=nil{
             stack=append(stack,root)
             root=root.Left
         }
         root=stack[len(stack)-1]
         stack=stack[:len(stack)-1]
         res=append(res,root.Val)
         root=root.Right
     }
    return
}
Morris遍历
Go 复制代码
func inorderTraversal(root *TreeNode)  (res []int) {
  for root!=nil{
      if root.Left!=nil{
          // predecessor 节点表示当前 root 节点向左走一步,然后一直向右走至无法走为止的节点
          p:=root.Left
          for p.Right!=nil&&p.Right!=root{
              // 有右子树且没有设置过指向 root,则继续向右走
              p=p.Right
          }
          if p.Right==nil{
              // 将 predecessor 的右指针指向 root,这样后面遍历完左子树 root.Left 后,就能通过这个指向回到 root
              p.Right=root
              // 遍历左子树
              root=root.Left
          }else{// predecessor 的右指针已经指向了 root,则表示左子树 root.Left 已经访问完了
              res=append(res,root.Val)
              p.Right=nil// 恢复原样
              root=root.Right// 遍历右子树
          }
      }else{// 没有左子树
          res=append(res,root.Val)
          // 若有右子树,则遍历右子树
			// 若没有右子树,则整颗左子树已遍历完,root 会通过之前设置的指向回到这颗子树的父节点
          root=root.Right
      }
  }
  return
}
三、二叉树的后序遍历

145.二叉树的后序遍历

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

递归
Go 复制代码
func postorderTraversal(root *TreeNode) []int {
    var res []int

  var postorder func(*TreeNode)
  postorder=func(node *TreeNode){
      if node==nil{
          return 
      }
      postorder(node.Left)
      postorder(node.Right)
     res=append(res,node.Val)
  }
  postorder(root)
  return res
}
迭代
Go 复制代码
func postorderTraversal(root *TreeNode)[]int{
    var res []int
    var stack []*TreeNode

    for len(stack)>0||root!=nil{
        for root!=nil{
            res=append(res,root.Val)
            stack=append(stack,root.Left)
            root=root.Right
        }//循环结束后,即当前节点为空时
       root=stack[len(stack)-1]
       stack=stack[:len(stack)-1]
    }
    reverse(res)
    return  res
}
func reverse(a []int) {
    l, r := 0, len(a) - 1
    for l < r {
        a[l], a[r] = a[r], a[l]
       l++
       r--
    }
}
//先序 中左右 调整为中右左 反转 左右中
Go 复制代码
func postorderTraversal(root *TreeNode)(res []int){
    stack:=[]*TreeNode{}
    var prev *TreeNode
    for root!=nil||len(stack)>0{
      for root!=nil{
          stack=append(stack,root)
          root=root.Left
      }
      root=stack[len(stack)-1]
      stack=stack[:len(stack)-1]
      if root.Right==nil||root.Right==prev{
          res=append(res,root.Val)
          prev=root
          root=nil
      }else{
          stack=append(stack,root)
          root=root.Right
      }
    }
    return
}
Morris遍历
Go 复制代码
func postorderTraversal(root *TreeNode) (res []int) {
    addPath:=func(node *TreeNode){
        resSize:=len(res)
        for ;node!=nil;node=node.Right{
            res=append(res,node.Val)
        }
        reverse(res[resSize:])
    }
    p1:=root
    for p1!=nil{
        if p2:=p1.Left;p2!=nil{
          for p2.Right!=nil&&p2.Right!=p1{
              p2=p2.Right
          }
          if p2.Right==nil{
              p2.Right=p1
              p1=p1.Left
              continue
          }
          p2.Right=nil
          addPath(p1.Left)
        }
        p1=p1.Right
    }
    addPath(root)
    return
}
func reverse(a []int){
    for i,n:=0,len(a);i<n/2;i++{
        a[i],a[n-i-1]=a[n-i-1],a[i]
    }
}

最后

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

相关推荐
cxylay36 分钟前
自适应滤波算法分类及详细介绍
算法·分类·自适应滤波算法·自适应滤波·主动噪声控制·anc
茶猫_43 分钟前
力扣面试题 - 40 迷路的机器人 C语言解法
c语言·数据结构·算法·leetcode·机器人·深度优先
轻浮j1 小时前
Sentinel底层原理以及使用算法
java·算法·sentinel
Abelard_1 小时前
LeetCode--347.前k个高频元素(使用优先队列解决)
java·算法·leetcode
小猪写代码1 小时前
C语言:递归函数(新增)
算法·c#
点云SLAM1 小时前
C++创建文件夹和文件夹下相关操作
开发语言·c++·算法
heeheeai2 小时前
kotlin 函数作为参数
java·算法·kotlin
是十一月末2 小时前
opencv实现KNN算法识别图片数字
人工智能·python·opencv·算法·k-近邻算法
袖清暮雨2 小时前
5_SparkGraphX讲解
大数据·算法·spark
tatasix2 小时前
Go Redis实现排行榜
开发语言·redis·golang