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

前言

二叉树的第一天,掌握前序中序后序遍历,及对应的递归迭代,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没理解透,看后续训练吧。

相关推荐
1 9 J6 分钟前
Java 上机实践4(类与对象)
java·开发语言·算法
passer__jw7672 小时前
【LeetCode】【算法】3. 无重复字符的最长子串
算法·leetcode
passer__jw7672 小时前
【LeetCode】【算法】21. 合并两个有序链表
算法·leetcode·链表
sweetheart7-72 小时前
LeetCode22. 括号生成(2024冬季每日一题 2)
算法·深度优先·力扣·dfs·左右括号匹配
__AtYou__3 小时前
Golang | Leetcode Golang题解之第557题反转字符串中的单词III
leetcode·golang·题解
李元豪3 小时前
【智鹿空间】c++实现了一个简单的链表数据结构 MyList,其中包含基本的 Get 和 Modify 操作,
数据结构·c++·链表
2401_858286113 小时前
L7.【LeetCode笔记】相交链表
笔记·leetcode·链表
我不是星海3 小时前
1.集合体系补充(1)
java·数据结构
景鹤4 小时前
【算法】递归+回溯+剪枝:78.子集
算法·机器学习·剪枝
_OLi_5 小时前
力扣 LeetCode 704. 二分查找(Day1:数组)
算法·leetcode·职场和发展