【数据结构】二叉树全面详解
目录
1. 树型结构基础
1.1 树的基本概念
1.2 树的关键术语
1.3 树的表示形式
1.4 树的应用
2. 二叉树深度解析
2.1 二叉树概念
2.2 两种特殊的二叉树
2.2.1 满二叉树
2.2.2 完全二叉树
2.3 二叉树的性质
2.4 二叉树的存储
2.4.1 顺序存储
2.4.2 链式存储
2.5 二叉树的基本操作
2.5.1 二叉树遍历
2.5.2 其他基本操作
2.6 二叉树相关OJ题目解析
2.6.1 检查两棵树是否相同
2.6.2 判断子树
2.6.3 翻转二叉树
2.6.4 判断平衡二叉树
2.6.5 对称二叉树
2.6.6 二叉树的最近公共祖先
2.6.7 根据前序和中序遍历构建二叉树
3. 二叉树进阶话题
3.1 非递归遍历实现
3.1.1 前序遍历非递归
3.1.2 中序遍历非递归
3.1.3 后序遍历非递归
3.2 二叉树序列化与反序列化
4.常见面试考点
1. 树型结构基础
1.1 树的基本概念
树是一种非线性的数据结构,由n(n≥0)个有限节点组成的有层次关系的集合。它像一棵倒挂的树,根在上,叶在下。
核心特点:
-
有一个特殊的根节点,没有前驱节点
-
除根节点外,其余节点被分成互不相交的子树
-
每棵子树的根节点有且只有一个前驱,可以有多个后继
-
树是递归定义的
重要提示:树形结构中,子树之间不能有交集,否则就不是树形结构。
1.2 树的关键术语
术语 | 说明 | 示例 |
---|---|---|
结点的度 | 一个结点含有子树的个数 | 节点A的度为6 |
树的度 | 树中所有结点度的最大值 | 树的度为6 |
叶子结点 | 度为0的结点 | B、C、H、I等节点 |
父结点 | 含有子结点的结点 | A是B的父结点 |
子结点 | 一个结点含有的子树的根结点 | B是A的子结点 |
根结点 | 没有双亲结点的结点 | A |
结点的层次 | 从根开始定义,根为第1层 | 根节点在第1层 |
树的高度 | 树中结点的最大层次 | 树的高度为4 |
扩展概念:
-
非终端结点:度不为0的结点(D、E、F、G等)
-
兄弟结点:具有相同父结点的结点(B和C)
-
堂兄弟结点:双亲在同一层的结点(H和I)
-
结点的祖先:从根到该结点所经分支上的所有结点
-
子孙:以某结点为根的子树中任一结点
-
森林:由m(m≥0)棵互不相交的树组成的集合
1.3 树的表示形式
最常用的是孩子兄弟表示法:
class TreeNode {int value;TreeNode firstChild; // 第一个孩子节点TreeNode nextSibling; // 下一个兄弟节点
}
1.4 树的应用
-
文件系统管理:目录和文件的层次结构
-
组织架构:公司部门的层级关系
-
家谱系统:家族成员的血缘关系
2. 二叉树深度解析
2.1 二叉树概念
一棵二叉树是结点的有限集合,该集合:
-
或者为空
-
或者由一个根节点加上两棵分别称为左子树和右子树的二叉树组成
重要特性:
-
二叉树不存在度大于2的结点
-
二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
二叉树的基本形态:
-
空树
-
只有根节点
-
根节点+左子树
-
根节点+右子树
-
根节点+左子树+右子树
2.2 两种特殊的二叉树
2.2.1 满二叉树
-
定义:每层的结点数都达到最大值
-
性质:层数为K的满二叉树,结点总数为2^K - 1
-
示例:
A/ \B C/ \ / \D E F G
2.2.2 完全二叉树
-
定义:深度为K的有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应
-
特点:叶子结点只能出现在最下两层,且最底层叶子结点都靠左排列
-
示例:
A/ \B C/ \ /D E F
2.3 二叉树的性质
-
第i层最多有2^(i-1)个结点(i>0)
-
深度为K的二叉树最大结点数是2^K - 1(K≥0)
-
对于任何二叉树:叶子结点数n₀ = 度为2的非叶结点数n₂ + 1
-
具有n个结点的完全二叉树的深度k为⌈log₂(n+1)⌉
-
对于完全二叉树编号为i的结点:
-
父结点序号:(i-1)/2 (i>0)
-
左孩子序号:2i+1 (如果2i+1<n)
-
右孩子序号:2i+2 (如果2i+2<n)
-
2.4 二叉树的存储
2.4.1 顺序存储
使用数组存储,适合完全二叉树:
// 对于完全二叉树,可以用数组高效存储
int[] tree = new int[n];
// 节点i的父节点:tree[(i-1)/2]
// 节点i的左孩子:tree[2*i+1]
// 节点i的右孩子:tree[2*i+2]
2.4.2 链式存储
// 孩子表示法
class Node {int val;Node left; // 左子树Node right; // 右子树
}// 孩子双亲表示法
class Node {int val;Node left;Node right;Node parent; // 父节点
}
2.5 二叉树的基本操作
2.5.1 二叉树遍历
1. 前序遍历(NLR)
void preOrder(Node root) {if (root == null) return;System.out.print(root.val + " "); // 访问根节点preOrder(root.left); // 遍历左子树preOrder(root.right); // 遍历右子树
}
2. 中序遍历(LNR)
void inOrder(Node root) {if (root == null) return;inOrder(root.left); // 遍历左子树System.out.print(root.val + " "); // 访问根节点inOrder(root.right); // 遍历右子树
}
3. 后序遍历(LRN)
void postOrder(Node root) {if (root == null) return;postOrder(root.left); // 遍历左子树postOrder(root.right); // 遍历右子树System.out.print(root.val + " "); // 访问根节点
}
4. 层次遍历
void levelOrder(Node root) {if (root == null) return;Queue<Node> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {Node node = queue.poll();System.out.print(node.val + " ");if (node.left != null) queue.offer(node.left);if (node.right != null) queue.offer(node.right);}
}
2.5.2 其他基本操作
// 1. 获取节点个数
int size(Node root) {if (root == null) return 0;return 1 + size(root.left) + size(root.right);
}// 2. 获取叶子节点个数
int getLeafNodeCount(Node root) {if (root == null) return 0;if (root.left == null && root.right == null) return 1;return getLeafNodeCount(root.left) + getLeafNodeCount(root.right);
}// 3. 获取第K层节点个数
int getKLevelNodeCount(Node root, int k) {if (root == null) return 0;if (k == 1) return 1;return getKLevelNodeCount(root.left, k-1) + getKLevelNodeCount(root.right, k-1);
}// 4. 获取二叉树高度
int getHeight(Node root) {if (root == null) return 0;return 1 + Math.max(getHeight(root.left), getHeight(root.right));
}// 5. 查找值为val的节点
Node find(Node root, int val) {if (root == null) return null;if (root.val == val) return root;Node left = find(root.left, val);if (left != null) return left;return find(root.right, val);
}
2.6 二叉树相关OJ题目解析
2.6.1 检查两棵树是否相同
public boolean isSameTree(Node p, Node q) {if (p == null && q == null) return true;if (p == null || q == null) return false;if (p.val != q.val) return false;return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
2.6.2 判断子树
public boolean isSubtree(Node root, Node subRoot) {if (root == null) return false;if (isSameTree(root, subRoot)) return true;return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}
2.6.3 翻转二叉树
public Node invertTree(Node root) {if (root == null) return null;Node left = invertTree(root.left);Node right = invertTree(root.right);root.left = right;root.right = left;return root;
}
2.6.4 判断平衡二叉树
public boolean isBalanced(Node root) {return checkHeight(root) != -1;
}private int checkHeight(Node root) {if (root == null) return 0;int leftHeight = checkHeight(root.left);if (leftHeight == -1) return -1;int rightHeight = checkHeight(root.right);if (rightHeight == -1) return -1;if (Math.abs(leftHeight - rightHeight) > 1) return -1;return 1 + Math.max(leftHeight, rightHeight);
}
2.6.5 对称二叉树
public boolean isSymmetric(Node root) {if (root == null) return true;return isMirror(root.left, root.right);
}private boolean isMirror(Node t1, Node t2) {if (t1 == null && t2 == null) return true;if (t1 == null || t2 == null) return false;if (t1.val != t2.val) return false;return isMirror(t1.left, t2.right) && isMirror(t1.right, t2.left);
}
2.6.6 二叉树的最近公共祖先
public Node lowestCommonAncestor(Node root, Node p, Node q) {if (root == null || root == p || root == q) return root;Node left = lowestCommonAncestor(root.left, p, q);Node right = lowestCommonAncestor(root.right, p, q);if (left != null && right != null) return root;return left != null ? left : right;
}
2.6.7 根据前序和中序遍历构建二叉树
public Node buildTree(int[] preorder, int[] inorder) {return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
}private Node build(int[] preorder, int preStart, int preEnd,int[] inorder, int inStart, int inEnd) {if (preStart > preEnd || inStart > inEnd) return null;int rootVal = preorder[preStart];Node root = new Node(rootVal);int rootIndex = 0;for (int i = inStart; i <= inEnd; i++) {if (inorder[i] == rootVal) {rootIndex = i;break;}}int leftSize = rootIndex - inStart;root.left = build(preorder, preStart + 1, preStart + leftSize,inorder, inStart, rootIndex - 1);root.right = build(preorder, preStart + leftSize + 1, preEnd,inorder, rootIndex + 1, inEnd);return root;
}
3. 二叉树进阶话题
3.1 非递归遍历实现
3.1.1 前序遍历非递归
public List<Integer> preorderTraversal(Node root) {List<Integer> result = new ArrayList<>();if (root == null) return result;Stack<Node> stack = new Stack<>();stack.push(root);while (!stack.isEmpty()) {Node node = stack.pop();result.add(node.val);if (node.right != null) stack.push(node.right);if (node.left != null) stack.push(node.left);}return result;
}
3.1.2 中序遍历非递归
public List<Integer> inorderTraversal(Node root) {List<Integer> result = new ArrayList<>();Stack<Node> stack = new Stack<>();Node current = root;while (current != null || !stack.isEmpty()) {while (current != null) {stack.push(current);current = current.left;}current = stack.pop();result.add(current.val);current = current.right;}return result;
}
3.1.3 后序遍历非递归
public List<Integer> postorderTraversal(Node root) {List<Integer> result = new ArrayList<>();if (root == null) return result;Stack<Node> stack1 = new Stack<>();Stack<Node> stack2 = new Stack<>();stack1.push(root);while (!stack1.isEmpty()) {Node node = stack1.pop();stack2.push(node);if (node.left != null) stack1.push(node.left);if (node.right != null) stack1.push(node.right);}while (!stack2.isEmpty()) {result.add(stack2.pop().val);}return result;
}
3.2 二叉树序列化与反序列化
// 序列化
public String serialize(Node root) {if (root == null) return "null";return root.val + "," + serialize(root.left) + "," + serialize(root.right);
}// 反序列化
public Node deserialize(String data) {Queue<String> queue = new LinkedList<>(Arrays.asList(data.split(",")));return buildTree(queue);
}private Node buildTree(Queue<String> queue) {String val = queue.poll();if (val.equals("null")) return null;Node node = new Node(Integer.parseInt(val));node.left = buildTree(queue);node.right = buildTree(queue);return node;
}
4. 常见面试考点
-
二叉树的各种遍历算法(递归和非递归)
-
二叉树的性质和相关计算
-
二叉树的构建和重构
-
二叉树与其他数据结构的结合
-
二叉树在实际问题中的应用