目录
代码随想录day14
理论基础:
二叉树的递归遍历
144、二叉树的前序遍历
145、二叉树的后序遍历
94、二叉树的中序遍历
前中后序的迭代遍历
在我们解题过程中二叉树有两种主要的形式:满二叉树和完全二叉树
满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
这棵二叉树为满二叉树,也可以说深度为k,有(2^k) -1个节点的二叉树。
完全二叉树:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
我来举一个典型的例子如图:
二叉搜索树:
前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。
1、若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2、若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3、它的左、右子树也分别为二叉排序树
下面这两棵树都是搜索树
平衡二叉树:
又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。如图:
最后一棵 不是平衡二叉树,因为它的左右两个子树的高度差的绝对值超过了1。
二叉树的存储方式:
二叉树可以链式存储也可以顺序存储。那么链式存储方式就用指针, 顺序存储的方式就是用数组。顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在散落在各个地址的节点串联一起。
链式存储如图:
链式存储是大家很熟悉的一种方式,那么我们来看看如何顺序存储呢?其实就是用数组来存储二叉树,顺序存储的方式如图:
用数组来存储二叉树如何遍历的呢?如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
但是用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。所以大家要了解,用数组依然可以表示二叉树
二叉树的遍历方式:
二叉树主要有两种遍历方式:
这两种遍历是图论中最基本的两种遍历方式,后面在介绍图论的时候 还会介绍到。那么从深度优先遍历和广度优先遍历进一步拓展,才有如下遍历方式:
看如下中间节点的顺序,就可以发现,中间节点的顺序就是所谓的遍历方式
大家可以对着如下图,看看自己理解的前后中序有没有问题。
最后再说一说二叉树中深度优先和广度优先遍历实现方式,我们做二叉树相关题目,经常会使用递归的方式来实现深度优先遍历,也就是实现前中后序遍历,使用递归是比较方便的。
而广度优先遍历(层序遍历)的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
二叉树的定义:
刚刚我们说过了二叉树有两种存储方式顺序存储,和链式存储,顺序存储就是用数组来存,这个定义没啥可说的,我们来看看链式存储的二叉树节点的定义方式。
type TreeNode struct {
val int
left *TreeNode
right *TreeNode
}
大家会发现二叉树的定义 和链表是差不多的,相对于链表 ,二叉树的节点里多了一个指针, 有两个指针,指向左右孩子。
我们说了,递归遍历三种,分别是前序遍历、中序遍历、后序遍历。下面我们分别来看三道题目。
这里帮助大家确定下来递归算法的三个要素。每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
1、确定递归函数的参数和返回值:确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
2、确定终止条件:写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
3、确定单层递归的逻辑:确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
好了我们来练练手,以下以前序遍历为例:
1、确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入vector在放节点的数值,除了这一点就不需要在处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
func preorderTraversal(root *TreeNode) []int {
其他操作
}
2、确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
if root == nil {
return
}
3、确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
res = append(res, node.Val)
traversal(node.Left)
traversal(node.Right)
这样单层递归的逻辑就是按照中左右的顺序来处理的,这样二叉树的前序遍历,基本就写完了,再看一下完整代码:
var res []int
func preorderTraversal(root *TreeNode) []int {
res = []int{}
traversal(root)
return res
}
func traversal(node *TreeNode) {
if node == nil { //2.递归终止条件
return
}
res = append(res, node.Val) //3.处理单层逻辑
traversal(node.Left)
traversal(node.Right)
}
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
输入:root = [1,null,2,3]
输出:[1,2,3]
var res []int
func preorderTraversal(root *TreeNode) []int {
res = []int{}
traversal(root)
return res
}
func traversal(node *TreeNode) { // 1.确定参数及返回值
if node == nil { //2.递归终止条件
return
}
res = append(res, node.Val) //3.处理单层逻辑
traversal(node.Left)
traversal(node.Right)
}
给你二叉树的根节点 root ,返回它节点值的 后序 遍历。
示例1:
输入:root = [1,null,2,3]
输出:[3,2,1]
var res []int
func postorderTraversal(root *TreeNode) []int {
res = []int{}
traversal(root)
return res
}
func traversal(root *TreeNode) { // 1.确定递归参数及返回参数
if root == nil { // 2.递归终止条件
return
}
traversal(root.Left) //3.单层递归逻辑
traversal(root.Right) //3.单层递归逻辑
res = append(res, root.Val) //3.单层递归逻辑
}
给你二叉树的根节点 root ,返回它节点值的 中序 遍历。
示例1:
输入:root = [1,null,2,3]
输出:[1,3,2]
var res []int
func inorderTraversal(root *TreeNode) []int {
res = []int{}
traversal(root)
return res
}
func traversal(root *TreeNode) { // 1、确定返回值及其参数
if root == nil { // 2、确定递归终止条件
return
}
traversal(root.Left) // 3、单层递归逻辑
res = append(res, root.Val) // 3、单层递归逻辑
traversal(root.Right) // 3、单层递归逻辑
}
以上就是所有内容啦,其实前中后序的迭代遍历法,是用栈实现的,但是我偷懒了。我先不学,等后续可能学学~
暂略。