二叉树的前序、中序、后序递归及非递归遍历、层次遍历

二叉树的组成与定义

一棵二叉树由根结点、左子树和右子树三部分组成。

前序、中序、后续遍历次序

若使用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实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

love666666shen

谢谢您的鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值