二叉树的组成与定义
一棵二叉树由根结点、左子树和右子树三部分组成。
前序、中序、后续遍历次序
若使用D、L、R 分别标识根结点、左子树、右子树,则二叉树的遍历方式有 6 种:DLR、DRL、LDR、LRD、RDL、RLD。由于先遍历左子树和先遍历右子树在算法设计上没有本质区别,所以,只讨论三种方式:
DLR--前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 );
LDR--中序遍历(根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面);
LRD--后序遍历(根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面)
注意:
- 根是相对的,对于整棵树而言只有一个根,但对于每个子树而言,又有自己的根。比如对于下面三个图,对于整棵树而言,A是根,A分别在最前面、中间、后面被遍历到。而对于D,它是G和H的根,对于D、G、H这棵小树而言,顺序分别是DGH、GDH、GHD;对于C,它是E和F的根,三种排序的顺序分别为: CEF、ECF、EFC。是不是根上面的DLR、LDR、LRD一模一样呢~~
- 整棵树的起点,就如上面所说的,从A开始,前序遍历的话,一棵树的根永远在左子树前面,左子树又永远在右子树前面,只需找起点就好了。
- 二叉树结点的先根序列、中根序列和后根序列中,所有叶子结点的先后顺序一样
二叉树结构定义
type TreeNode struct {
data interface{} // 数值
Lchild *TreeNode // 左节点
Rchild *TreeNode // 右节点
}
前序遍历
二叉树的前序遍历,是先遍历根,然后遍历左子树,最后是右子树。
递归遍历方式
二叉树的前序遍历:按照访问根节点——左子树——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候又按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,可以直接用递归函数来模拟这一过程。
定义 inorder(root) 表示当前遍历到 root 节点,按照二叉树中序遍历的定义,只要先访问根节点,然后递归调用 inorder(root.left) 来遍历 root 节点的左子树,再递归调用inorder(root.right) 来遍历 root节点的右子树即可。
代码:
void preOrder(root *TreeNode) []int{ // 前序递归遍历
var res []int
if(root == nil)
return res
res = append(res, root.data) // 先存放根节点
res = append(res, preOrder(root.Lchild)...) // 递归遍历左子树,并将遍历后的结果放入结果列表
res = append(res, preOrder(root.Rchild)...) // 递归遍历右子树,并将遍历后的结果放入结果列表
return res
}
非递归遍历方式
实现:第一次访问的时候就记录到结果数组中,然后进栈,依次访问其左孩子,知道左孩子为空弹出栈顶,访问其右孩子,循环这个过程,直到栈为空,root也为空
代码:
func preOrder(root *TreeNode) []int{ //非递归前序遍历
res:=[]int{}
if root==nil{
return res
}
stack:=[]*TreeNode{} //定义一个栈储存节点信息
for root!=nil || len(stack)!=0{
if root!=nil{
res=append(res,root.data)
stack=append(stack,root)
root=root.Lchild
}else{
root=stack[len(stack)-1]
stack=stack[:len(stack)-1]
root=root.Rchild
}
}
return res
}
中序遍历
中序遍历:先左子树,再访问根节点,最后遍历右子树。
递归方式
二叉树的中序遍历:按照访问左子树——根节点——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候又按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,可以直接用递归函数来模拟这一过程。
定义 inOrder(root) 表示当前遍历到 root 节点,按照二叉树中序遍历的定义,只要递归调用 inOrder(root.left) 来遍历 root 节点的左子树,然后将 root 节点的值加入答案,再递归调用inOrder(root.right) 来遍历 root节点的右子树即可。
代码:
void inOrder(root *TreeNode) []int{ // 中序递归遍历
var res []int
if(root == nil)
return res
res = append(res, inOrder(root.Lchild)...) // 递归遍历左子树,并将遍历后的结果放入结果列表
res = append(res, root.data) // 先存放根节点
res = append(res, inOrder(root.Rchild)...) // 递归遍历右子树,并将遍历后的结果放入结果列表
return res
}
非递归方式
实现:根前序很相似,只是改变一下顺序,从栈中弹出的时候(第二次访问)才放进结果数组
代码:
func inOrder(root *TreeNode) []int{
res:=[]int{}
if root==nil{
return res
}
stack:=[]*TreeNode{}
for root!=nil || len(stack)!=0{
if root!=nil{
stack=append(stack,root)
root=root.Lchild
}else{
root=stack[len(stack)-1]
res=append(res,root.data)
stack=stack[:len(stack)-1]
root=root.Rchild
}
}
return res
}
后序遍历
二叉树的后序遍历:先遍历左子树、再遍历右子树、最后访问根节点。
递归方式
二叉树的后序遍历:按照访问左子树——右子树——根节点的方式遍历这棵树,而在访问左子树或者右子树的时候又按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,可以直接用递归函数来模拟这一过程。
定义 postOrder(root) 表示当前遍历到 root 节点,按照二叉树中序遍历的定义,只要递归调用 postOrder(root.left) 来遍历 root 节点的左子树,然后递归调用postOrder(root.right) 来遍历 root节点的右子树,最后访问根节点即可。
代码:
void postOrder(root *TreeNode) []int{ // 后序递归遍历
var res []int
if(root == nil)
return res
res = append(res, postOrder(root.Lchild)...) // 递归遍历左子树,并将遍历后的结果放入结果列表
res = append(res, postOrder(root.Rchild)...) // 递归遍历右子树,并将遍历后的结果放入结果列表
res = append(res, root.data) // 先存放根节点
return res
}
非递归方式
前序中序都比较简单,而且很相似,后序就稍微复杂一下了
实现:这里要增加一个辅助节点来节点上一次放入结果数组的值,当一个节点左右都是空的时候,就可以放入结果集,当上一个放入结果集的节点是他的孩子节点的时候,说明他的孩子已经访问完成了,到这里就是第三次了就可以放入了,还有一点要注意的是,当一个节点的左右不为空时,要先加入右孩子,再加入左孩子,这样才能先访问左孩子。
func postOrder(root *TreeNode) []int {
res:=[]int{}
if root==nil{
return res
}
stack:=[]*TreeNode{}
pre:=&TreeNode{}
stack=append(stack,root)
for len(stack)!=0{
cur:=stack[len(stack)-1]
if (cur.Lchild==nil && cur.Rchild==nil) || (pre!=nil &&(pre==cur.Lchild || pre==cur.Rchild)){
res=append(res,cur.data)
pre=cur
stack=stack[:len(stack)-1]
}else{
if cur.Rchild!=nil{
stack=append(stack,cur.Rchild)
}
if cur.Lchild!=nil{
stack=append(stack,cur.Lchild)
}
}
}
return res
}
层次遍历
二叉树的层序遍历,就是按树的层级,从上到下,从左到右遍历。
各节点存放在一个数组中
普通的BFS 比较简单 只需要一个一维数组[]int存放tree的队列、一个一维数组[]*TreeNode存放当前层级遍历到的节点,利用queue先进先出的规则存放value即可;
实现:
func BFS(root *TreeNode) []int {
valueList := make([]int, 0)
queue := make([]*TreeNode, 0)
if root == nil{
return valueList
}
queue = append(queue, root)
for len(queue) > 0 {
first := queue[0]
valueList = append(valueList, first.Value)
queue = queue[1:]
if first.Left != nil {
queue = append(queue, first.Left)
}
if first.Right != nil {
queue = append(queue, first.Right)
}
}
return valueList
}
每层一个单独数组打印
如果根据层级来保存维护一个二维数组[][]int, 需要声明一个正在遍历的队列和一个保存下一层节点的队列即可
实现:
func BFSLevelOrder(root *TreeNode) [][]int {
queue := make([]*TreeNode, 0)
valueList := make([][]int, 0)
if root == nil{
return valueList
}
queue = append(queue, root)
for len(queue) > 0 {
tmpQueue := make([]*TreeNode, 0)
tmpValueList := make([]int, 0)
for len(queue) > 0 {
first := queue[0]
queue = queue[1:]
tmpValueList = append(tmpValueList, first.Val)
if first.Left != nil {
tmpQueue = append(tmpQueue, first.Left)
}
if first.Right != nil {
tmpQueue = append(tmpQueue, first.Right)
}
}
queue = queue[0:0]
queue = append(queue, tmpQueue...)
valueList = append(valueList, tmpValueList)
}
return valueList
}
之字形打印
剑指 Offer 32 - III. 从上到下打印二叉树 III
参考资料
二叉树前序遍历、中序遍历、后序遍历、层序遍历的直观理解
二叉树树的前,中,后序遍历非递归–go语言实现
leetcode 102.二叉树的层级遍历 golang实现