代码随想录 day14 第六章 二叉树part01

今日内容:

●  理论基础

●  递归遍历

●  迭代遍历

●  统一迭代

1. 理论基础

  • 二叉树的种类

    • 两种主要形式:
      1. 满二叉树

        1. 定义:

          1. 满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
          2. 度:叶子结点
          3. 深度:树的层数:k;则满二叉树共有 2^k-1 个节点
          4. 如下图所示【图片来自代码随想录官网】
      2. 完全二叉树

        1. 定义

          1. 完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
          2. 上面和满二叉树一样的,最下一层先把左边填满的二叉树
          3. 如下图所示【图片来自代码随想录官网】
      3. 二叉搜索树【扩展】

        • 二叉搜索树是有数值的了,二叉搜索树是一个有序树

          • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
          • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
          • 它的左、右子树也分别为二叉排序树
        • 图片示例【来自代码随想录官网】

      4. 平衡二叉搜索树【扩展】

        • 平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

        • 图片示例【来自代码随想录官网】

  • 二叉树的存储方式

    1. 链式存储

      1. 指针方式

      2. 在内存中是散列分布的

      3. 图片示例【来自代码随想录官网】

    2. 顺序存储

      1. 数组方式

      2. 在内存中是连续分布的

      3. 节点下标间关系

        1. 父节点 Index:i
          1. 左孩子 Index:i*2+1
          2. 有孩子 Index:i*2+2
      4. 图片示例【来自代码随想录官网】

  • 遍历方式

    • 深度优先遍历
      • 先往深走,遇到叶子节点再往回走【从上往下直到走不动】
      • 遍历方式【指的是 中间节点 所在的位置,左右永远是左右】
        • 前序遍历(递归法,迭代法)【中左右】
        • 中序遍历(递归法,迭代法)【左中右】
        • 后序遍历(递归法,迭代法)【左右中】
    • 广度优先遍历
      • 一层一层的去遍历。【每层从左到右走完】
      • 遍历方式
        • 层次遍历(迭代法)
  • 二叉树的定义

    • 链式存储定义
    • 节点值,左右孩子的指针
    type TreeNode struct {
        Val int
        Left *TreeNode
        Right *TreeNode
    }
    

2. 递归遍历 (必须掌握)

关联 leetcode 144.二叉树的前序遍历

关联 leetcode 145.二叉树的后序遍历

关联 leetcode 94.二叉树的中序遍历

  • 递归三部曲
    1. 确定递归函数的参数和返回值
      1. 没有必要一次性确定,可以逐步确认
      2. 大多数都是 根节点+存放结果的数组
        1. 返回值一般都是 void
          1. 结果直接放在参数里面了
    2. 确定终止条件
    3. 确定单层递归的逻辑
  • 添加处理的都是 cur 当前节点
    • 添加入数组的都是
  • 题解
    • 144.二叉树的前序遍历

      • 写法一
      func traversal(cur *TreeNode, res *[]int) {//每次的res是入参的副本,这里要用引用类型
      	if cur == nil {
      		return
      	}
      	*res = append(*res, cur.Val) //中
      	traversal(cur.Left, res)     //左
      	traversal(cur.Right, res)    //右
      }
      func preorderTraversal(root *TreeNode) []int {
      	res := make([]int, 0)
      	traversal(root, &res)
      	return res
      }
      
      • 写法二【闭包实现】
      func preorderTraversal(root *TreeNode) []int {
      	res := make([]int, 0)
      	var traversal func(cur *TreeNode) 
      	traversal = func(cur *TreeNode) {
      		if cur == nil {
      			return
      		}
      		res = append(res, cur.Val) //中
      		traversal(cur.Left)        //左
      		traversal(cur.Right)       //右
      	}
      	traversal(root)
      	return res
      }
      
    • 145.二叉树的后序遍历

      func postorderTraversal(root *TreeNode) []int {
      	res := make([]int, 0)
      	var traversal func(cur *TreeNode)
      	traversal = func(cur *TreeNode) {
      		if cur == nil {
      			return
      		}
      		traversal(cur.Left)        //左
      		traversal(cur.Right)       //右
      		res = append(res, cur.Val) //中
      	}
      	traversal(root)
      	return res
      }
      
    • 94.二叉树的中序遍历

      func inorderTraversal(root *TreeNode) []int {
      	res := make([]int, 0)
      	var traversal func(cur *TreeNode)
      	traversal = func(cur *TreeNode) {
      		if cur == nil {
      			return
      		}
      		traversal(cur.Left)        //左
      		res = append(res, cur.Val) //中
      		traversal(cur.Right)       //右
      	}
      	traversal(root)
      	return res
      }
      

3. 迭代遍历 (基础不好的录友,迭代法可以放过)

  • 操作系统实现递归:

    • 也是用 这种数据结构实现的
  • 迭代法:【非递归遍历】

    • 迭代法模拟递归
    • 使用栈来实现
    • 需要个人实现一个栈
  • 144.二叉树的前序遍历

     func preorderTraversal(root *TreeNode) []int {
    	ret := make([]int, 0)
    	if root == nil {
    		return ret
    	}
    
    	stack := make([]*TreeNode,0)// 使用数组做栈的底层结构
    	stack = append(stack, root)// 初始化,追加根节点
    
    	for len(stack)!=0 {
    		node:=stack[len(stack)-1]//取得栈顶元素节点[中间节点]
    		val := node.Val
    		ret = append(ret, val)
    		stack=stack[:len(stack)-1]
    
    		// 先压右节点,后左节点,栈:先进后出
    		if node.Right != nil {
    			stack = append(stack, node.Right)
    		}
    		if node.Left != nil {
    			stack = append(stack, node.Left)
    		}
    	}
    	return ret
    }
    
  • 145.二叉树的后序遍历

    • 将前序递归的左右操作互换
      • 得到结果元素序列:—> 中右左
        • 对结果序列整体做一个翻转:—> 左右中
     func postorderTraversal(root *TreeNode) []int {
    	ret := make([]int, 0)
    	if root == nil {
    		return ret
    	}
    
    	stack := make([]*TreeNode,0)// 使用数组做栈的底层结构
    	stack = append(stack, root)// 初始化,追加根节点
    
    	for len(stack)!=0 {
    		node:=stack[len(stack)-1]//取得栈顶元素[中间节点]
    		val := node.Val
    		ret = append(ret, val)
    		stack=stack[:len(stack)-1]
    		// 压栈操作 先左后右,出栈 右左
    		if node.Left != nil {
    			stack = append(stack, node.Left)
    		}
    		if node.Right != nil {
    			stack = append(stack, node.Right)
    		}
    	}
    	//结束循环后,得到结果数组遍历顺序: 中右左
    
    	//整体翻转数组 中右左 --> 左右中
    	slices.Reverse(ret)
    	return ret
    }
    
  • 94.二叉树的中序遍历

    • 遍历的节点和要处理的节点不是同一个节点
      • 不能像上面的 前后序那样简单处理
     func inorderTraversal(root *TreeNode) []int {
    	ret := make([]int, 0)
    	stack := make([]*TreeNode, 0)
    	cur := root
    	for cur != nil || len(stack) != 0 {
    		if cur != nil {
    			stack = append(stack, cur)//当前节点入栈
    			cur=cur.Left//遍历当前节点左节点, 直到最底层最左子节点
    		}else {
    			//当前节点已无左子节点了
    			//此时栈顶的就是当前最底层左子节点
    			cur=stack[len(stack)-1]//取出栈顶元素pop()
    			stack=stack[:len(stack)-1]
    
    			ret = append(ret, cur.Val)//中间节点, 它没有左子节点, 按照顺序: 左,中,右, 处理 中间节点
    			cur=cur.Right// 处理它的右子节点
    		}
    	}
    	return ret
    }
    

4. 统一迭代 (基础不好的录友,迭代法可以放过)

  • 统一迭代法
    • 像递归那样,统一处理节点

      • 注意压入栈的都是节点本身
    • 前序:中左右

      func preorderTraversal(root *TreeNode) []int {
      	rets := make([]int, 0)
      	//使用官方库的链表实现栈
      	stack := list.New()
      	if root != nil {
      		stack.PushBack(root)
      	}
      	//栈不为空
      	for stack.Len() != 0 {
      		e := stack.Back() //取栈顶元素
      		stack.Remove(e)   //弹出该元素, 避免重复操作
      
      		if e.Value != nil {
      			//只用变动这里的压栈顺序即可
      			//	该节点的右中左节点添加到栈中
      			midNode := e.Value.(*TreeNode)
      
      			//出栈顺序: 中左右
      			//压栈顺序: 右左中
      			rightSon := midNode.Right
      			if rightSon != nil {
      				stack.PushBack(rightSon)
      			}
      			leftSon := midNode.Left
      			if leftSon != nil {
      				stack.PushBack(leftSon)
      			}
      			//要弹出处理的节点,用空节点标识
      			stack.PushBack(midNode)
      			stack.PushBack(nil) //用空节点来标记待操作元素
      
      		} else {
      			//遇到操作标记的空节点
      			//处理节点逻辑不变
      			curNode := stack.Back() // 取出待操作的中间节点
      			stack.Remove(curNode)
      			midNode := curNode.Value.(*TreeNode) // 当前要操作的节点
      			rets = append(rets, midNode.Val)
      		}
      	}
      	return rets
      }
      
    • 中序:左中右

      func inorderTraversal(root *TreeNode) []int {
      	rets := make([]int, 0)
      	//使用官方库的链表实现栈
      	stack := list.New()
      	if root != nil {
      		stack.PushBack(root)
      	}
      	//栈不为空
      	for stack.Len() != 0 {
      		e := stack.Back() //取栈顶元素
      		stack.Remove(e)   //弹出该元素, 避免重复操作
      
      		if e.Value != nil {
      			//只用变动这里的压栈顺序即可
      			//	该节点的右中左节点添加到栈中
      			midNode := e.Value.(*TreeNode)
      
      			//出栈顺序: 左中右
      			//压栈顺序: 右中左
      			rightSon := midNode.Right
      			if rightSon != nil {
      				stack.PushBack(rightSon)
      			}
      			//要弹出处理的节点,用空节点标识
      			stack.PushBack(midNode)
      			stack.PushBack(nil) //用空节点来标记待操作元素
      
      			leftSon := midNode.Left
      			if leftSon != nil {
      				stack.PushBack(leftSon)
      			}
      		} else {
      			//遇到操作标记的空节点
      			//处理节点逻辑不变
      			curNode := stack.Back() // 取出待操作的中间节点
      			stack.Remove(curNode)
      			midNode := curNode.Value.(*TreeNode) // 当前要操作的节点
      			rets = append(rets, midNode.Val)
      		}
      	}
      	return rets
      }
      
    • 后序:左中右

      func postorderTraversal(root *TreeNode) []int {
      
      	rets := make([]int, 0)
      	//使用官方库的链表实现栈
      	stack := list.New()
      	if root != nil {
      		stack.PushBack(root)
      	}
      	//栈不为空
      	for stack.Len() != 0 {
      		e := stack.Back() //取栈顶元素
      		stack.Remove(e)   //弹出该元素, 避免重复操作
      
      		if e.Value != nil {
      			//只用变动这里的压栈顺序即可
      			//	该节点的右中左节点添加到栈中
      			midNode := e.Value.(*TreeNode)
      
      			//出栈顺序: 左右中
      			//压栈顺序: 中右左
      			//要弹出处理的节点,用空节点标识
      			stack.PushBack(midNode)
      			stack.PushBack(nil) //用空节点来标记待操作元素
      			rightSon := midNode.Right
      			if rightSon != nil {
      				stack.PushBack(rightSon)
      			}
      			leftSon := midNode.Left
      			if leftSon != nil {
      				stack.PushBack(leftSon)
      			}
      
      		} else {
      			//遇到操作标记的空节点
      			//处理节点逻辑不变
      			curNode := stack.Back() // 取出待操作的中间节点
      			stack.Remove(curNode)
      			midNode := curNode.Value.(*TreeNode) // 当前要操作的节点
      			rets = append(rets, midNode.Val)
      		}
      	}
      	return rets
      }
      

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值