- 代码随想录 (programmercarl.com)_二叉树理论基础
- 需额外注意用数组表示二叉树。如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
- 代码随想录 (programmercarl.com)——二叉树递归遍历
-
前中后序
- 前中后,其实指的就是中间节点的遍历顺序,只要大家记住 前中后序指的就是中间节点的位置就可以了。
- 这种遍历方式属于dfs深度优先搜索
-
递归遍历
-
递归代码写法三要素
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
-
代码
- 前序
-
func preorderTraversal(root *TreeNode) (res []int) {
var traversal func(node *TreeNode)
traversal = func(node *TreeNode) {
if node == nil {
return
}
res = append(res,node.Val)
traversal(node.Left)
traversal(node.Right)
}
traversal(root)
return res
}
- 中序
-
func inorderTraversal(root *TreeNode) (res []int) {
var traversal func(node *TreeNode)
traversal = func(node *TreeNode) {
if node == nil {
return
}
traversal(node.Left)
res = append(res,node.Val)
traversal(node.Right)
}
traversal(root)
return res
}
- 后序
-
func postorderTraversal(root *TreeNode) (res []int) {
var traversal func(node *TreeNode)
traversal = func(node *TreeNode) {
if node == nil {
return
}
traversal(node.Left)
traversal(node.Right)
res = append(res,node.Val)
}
traversal(root)
return res
}
-
迭代遍历
- 代码随想录 (programmercarl.com)
- 由于递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数。我们可以直接用栈的方式来弹出参数。
- 需要注意前中后序的遍历需要构建不同的逻辑
-
前序遍历
-
func preorderTraversal(root *TreeNode) []int {
ans := []int{}
if root == nil {
return nil
}
st := list.New()
st.PushBack(root)
for st.Len()>0 {
node := st.Remove(st.Back()).(*TreeNode)
ans = append(ans, node.Val)
if node.Right != nil {
st.PushBack(node.Right)
}
if node.Left != nil {
st.PushBack(node.Left)
}
}
return ans
}
-
中序
- 中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
- 那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
-
-
后序
- 先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后再反转
-
func postorderTraversal(root *TreeNode) []int {
ans := []int{}
if root == nil {
return ans
}
st := list.New()
st.PushBack(root)
for st.Len() > 0 {
node := st.Remove(st.Back()).(*TreeNode)
ans = append(ans, node.Val)
if node.Left != nil {
st.PushBack(node.Left)
}
if node.Right != nil {
st.PushBack(node.Right)
}
}
reverse(ans)
return ans
}
func reverse(a []int) {
l, r := 0, len(a) - 1
for l < r {
a[l], a[r] = a[r], a[l]
l, r = l+1, r-1
}
}
-
统一迭代法
- 代码随想录 (programmercarl.com)
- 使用栈的话,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。
- 如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法
-
代码
- 前序
-
func preorderTraversal(root *TreeNode) []int {
if root == nil {
return nil
}
var stack = list.New()
res:=[]int{}
stack.PushBack(root)
var node *TreeNode
for stack.Len()>0{
e := stack.Back()
stack.Remove(e)
if e.Value==nil{
e=stack.Back()
stack.Remove(e)
node=e.Value.(*TreeNode)
res=append(res,node.Val)
continue
}
node = e.Value.(*TreeNode)
if node.Right!=nil{
stack.PushBack(node.Right)
}
if node.Left!=nil{
stack.PushBack(node.Left)
}
stack.PushBack(node)
stack.PushBack(nil)
}
return res
}
- 中序
-
func inorderTraversal(root *TreeNode) []int {
if root==nil{
return nil
}
stack:=list.New()
res:=[]int{}
stack.PushBack(root)
var node *TreeNode
for stack.Len()>0{
e := stack.Back()
stack.Remove(e)
if e.Value==nil{
e=stack.Back()
stack.Remove(e)
node=e.Value.(*TreeNode)
res=append(res,node.Val)
continue
}
node = e.Value.(*TreeNode)
if node.Right!=nil{
stack.PushBack(node.Right)
}
stack.PushBack(node)
stack.PushBack(nil)
if node.Left!=nil{
stack.PushBack(node.Left)
}
}
return res
}
- 后序
-
func postorderTraversal(root *TreeNode) []int {
if root == nil {
return nil
}
var stack = list.New()
res:=[]int{}
stack.PushBack(root)
var node *TreeNode
for stack.Len()>0{
e := stack.Back()
stack.Remove(e)
if e.Value==nil{
e=stack.Back()
stack.Remove(e)
node=e.Value.(*TreeNode)
res=append(res,node.Val)
continue
}
node = e.Value.(*TreeNode)
stack.PushBack(node)
stack.PushBack(nil)
if node.Right!=nil{
stack.PushBack(node.Right)
}
if node.Left!=nil{
stack.PushBack(node.Left)
}
}
return res
}
- 我们此时只需要调换两行代码的顺序了。