导言:
二叉树是一种常见的树形数据结构,一棵二叉树是节点的一个有限集合,该集合或者为空或者是由一个根节点加上两颗分别为左子树和右子树的二叉树组成。本文主要对二叉树的四种遍历和基本操作进行介绍。
正文:
一、 二叉树的基本概念
1.概念
二叉树是一种树形结构,它由节点组成,每个节点最多有两个子节点。每个节点包含一个值和指向左子树和右子树的指针。二叉树可以为空,也可以是非空的。由此可得二叉树的性质只能有如下几种:
2.两种特殊的二叉树:
-
满二叉树: 满二叉树是一种特殊的二叉树,其中每个节点要么没有子节点,要么有两个子节点。换句话说,每一层的节点都是满的,除了最后一层外,所有层的节点数都达到最大可能的数量。满二叉树的一个重要特性是,叶子节点都在最后一层,并且所有非叶子节点都有两个子节点。
-
完全二叉树: 完全二叉树是一种二叉树,其中除了最后一层,其他层的节点都是满的,而且最后一层的节点都集中在树的左部。换句话说,如果最后一层不是满的,那么缺失的节点只能出现在最后一层的最右边。完全二叉树常常用数组来表示,因为它的结构相对简单,可以用数组的索引来表示节点之间的父子关系,这对于存储和检索数据非常有用。
3.二叉树的基本性质
- 对于一个高度为h的满二叉树,其节点数量为
。这是因为满二叉树的每一层都是满的,所以节点数量可以通过累加每一层的节点数得到。
- 若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是
(k>=0)
- 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1
- 具有n个结点的完全二叉树的深度k为
上取整
- 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i的结点有:
若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
若2i+1<n,左孩子序号:2i+1,否则无左孩子
若2i+2<n,右孩子序号:2i+2,否则无右孩子 - 对于一个高度为h的完全二叉树,其节点数量在1到2^h - 1之间。完全二叉树的节点分布在各层中,所以节点数量可能不会达到满二叉树的节点数量。
- 对于一个有n个节点的二叉树,其最小高度为log2(n+1) - 1。这是因为如果二叉树是满的,则节点数量为2^h - 1,解出h即可得到最小高度。
- 对于一个有n个节点的二叉树,其最大高度为n - 1。这是因为如果二叉树是单侧的,即只有左子树或右子树,那么高度为n - 1。
二、二叉树的基本操作
1.前序遍历
二叉树的前序遍历是一种深度优先遍历方法,用于按照特定顺序访问二叉树的所有节点。在前序遍历中,首先访问根节点,然后递归地对左子树和右子树进行前序遍历。
具体步骤如下:
- 访问根节点。
- 递归地对根节点的左子树进行前序遍历。
- 递归地对根节点的右子树进行前序遍历。
下面是一个示例二叉树和其前序遍历的过程:
1
/ \
2 3
/ \
4 5
前序遍历的结果为:1, 2, 4, 5, 3
代码如下:
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
public void preorderTraversal(TreeNode root) {
if (root != null) {
System.out.print(root.val + " "); // 访问根节点
preorderTraversal(root.left); // 递归遍历左子树
preorderTraversal(root.right); // 递归遍历右子树
}
}
上面的是递归方式,非递归遍历方法如下:
当使用非递归方式实现二叉树的前序遍历时,我们需要借助栈来模拟递归的过程。具体步骤如下:
- 首先,我们创建一个栈,将根节点压入栈中。
- 在每一轮循环中,我们从栈中弹出一个节点,并访问它。
- 如果这个节点的右子树不为空,我们将右子树压入栈中,因为在前序遍历中,我们需要先访问左子树。
- 如果这个节点的左子树不为空,我们将左子树压入栈中,因为左子树在栈中的位置要比右子树靠后,保证左子树先出栈。
- 重复步骤2-4,直到栈为空。
代码如下:
public void preorderTraversal(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); // 再压入左子树,保证左子树先出栈
}
}
}
2.中序遍历
中序遍历也是二叉树遍历的一种方式,其遍历顺序为:先遍历左子树,然后访问根节点,最后遍历右子树。具体操作如下:
- 从根节点开始,先遍历左子树。
- 访问根节点。
- 最后遍历右子树。
下面是一个示例二叉树和其中序遍历的过程:
1
/ \
2 3
/ \
4 5
中序遍历的结果为:4,2,5,1,3
代码如下:
public void inorderTraversal(TreeNode root) {
if (root != null) {
inorderTraversal(root.left); // 递归遍历左子树
System.out.print(root.val + " "); // 访问根节点
inorderTraversal(root.right); // 递归遍历右子树
}
}
非递归实现如下:
当使用非递归的方式实现二叉树的中序遍历时,我们利用栈来模拟递归的过程。具体的实现思路如下:
-
首先,我们从根节点开始,将当前节点及其所有左子节点一直压入栈中,直到没有左子节点为止。这样可以确保在遍历完左子树后,能够按照中序遍历的顺序访问节点。
-
然后,我们弹出栈顶节点,访问该节点并将其值加入遍历结果中。
-
接着,我们将当前节点指向其右子节点,继续遍历右子树。
-
重复步骤 1-3,直到栈为空且当前节点为空,此时遍历结束。
代码如下:
public class InorderTraversal {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode current = root;
while (current != null || !stack.isEmpty()) {
while (current != null) {
stack.push(current);
current = current.left;
}
current = stack.pop();
result.add(current.value);
current = current.right;
}
return result;
}
3.后序遍历
后序遍历与前面两种类似,其遍历顺序为:先遍历左子树,然后遍历右子树,最后访问根节点。具体操作如下:
- 从根节点开始,先遍历左子树。
- 遍历右子树。
- 最后访问根节点。
下面是一个示例二叉树和其中序遍历的过程:
1
/ \
2 3
/ \
4 5
前序遍历的结果为:4,5,2,3,1
代码实现:
public void PostorderTraversal(TreeNode root) {
if (root != null) {
PostorderTraversal(root.left); // 递归遍历左子树
PostorderTraversal(root.right); // 递归遍历右子树
System.out.print(root.val + " "); // 访问根节点
}
}
非递归实现如下:
当我们进行二叉树的后序非递归遍历时,我们需要使用栈来辅助实现。具体思路如下:
- 首先,我们将根节点入栈。
- 然后循环执行以下步骤:
弹出栈顶节点,将其值插入结果列表的头部。
将左子节点和右子节点依次入栈。 - 最后,遍历结果列表即可得到后序遍历的结果。
代码如下:
public class PostorderTraversal {
public List<Integer> postorderTraversalIterative(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
TreeNode prev = null;
while (!stack.isEmpty()) {
TreeNode current = stack.peek();
if (prev == null || prev.left == current || prev.right == current) {
if (current.left != null) {
stack.push(current.left);
} else if (current.right != null) {
stack.push(current.right);
}
} else if (current.left == prev) {
if (current.right != null) {
stack.push(current.right);
}
} else {
result.add(current.value);
stack.pop();
}
prev = current;
}
return result;
}
4.层序遍历
二叉树的层序遍历是一种广度优先搜索(BFS)的遍历方式,它按照树的层级顺序逐层访问树的节点。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。具体的实现思路如下:
- 首先,我们使用一个队列来辅助进行层序遍历。
- 将根节点入队。
- 循环执行以下步骤,直到队列为空:
- 弹出队首节点,将其值加入结果列表。
- 将队首节点的左子节点和右子节点(如果存在)依次入队。
- 最后,遍历结果列表即可得到层序遍历的结果。
下面是一个示例二叉树和其层序遍历的过程:
1
/ \
2 3
/ \
4 5
层序遍历的结果为:1,2,3,4,5
代码如下:
public class LevelOrderTraversal {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size();
List<Integer> currentLevel = new ArrayList<>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
currentLevel.add(node.value);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
result.add(currentLevel);
}
return result;
}
5.二叉树的创建
我们可以通过递归来创建二叉树。,通过节点之间的关系来表示节点之间的父子关系。通常,对于节点中下标为i
的元素,其左子节点的下标为2*i+1
,右子节点的下标为2*i+2
。
具体步骤如下:
-
我们首先定义了两个类:
TreeNode
用于表示二叉树的节点,LinkedListNode
用于表示链表的节点。 -
在
BinaryTreeCreation
类中,我们定义了一个createBinaryTree
方法,该方法接收链表的头节点作为参数,并返回创建的二叉树的根节点。在这个方法中,我们首先判断链表头节点是否为空,如果为空则返回null;否则,我们创建二叉树的根节点,并将链表头节点的值赋给根节点,然后将链表头节点指向下一个节点。 -
接下来,我们调用
createTree
方法来递归地创建左子树和右子树。在createTree
方法中,我们首先判断链表节点是否为空,如果为空则返回;否则,我们创建当前节点的左子节点,并将链表节点指向下一个节点,然后递归调用createTree
方法来创建左子树。接着,我们判断链表节点是否为空,如果不为空则创建当前节点的右子节点,然后递归调用createTree
方法来创建右子树。 -
最后,我们在
main
方法中创建了一个链表,并调用createBinaryTree
方法来创建二叉树,并得到了根节点。
代码如下:
public class BinaryTreeCreation {
static class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
static class LinkedListNode {
int val;
LinkedListNode next;
LinkedListNode(int x) {
val = x;
}
}
public TreeNode createBinaryTree(LinkedListNode head) {
if (head == null) {
return null;
}
TreeNode root = new TreeNode(head.val);
head = head.next;
createTree(root, head);
return root;
}
private void createTree(TreeNode node, LinkedListNode head) {
if (head == null) {
return;
}
node.left = new TreeNode(head.val);
head = head.next;
createTree(node.left, head);
if (head != null) {
node.right = new TreeNode(head.val);
head = head.next;
createTree(node.right, head);
}
}
}
总结:
二叉树是一种树形数据结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树通常用于实现二叉搜索树、堆、表达式树等数据结构,以及在算法和数据结构中的许多问题中。大多数的操作都是围绕着遍历展开,所以掌握好遍历是掌握二叉树的基础。希望本文对你有所帮助。