前言
二叉树的第一天,掌握前序中序后序遍历,及对应的递归迭代,morris写法。难度一个比一个高是吧。。。
内容
一、二叉树的前序遍历
给你二叉树的根节点 root
,返回它节点值的 前序 遍历。
递归
每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
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
}
二、 二叉树的中序遍历
给定一个二叉树的根节点 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
}
三、二叉树的后序遍历
给你一棵二叉树的根节点 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没理解透,看后续训练吧。