二叉树
二叉树是一种非线性数据结构,代表“祖先”与“后代”之间的派生关系,体现了“一分为二”的分治逻辑。与链表类似,二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子节点引用。
/* 二叉树节点结构体 */
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
/* 构造方法 */
func NewTreeNode(v int) *TreeNode {
return &TreeNode{
Left: nil, // 左子节点指针
Right: nil, // 右子节点指针
Val: v, // 节点值
}
}
每个节点都有两个指针,分别指向左子节点和右子节点,该节点被称为这两个子节点的父节点。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的左子树,同理可得右子树。
在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树
二叉树常见术语
根节点:位于二叉树顶层的节点,没有父节点。 叶节点:没有子节点的节点,其两个指针均指向 None 。 边:连接两个节点的线段,即节点指针。 节点所在的层:从顶至底递增,根节点所在层为 1 。 节点的度:节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。 二叉树的高度:从根节点到最远叶节点所经过的边的数量。 节点的深度:从根节点到该节点所经过的边的数量。 节点的高度:从距离该节点最远的叶节点到该节点所经过的边的数量。
二叉树基本操作
初始化二叉树
与链表类似,首先初始化节点,然后构建指针
/* 初始化二叉树 */
// 初始化节点
n1 := NewTreeNode(1)
n2 := NewTreeNode(2)
n3 := NewTreeNode(3)
n4 := NewTreeNode(4)
n5 := NewTreeNode(5)
// 构建节点之间的引用(指针)
n1.Left = n2
n1.Right = n3
n2.Left = n4
n2.Right = n5
插入与删除节点
与链表类似,在二叉树中插入与删除节点可以通过修改指针来实现
/* 插入与删除节点 */
// 在 n1 -> n2 中间插入节点 P
p := NewTreeNode(0)
n1.Left = p
p.Left = n2
// 删除节点 P
n1.Left = n2
常见二叉树类型
完美二叉树:所有层的节点都被完全填满
完全二叉树:仅允许最底层的节点不完全填满,且最底层的节点必须从左至右依次连续填充。完美二叉树也是一棵完全二叉树
完满二叉树:除了叶节点之外,其余所有节点都有两个子节点
平衡二叉树:任意节点的左子树和右子树的高度之差的绝对值不超过 1
二叉树的退化
二叉树的每层节点都被填满时,达到“完美二叉树”;当所有节点都偏向一侧时,二叉树退化为“链表“
- 完美二叉树是理想情况,可以充分发挥二叉树“分治”的优势。
- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至O(n) 。