leetcode的题目:
给定一个二叉树,给出二叉树的先序、中序、后序的遍历结构
解题思路【递归解法】:
### 1.第一个知识点二叉树的先序遍历、中序遍历、后序遍历
先序遍历结构:根 --> 左 -> 右 遍历结果:1->2->3
中序遍历结构:左–> 根–> 右 遍历结果:1->3->2
后序遍历结构:左–>右–>根 遍历结果:3->2->1
### 2. 第一种解法:递归的解法;递归的解法三种遍历相差不大,因此就写一个先序遍历;递归比较简单直接上代码就可以;以go语言为例子;
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func preorderTraversal(root *TreeNode) []int {
var a []int
doPreorder(root,&a)
return a
}
func doPreorder(root *TreeNode, result *[]int){
if root ==nil{
return
}
*result = append(*result,root.Val)
doPreorder(root.Left,result)
doPreorder(root.Right,result)
}
###3. 递归的解法在go语言里面有两个点需要注意:
-
在leetcoe中要求返回的是一个数组,因此如果直接在该方法中进行递归去return在遇到nil的节点会把数组清空,因此递归不能return,需要开辟一个新的方法去调用;
-
在开辟数组调用传slice的是时候,需要传个指针,这里由于在go中都是值传递,而map、chan、slice比较特殊默认传的是指针,但是如果用了slice的append方法则更为特殊;具体看这篇博客写的:https://www.cnblogs.com/snowInPluto/p/7477365.html
解题思路【非递归解法】
思路的灵感:根据递归操作的解法,
- 是一个从根节点一直到最左子树的叶子节点;
- 然后向前返回;找到右节点
- 重复步骤一
因此很容易想到是一个压栈的操作,一直压倒最左子树的叶子节点,然后从栈顶弹出,判断其是否有右节点,然后重复压栈操作;
开始上代码:
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
// 前序遍历
func preorderTraversal(root *TreeNode) []int {
var result []int
if root==nil{
return result
}
tmpList := list.new()
for root!=nil || tmpList.Len()!=0{
for root!=nil{
// 前序遍历就是入栈的数据,因此在入栈的时候放入到数组当中
result = append(result,root.Val)
tmpList.PushFront(root)
root = root.Left
}
// 从栈中取出一个
tmpNodeInterface := tmpList.Front()
// 取出来的是interface类型转化为TreeNode类型
tmpNodeData :=tmpNodeInterface.Val.(*TreeNode)
tmpList.Remove(tmpNodeInterface)
root = tmpNodeData.Right
}
return result
}
// 中序遍历;
中序遍历的思路和先序遍历的思路是一样;有一点区别是就是出栈的顺序
直接上中序遍历的代码:
func preorderTraversal(root *TreeNode) []int {
var result []int
if root==nil{
return result
}
tmpList := list.new()
for root!=nil || tmpList.Len()!=0{
for root!=nil{
tmpList.PushFront(root)
root = root.Left
}
// 从栈中取出一个
tmpNodeInterface := tmpList.Front()
// 取出来的是interface类型转化为TreeNode类型
tmpNodeData :=tmpNodeInterface.Val.(*TreeNode)
// 中序遍历就是出栈的数据,因此在入栈的时候放入到数组当中
result = append(result,tmpNodeData.Val)
tmpList.Remove(tmpNodeInterface)
root = tmpNodeData.Right
}
return result
}
// 后序遍历的思路跟先序和中序遍历的思路是有区别的
先上代码再分析
func preorderTraversal(root *TreeNode) []int {
var result []int
if root==nil{
return result
}
tmpList := list.new()
var pre *TreeNode
for root!=nil || tmpList.Len()!=0{
for root!=nil{
tmpList.PushFront(root)
root = root.Left
}
// 从栈中取出一个
tmpNodeInterface := tmpList.Front()
// 取出来的是interface类型转化为TreeNode类型
tmpNodeData :=tmpNodeInterface.Val.(*TreeNode)
// 这里右两种情况
// 1. 如果没有右节点则直接出栈
// 2. 如有有右节点,且右节点已经被访问过了,则这时候才能出栈
if tmpNodeData.Right==nil || tmpNodeData.Right == pre{
result = append(result,tmpNodeData.Val)
tmpList.Remove(tmpNodeInterface)
pre = tmpNodeData
}else{
root = tmpNodeData.Right
}
}
return result
}
分析:后续遍历是先访问左子树,再回退到根节点,但是回退到根节点不是立马访问根节点,而是先访问右子树,访问右子树完成后再访问根节点。
因此
第一点:在出栈的时候遇到没有右子树的节点则直接出栈。
第二点:在出栈的时候遇到右子树的节点,则不能直接出栈,需要继续他的右子树;因此我们这里不是直接pop,这是要peek一下
第三点:在第二点中右子树看完之后,在回到这个节点时候,这个节点就要出栈了;怎么判断呢
因此这用了 tmpNodeData == pre 然后 pre = tmpNodeData 用来判断有右节点,且右节点已经访问过了