目录
一.基础
1. 定义
2. 二叉树的特点
3. 二叉树的类型
(1) 满二叉树:
(2) 完全二叉树:
(3) 斜二叉树:
(4) 二叉搜索树(Binary Search Tree,BST)
(5)平衡二叉搜索树(Balanced Binary Search Tree)
4. 二叉树的遍历
(1)深度优先遍历
(2)广度优先遍历
5. 二叉树的存储结构
• 顺序存储结构
• 链式存储结构
6. 二叉树的性质
7. 二叉树的构建
5. 二叉树的应用
二.拓展
1. 二叉树的操作及算法复杂度
2. 二叉树的平衡问题
3.Java中的二叉树实现
(1). 定义二叉树节点类
(2). 构建二叉树
(3). 二叉树的遍历实现
• 前序遍历:
• 中序遍历:
• 后序遍历:
(4). 其他操作
一.基础
1. 定义
二叉树是一种树形结构,它是每个节点最多有两个子树的树结构,通常子树被称作“左子树”和“右子树”。
2. 二叉树的特点
a.递归性:二叉树的定义是递归的,即二叉树可以用其自身来定义。一个二叉树要么为空,要么由一个根节点、一棵左子树和一棵右子树组成,左子树和右子树也都是二叉树。
b.节点结构:二叉树中的每个节点包含数据元素以及指向左子树和右子树的指针(如果存在的话)。
3. 二叉树的类型
(1) 满二叉树:
如果一棵二叉树的所有叶节点都在同一层,并且每个非叶节点都有两个子节点,那么这棵二叉树就是满二叉树。例如:
(2) 完全二叉树:
1. 定义
• 完全二叉树是一种特殊的二叉树。设二叉树的深度为h(根节点所在层为第1层)。
• 对于完全二叉树,除了第h层外,其它各层的节点数都达到最大个数,即第i层有(2的i-1次方)个节点。
• 在第h层上的节点都集中在最左边,这意味着第h层节点是从左向右连续存在的。
2. 示例
• 例如,下面这棵二叉树就是完全二叉树:
1
/ \
2 3
/ \ /
4 5 6
• 而下面这棵二叉树不是完全二叉树:
1
/ \
2 3
\ \
4 5
• 因为在第二层中,节点2的右子树节点4的位置不符合完全二叉树的要求(完全二叉树要求节点是按层序从左到右依次排列的)。
(3) 斜二叉树:
是一种特殊的二叉树,所有节点都只有左子树或者所有节点都只有右子树。例如,所有节点都只有左子树的斜二叉树:
1
\
2
\
3
\
4
(4) 二叉搜索树(Binary Search Tree,BST)
(a)定义
二叉搜索树是一种特殊的二叉树,它满足以下性质:对于树中的任意一个节点,其左子树中的所有节点的值都小于该节点的值,其右子树中的所有节点的值都大于该节点的值。
(b)节点插入操作
插入一个新节点时,首先从根节点开始比较。如果新节点的值小于当前节点的值,则向左子树方向继续比较;如果新节点的值大于当前节点的值,则向右子树方向继续比较。直到找到合适的空位置插入新节点。
(c)节点查找操作
查找一个值时,也是从根节点开始。如果要查找的值等于当前节点的值,则查找成功;如果要查找的值小于当前节点的值,则在左子树中继续查找;如果要查找的值大于当前节点的值,则在右子树中继续查找。如果最终未找到匹配的节点,则查找失败。
(d)节点删除操作
情况较为复杂。如果要删除的节点是叶节点(没有子节点),直接删除即可;如果要删除的节点只有一个子节点,那么将这个子节点取代被删除节点的位置;如果要删除的节点有两个子节点,通常的做法是用该节点右子树中的最小值(即右子树中最左边的节点)或者左子树中的最大值(即左子树中最右边的节点)来替换被删除的节点,然后再删除用于替换的那个节点。
(e)时间复杂度
在理想情况下,二叉搜索树的高度为 O(logn)(n为树中节点个数),此时查找、插入和删除操作的时间复杂度都是 O(logn)。但在最坏情况下,例如当二叉搜索树退化为斜二叉树(所有节点都只有左子树或者所有节点都只有右子树)时,树的高度为O(n),这些操作的时间复杂度也会退化为O(n)。
(5)平衡二叉搜索树(Balanced Binary Search Tree)
(a)定义
平衡二叉搜索树是一种特殊的二叉搜索树,它在满足二叉搜索树性质的基础上,还保证树的高度是相对平衡的。不同类型的平衡二叉搜索树有不同的平衡定义。
(b)目的
为了克服二叉搜索树在最坏情况下时间复杂度退化为O(n)的问题,平衡二叉搜索树通过自动调整树的结构来保持树的高度在 O(logn)范围内,从而保证查找、插入和删除操作的时间复杂度始终为 O(logn)。
(c)常见类型
• AVL树
平衡因子:AVL树中每个节点都有一个平衡因子,定义为左子树高度减去右子树高度的值,这个值只能是0、1或者-1。
调整操作:当插入或删除节点导致平衡因子超出这个范围时,就需要通过旋转操作(左旋、右旋、先左旋后右旋、先右旋后左旋)来重新平衡树。例如,在插入一个节点后,如果某个节点的平衡因子变为2或-2,就需要进行调整。
• 红黑树
节点颜色属性:红黑树的每个节点除了有数据域、左指针域、右指针域外,还有一个颜色属性,节点颜色可以是红色或者黑色。
红黑性质:
根节点是黑色的。
每个叶节点(空节点)是黑色的。
如果一个节点是红色的,则它的两个子节点都是黑色的(从每个叶子到根的所有路径上不能有两个连续的红色节点)。
从任一节点到其每个叶节点的所有路径上包含相同数目的黑色节点。
调整操作:当插入或删除节点破坏了红黑性质时,需要通过变色和旋转操作来恢复红黑性质,从而保持树的平衡。
4. 二叉树的遍历
(1)深度优先遍历
• 前序遍历:先访问根节点,然后前序遍历左子树,再前序遍历右子树。例如对于二叉树:
1
/ \
2 3
/ \ / \
4 5 6 7
前序遍历的结果是:1、2、4、5、3、6、7。
• 中序遍历:先中序遍历左子树,然后访问根节点,再中序遍历右子树。上述二叉树的中序遍历结果是:4、2、5、1、6、3、7。
• 后序遍历:先后序遍历左子树,然后后序遍历右子树,最后访问根节点。该二叉树的后序遍历结果是:4、5、2、6、7、3、1。
(2)广度优先遍历
• 层序遍历:按层次顺序,从根节点开始,逐层向下遍历二叉树,同一层的节点按照从左到右的顺序访问。上述二叉树的层序遍历结果是:1、2、3、4、5、6、7。
5. 二叉树的存储结构
• 顺序存储结构
对于完全二叉树,可以采用顺序存储结构。将二叉树的节点按照从上到下、从左到右的顺序依次存储在数组中。
节点在数组中的下标具有一定的规律,设根节点的下标为0,如果某个节点的下标为i,那么它的左子节点的下标为2i + 1,右子节点的下标为2i+2。
但是这种存储方式对于非完全二叉树会造成空间浪费,因为要保证逻辑上二叉树的结构,可能需要用空节点来填补数组中的某些位置。
• 链式存储结构
二叉链表:每个节点包含三个域,数据域、左指针域和右指针域。左指针指向该节点的左子节点,右指针指向该节点的右子节点。这种结构简单灵活,是二叉树最常用的存储方式。
三叉链表:在二叉链表的基础上增加一个父指针域,用于指向该节点的父节点。这种结构在某些需要回溯到父节点的操作中比较方便,例如在计算二叉树的高度、求最近公共祖先等操作中。
6. 二叉树的性质
• 二叉树第i层上的最大节点数为 (2的i-1次方(i>=1))。
• 深度为k的二叉树的最大节点数为 (2的k次方-1(k>=1))。
• 对任何一棵二叉树,如果其叶节点个数为n0,度为2的节点个数为n2,则n0 = n2 + 1。可以通过分析二叉树中节点的度数关系(总度数+1 = 节点总数)来推导这个性质。
7. 二叉树的构建
可以根据给定的遍历序列来构建二叉树。但是需要注意的是,仅通过一种遍历序列(如中序遍历、前序遍历或后序遍历中的一种)不能唯一确定一棵二叉树。但是如果已知中序遍历序列和前序遍历序列,或者中序遍历序列和后序遍历序列,则可以唯一确定一棵二叉树。
• 例如,已知前序遍历序列和中序遍历序列构建二叉树的过程:
• 首先,前序遍历的第一个节点为根节点。
• 在中序遍历中,将序列分为两部分,左边是左子树的中序遍历序列,右边是右子树的中序遍历序列。
• 在前序遍历中,是左子树的前序遍历序列,是右子树的前序遍历序列。
• 然后分别对左子树和右子树递归地进行构建。
5. 二叉树的应用
在数据结构和算法中广泛应用,例如用于构建二叉搜索树(BST),以实现高效的查找、插入和删除操作;在表达式树中表示算术表达式;在哈夫曼树中用于数据压缩等。
二.拓展
1. 二叉树的操作及算法复杂度
(1) 插入节点
在二叉链表存储的二叉树中插入节点操作相对复杂,需要考虑插入的位置(作为叶节点插入或者替换某个节点等情况)。如果插入节点的操作是作为叶节点插入到二叉搜索树(一种特殊的二叉树)中,平均时间复杂度为O(logn)(为树中节点个数),最坏情况(树退化为斜二叉树)为O(n)。
(2) 删除节点
同样在二叉搜索树中,删除节点根据节点的度数(0度、1度或2度)不同有不同的处理方式。平均时间复杂度为O(logn),最坏情况为O(n)。
(3) 查找节点
在二叉搜索树中,根据节点的值进行查找。平均时间复杂度为O(logn),最坏情况为O(n)。如果是普通二叉树,查找操作可能需要遍历整棵树,时间复杂度为O(n)。
2. 二叉树的平衡问题
• 在二叉搜索树中,如果插入和删除操作不当,可能会导致树变得不平衡,例如形成斜二叉树,这会使查找、插入和删除操作的时间复杂度退化为O(n)。
• 为了解决这个问题,有多种平衡二叉树的算法,如AVL树和红黑树。AVL树通过调整节点的平衡因子(左子树高度与右子树高度之差),在插入和删除节点时进行旋转操作(左旋、右旋、先左旋后右旋、先右旋后左旋)来保持树的平衡,使得树的高度始终保持在O(logn),从而保证操作的时间复杂度为O(logn)。红黑树则通过定义节点的颜色(红色或黑色),并在插入和删除节点时通过变色和旋转操作来保持树的平衡。
3.Java中的二叉树实现
(1). 定义二叉树节点类
• 首先,需要定义二叉树的节点类,节点类包含节点的值以及指向左子节点和右子节点的引用。• 以下是一个简单的Java代码示例:
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
this.left = null;
this.right = null;
}
}
(2). 构建二叉树
• 可以通过手动创建节点并连接它们来构建二叉树。例如,构建一个简单的二叉树:
public class BinaryTreeExample {
public static void main(String[] args) {
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
}
}
(3). 二叉树的遍历实现
• 前序遍历:
• 递归实现:
public static void preorderTraversal(TreeNode root) {
if (root!= null) {
System.out.print(root.val + " ");
preorderTraversal(root.left);
preorderTraversal(root.right);
}
}
• 非递归实现(使用栈):
public static void preorderTraversalNonRecursive(TreeNode root) {
if (root == null) {
return;
}
java.util.Stack<TreeNode> stack = new java.util.Stack<>();
stack.push(root);
while (!stack.empty()) {
TreeNode node = stack.pop();
System.out.print(node.val + " ");
if (node.right!= null) {
stack.push(node.right);
}
if (node.left!= null) {
stack.push(node.left);
}
}
}
• 中序遍历:
• 递归实现:
public static void inorderTraversal(TreeNode root) {
if (root!= null) {
inorderTraversal(root.left);
System.out.print(root.val + " ");
inorderTraversal(root.right);
}
}
• 非递归实现(使用栈):
public static void inorderTraversalNonRecursive(TreeNode root) {
if (root == null) {
return;
}
java.util.Stack<TreeNode> stack = new java.util.Stack<>();
TreeNode current = root;
while (current!= null ||!stack.empty()) {
while (current!= null) {
stack.push(current);
current = current.left;
}
current = stack.pop();
System.out.print(current.val + " ");
current = current.right;
}
}
• 后序遍历:
• 递归实现:
public static void postorderTraversal(TreeNode root) {
if (root!= null) {
postorderTraversal(root.left);
postorderTraversal(root.right);
System.out.print(root.val + " ");
}
}
• 非递归实现(使用栈和一个额外的变量记录上一次访问的节点):
public static void postorderTraversalNonRecursive(TreeNode root) {
if (root == null) {
return;
}
java.util.Stack<TreeNode> stack = new java.util.Stack<>();
TreeNode lastVisited = null;
while (root!= null ||!stack.empty()) {
while (root!= null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
// 如果右子节点为空或者已经访问过,则访问该节点
if (root.right == null || root.right == lastVisited) {
System.out.print(root.val + " ");
lastVisited = root;
root = null;
} else {
stack.push(root);
root = root.right;
}
}
}
(4). 其他操作
• 例如计算二叉树的高度:
public static int getHeight(TreeNode root) {
if (root == null) {
return 0;
}
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
return Math.max(leftHeight, rightHeight)+1;
}