- 什么是二叉树?
二叉树(Binary Tree)是一种树状数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。每个节点包含一个值或数据元素,并可以包含指向其子节点的引用。二叉树的结构是由节点和节点之间的连接关系组成的。
二叉树的基本特点包括:
-
根节点(Root):二叉树的顶部节点,是整个树的起始点。
-
叶节点(Leaf):没有子节点的节点称为叶节点。
-
父节点(Parent):有子节点的节点称为父节点。
-
子节点(Child):父节点的下级节点称为子节点。通常分为左子节点和右子节点。
-
深度(Depth):节点的深度表示从根节点到该节点的路径长度,根节点的深度为0。
-
高度(Height):二叉树的高度表示树中最深的叶节点的深度。也可以说是从根节点到最深叶节点的最长路径的长度。
-
子树(Subtree):以某个节点为根的树,包括该节点及其所有后代节点。
-
遍历(Traversal):遍历二叉树是按照一定的顺序访问树中的节点。常见的遍历方式包括前序遍历、中序遍历和后序遍历。
不同的二叉树结构可以满足不同的需求,例如二叉搜索树(Binary Search Tree)具有有序性质,AVL 树和红黑树是平衡二叉搜索树,堆(Heap)是一种用于高效查找最小(或最大)元素的特殊二叉树,二叉树还用于构建哈夫曼树等数据压缩算法。
二叉树是计算机科学中非常重要的数据结构,用于实现各种算法和数据处理任务。
1. 前序遍历(Preorder Traversal)
前序遍历是从树的根节点开始,按照根节点、左子树、右子树(中、左、右)的顺序递归访问节点。在前序遍历中,首先访问根节点,然后依次递归遍历左子树和右子树。
public void preorderTraversal(TreeNode node) {
if (node != null) {
System.out.print(node.val + " "); // 访问当前节点
preorderTraversal(node.left); // 遍历左子树
preorderTraversal(node.right); // 遍历右子树
}
}
2. 中序遍历(Inorder Traversal)
中序遍历是按照左子树、根节点、右子树(左、中、右)的顺序递归访问节点。在中序遍历中,首先递归遍历左子树,然后访问根节点,最后递归遍历右子树。
public void inorderTraversal(TreeNode node) {
if (node != null) {
inorderTraversal(node.left); // 遍历左子树
System.out.print(node.val + " "); // 访问当前节点
inorderTraversal(node.right); // 遍历右子树
}
}
3. 后序遍历(Postorder Traversal)
后序遍历是按照左子树、右子树、根节点(左、右、中)的顺序递归访问节点。在后序遍历中,首先递归遍历左子树,然后递归遍历右子树,最后访问根节点。
public void postorderTraversal(TreeNode node) {
if (node != null) {
postorderTraversal(node.left); // 遍历左子树
postorderTraversal(node.right); // 遍历右子树
System.out.print(node.val + " "); // 访问当前节点
}
}
迭代的遍历方式
除了递归方式,你也可以使用迭代方式来遍历二叉树。迭代遍历方式通常需要使用辅助数据结构,如栈(Stack)或队列(Queue),以模拟递归调用的工作栈。下面我将介绍迭代的遍历方式,并解释其思想。
1. 迭代的前序遍历(中左右)
迭代的前序遍历使用栈来模拟递归调用的顺序。基本思想是维护一个栈,首先将根节点入栈,然后进入循环,循环条件为栈不为空。在每一轮循环中,将栈顶节点出栈,访问该节点,并将右子节点和左子节点按逆序入栈,以确保下一次出栈的是左子节点。这样,根节点会被最先访问,然后是左子树,最后是右子树。
public void iterativePreorderTraversal(TreeNode root) {
if (root == null) return;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
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);
}
}
}
当在Java中使用栈数据结构时,通常是使用java.util.Stack
类,这是一个基于双链表(LinkedList
)实现的栈。
如果你更喜欢使用双链表来实现栈,你可以按照以下方式修改代码:
public void iterativePreorderTraversal(TreeNode root) {
if (root == null) return;
LinkedList<TreeNode> stack = new LinkedList<>();//代码修改
stack.push(root);
while (!stack.isEmpty()) {
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);
}
}
}
2. 迭代的中序遍历(左中右)
迭代的中序遍历也使用栈来模拟递归调用的顺序。基本思想是从根节点开始,将左子节点依次入栈,直到没有左子节点。然后出栈,访问节点,然后将右子节点入栈,重复这个过程,直到栈为空。
public void iterativeInorderTraversal(TreeNode root) {
if (root == null) return;
LinkedList<TreeNode> stack = new LinkedList<>();
TreeNode current = root;
while (current != null || !stack.isEmpty()) {
while (current != null) {
stack.push(current);
current = current.left;
}
current = stack.pop();
System.out.print(current.val + " "); // 访问当前节点
current = current.right;
}
}
3. 迭代的后序遍历(左右中)
迭代的后序遍历也使用栈来模拟递归调用的顺序。基本思想是使用两个栈。
一个用于遍历,一个用于存储结果。
首先将根节点入遍历栈,然后进入循环,循环条件为遍历栈不为空。
在每一轮循环中,将栈顶节点出栈,并将其值插入到结果栈的栈顶。然后按照根节点、右子节点、左子节点的顺序,将节点依次入遍历栈。最后,将结果栈中的元素依次出栈,即可得到后序遍历的结果。
public void iterativePostorderTraversal(TreeNode root) {
if (root == null) return;
LinkedList<TreeNode> traversalStack = new LinkedList<>();
LinkedList<TreeNode> resultStack = new LinkedList<>();
traversalStack.push(root);
while (!traversalStack.isEmpty()) {
TreeNode node = traversalStack.pop();
resultStack.push(node);
if (node.left != null) {
traversalStack.push(node.left);
}
if (node.right != null) {
traversalStack.push(node.right);
}
}
while (!resultStack.isEmpty()) {
System.out.print(resultStack.pop().val + " "); // 访问当前节点
}
}