定义
树是一种非线性表结构,是n(>=0)个节点的有限集。当n=0是,称之为空树。
任何一颗非空树有以下两个特征
- 有且只有一个称为root的节点,即根节点
- 其余节点可以划分为m(>=0)个互不相交有限集,称之为root的子树
基本概念
度
- 节点的度:节点拥有的子树的数量
- 树的度:所有节点的度的最大值
节点分类
- 根节点:非空树的第一个节点
- 叶子节点:度为0的节点
- 内部节点:除了根节点和叶子节点之外的节点
节点的关系
- parant:双亲节点
- child:子节点,节点子树的根,该节点即为双亲节点
- sibling:兄弟节点,双亲节点一样的节点
- 祖先节点:从根节点到该节点经历的任一节点
- 子孙节点:以该节点为为根的子树中的任一节点
高度
- 节点的高度:节点到叶子节点的最长路径(即边的个数)
- 树的高度:根节点的高度
深度
- 节点的深度:根节点到这个节点所经历的边的个数
- 树的深度:叶子结点的深度
层
节点的深度+1
二叉树
节点的度最大为2的树
满二叉树
叶子节点全部在最底层,除了叶子节点,每个节点的度都为2
完全二叉树
叶子节点全部在最底下两层,最后一层叶子节点向左对齐,除了最后一层,其它层的节点个数必须达到最大
实现
之前说过,所有的高级数据结构都可以使用数组和链表实现,树也不例外。
实际一般使用链表来实现树。
基于数组
以二叉为例子,设根节点索引为i,左子节点为il,右子节点为ir,则有
当 i = 0 时
il = 2 * i + 1
ir = 2 * i + 2
根据这个规律,每个节点都可以在数组中找到唯一的一个位置存储。
这种方式存在一个缺点,当二叉树比较稀疏时,比较浪费空间。换句话说,满二叉树和完全二叉树非常适合使用数组实现,可以完全利用数组空间。
基于链表
以二叉树为例
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
算法
接下来为了方便代码实现,以链表实现的二叉树为例
深度优先遍历
树的深度优先遍历包括:前序、中序、后序遍历。
深度优先遍历就是一个递归问题,以前序遍历为例,可以按以下步骤写出递归代码。
- 递归公式
preOrderTravel = node.Val + preOrderTravel(node.Left) + preOrderTravel(node.Right)
- 终止条件
node == nil
- 翻译成代码
//前序
func preOrderTravel(root *TreeNode) {
if root == nil {
return
}
val := root.Val
//do something
preOrderTravel(root.Left)
preOrderTravel(root.Right)
}
//-------------------------------------
//中序
func preOrderTravel(root *TreeNode) {
if root == nil {
return
}
preOrderTravel(root.Left)
val := root.Val
//do something
preOrderTravel(root.Right)
}
//后序
func preOrderTravel(root *TreeNode) {
if root == nil {
return
}
preOrderTravel(root.Left)
preOrderTravel(root.Right)
val := root.Val
//do something
}
广度优先遍历
树的广度优先遍历一般称之为层次遍历,层次遍历需要借助队列来实现。
func levelTravel(root *TreeNode) [][]int{
ans := [][]int{}
queue := []int{}
if root == nil {
queue = append(queue, root)
}
for len(queue) > 0 {
size := len(queue)
item := []int{}
for size > 0 {
node := queue[0]
queue := queue[1:]
item = append(item, node.Val)
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
size -= 1
}
ans = append(ans, item)
}
return ans
}
查找节点
基于遍历算法,基于先序遍历的实现如下
func find(root *TreeNode, val int) *TreeNode {
var ans *TreeNode
var dfs func(*TreeNode)
dfs = func(node *TreeNode) {
//当访问到叶子节点或者已经找到目标节点时,直接返回
if node == nil || ans != nil {
return
}
if node.Val == val {
ans = node
return
}
dfs(node.Left)
dfs(node.Right)
}
dfs(root)
return ans
}
插入/删除节点
节点间没有规则的树,讨论插入/删除节点操作并没有什么意义。
拓展问题
如何通过非递归的方式实现二叉树的前中后序遍历
所有递归代码,理论上都可以通过手动模拟入栈出栈的方式转化成循环迭代代码。
- 时间复杂度:O(n)
- 空间复杂度:O(n)
//前序遍历
func preorderTraval(root *TreeNode) []int {
ans := []int{}
stack := []*TreeNode{}
node := root
for node != nil || len(stack) > 0 {
for node != nil {
ans = append(ans, node.Val)
stack = append(stack, node)
node = node.Left
}
node = stack[len(stack)-1]
stack = stack[:len(stack)-1]
node = node.Right
}
return ans
}
//中序遍历
func inorderTraval(root *TreeNode) []int {
ans := []int{}
stack := []*TreeNode{}
node := root
for node != nil || len(stack) > 0 {
for node != nil {
stack = append(stack, node)
node = node.Left
}
node = stack[len(stack)-1]
stack = stack[:len(stack)-1]
ans = append(ans, node.Val)
node = node.Right
}
return ans
}
//后序遍历
func postorderTraval(root *TreeNode) []int {
ans := []int{}
stack := []*TreeNode{}
node := root
var pre *TreeNode
for node != nil || len(stack) > 0 {
for node != nil {
stack = append(stack, node)
node = node.Left
}
node = stack[len(stack)-1]
stack = stack[:len(stack)-1]
if node.Right == nil || node.Right == pre {
ans = append(ans, node.Val)
pre = node
node = nil
} else {
stack = append(stack, node)
node = node.Right
}
}
return ans
}
如何求二叉树的最大深度
基于深度优先、深度优先遍历都可以求得二叉树的最大深度。
//深度优先
func maxDepth(root *TreeNode) int {
if root == nil {
return 0
}
return 1 + max(maxDepth(root.Left), maxDepth(root.Right))
}
func max(a, b int) {
if a < b {
return b
}
return a
}
//广度优先,这里只求深度,可以使用一个变量来替代队列
func maxDepth(root *TreeNode) int {
ans := 0
queue := []int{}
if root == nil {
queue = append(queue, root)
}
for len(queue) > 0 {
size := len(queue)
for size > 0 {
node := queue[0]
queue := queue[1:]
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
size -= 1
}
ans += 1
}
return ans
}