【数据结构】【二叉树】先序中序后序遍历解题报告

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

解题思路【非递归解法】

思路的灵感:根据递归操作的解法,

  1. 是一个从根节点一直到最左子树的叶子节点;
  2. 然后向前返回;找到右节点
  3. 重复步骤一
    因此很容易想到是一个压栈的操作,一直压倒最左子树的叶子节点,然后从栈顶弹出,判断其是否有右节点,然后重复压栈操作;
    开始上代码:
/**
 * 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 用来判断有右节点,且右节点已经访问过了

猜你喜欢

转载自blog.csdn.net/weixin_38024463/article/details/107424211