广度优先遍历(层次遍历)
对一棵二叉树进行广度优先遍历,就是对一棵二叉树中所有的节点,按照层次从上到下、每一层中节点从左到右的顺序,将二叉树中的所有节点进行遍历的操作。广度优先遍历方式只有一种,需要依赖于队列结构。
使用队列对二叉树进行层序遍历的步骤如下:
- 步骤1:将二叉树的根节点加入队列
- 步骤2:将队列头节点出队列
- 步骤3.1:如果根节点具有左孩子,则根节点的左孩子入队列
- 步骤3.2:如果根节点具有右孩子,则根节点的右孩子入队列
- 然后重复上述的步骤2至步骤3.2,直至整个队列中不再具有元素为止。在步骤3.1至步骤3.2中,一定是左孩子先进入队列,然后才是右孩子进入队列。
深度优先遍历
深度优先遍历是二叉树遍历方式中比较重点的一种遍历方式,可以分为先序遍历、中序遍历和后序遍历3种遍历序列。和广度优先遍历方式不同的是,深度优先遍历是从根节点开始,一条路走到黑,一个分支一个分支的进行节点遍历,其中一个分支全部遍历结束之后,再去进行下一个分支的遍历。在深度优先遍历中,我们必须记住这样一个规则:如何遍历整个二叉树,就如何遍历左右子树。
先序遍历
先序遍历处理节点的顺序是:根左右,即先访问根节点,然后访问左子树,最后访问右子树。
先序遍历二叉树节点的步骤是:
- 步骤1:首先得到整个二叉树的根节点,并访问这个根节点。
- 步骤2:如果根节点具有左孩子,则将左孩子及其子节点作为一个单独的二叉树进行先序遍历。
- 步骤3:在遍历过程中,如果一个节点没有左孩子或者左孩子已经遍历完成,那么判断这个节点有没有右孩子。
- 步骤4:如果这个节点具有右孩子,则将右孩子及其子节点作为一个单独的二叉树进行先序遍历。
- 步骤5:如果这个节点的没有右孩子或者右孩子已经遍历完成,那么向上回溯到父节点,重复判断父节点是否具有右孩子。
- 步骤6:重复上述步骤2-5,直到最终回溯到根节点为止,表示根节点及其左右子树已经全部遍历完成,得到完整的先序遍历序列。
中序遍历
中序遍历处理节点的顺序是:左根右,即先访问左子树,然后访问根节点,最后访问右子树。
中序遍历二叉树节点的步骤是:
- 步骤1:在得到根节点的时候,首先不要对根节点进行访问,而是直接判断这个根节点有没有左孩子。
- 步骤2:如果根节点存在左孩子,则对以左孩子为根的左子树进行中序遍历。
- 步骤3:如果一个节点不存在左孩子,或者左孩子已经遍历完成,那么项父节点进行回溯,在回溯到父节点的时候,访问父节点。
- 步骤4:在父节点访问完成后,判断父节点是否具有右孩子。
- 步骤5:如果父节点具有右孩子,则对右孩子为根的右子树进行中序遍历。
- 步骤6:如果父节点不具有右孩子或者右子树已经遍历完成,那么向上回溯的父节点。
- 步骤7:重复上述步骤1-6,直到回溯到整个二叉树的根节点为止,遍历完成,得到完整的中序遍历序列。
后序遍历
后序遍历处理节点的顺序是:左右根,即先访问左子树,然后访问右子树,最后访问根节点。
后序遍历二叉树节点的步骤是:
- 步骤1:得到整个二叉树的根节点,但是并不访问根节点,判断根节点是否具有左孩子。
- 步骤2:如果根节点具有左孩子,则后序遍历以左孩子为根的左子树。
- 步骤3:如果根节点不具有左孩子,或者左子树遍历完成,则回溯到根节点,但是依然不访问根节点,而是判断根节点是否具有右孩子。
- 步骤4:如果根节点具有右孩子,则后序遍历以右孩子为根的右子树。
- 步骤5:如果根节点不具有右孩子,或者右子树遍历完成,则回溯到根节点,此时访问根节点。
- 步骤6:重复上述步骤1-5,直到回溯到整个二叉树的根节点并且根节点访问完毕为止。
public class MyTree {
// 节点类
class Node<E> {
E data;
Node<E> left;
Node<E> right;
}
// 构建满二叉树
public Node<Integer> buildTree(int level, int value) {
Node<Integer> node = new Node<>();
node.data = value;
if (level == 1) {
return node;
}
Node<Integer> leftNode = buildTree(level - 1, value * 2);
node.left = leftNode;
Node<Integer> rightNode = buildTree(level - 1, value * 2 + 1);
node.right = rightNode;
return node;
}
/**
* 前序遍历(递归)
* 先序遍历的顺序是:先依次访问每颗子树的根节点,然后再依次访问左子树和右子树。
* 即:根-左-右
*/
public <E> void preOrderTraverse(Node<E> root) {
if (root == null) return;
System.out.print(root.data + " ");// 先根节点
preOrderTraverse(root.left);// 遍历左子树
preOrderTraverse(root.right);// 遍历右子树
}
// 前序遍历(非递归,使用栈)
public <E> void preOrderTraverseUseStack(Node<E> root) {
if (root == null) return;
Stack<Node<E>> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
Node<E> curNode = stack.pop();
System.out.print(curNode.data + " ");// 先根节点
// 先将右子树压入栈,再将左子树压入栈,这样出栈的时候会先出左子树
if (curNode.right != null) {
stack.push(curNode.right);
}
if (curNode.left != null) {
stack.push(curNode.left);
}
}
}
/**
* 中序遍历(递归)
* 中序遍历的顺序是:先依次访问每颗子树的左孩子节点,然后访问子树的根节点,最后访问子树的右孩子节点。
* 即:左-根-右。
*/
public <E> void inOrderTraverse(Node<E> root) {
if (root == null) return;
inOrderTraverse(root.left);// 遍历左子树
System.out.print(root.data + " ");// 先根节点
inOrderTraverse(root.right);// 遍历右子树
}
// 中序遍历(非递归,使用栈)
public <E> void inOrderTraverseUseStack(Node<E> root) {
if (root == null) return;
Stack<Node<E>> stack = new Stack<>();
Node<E> curNode = root;
while (curNode != null || !stack.isEmpty()) {
// 一直循环到二叉排序树最左端的叶子结点(curNode是null)
while (curNode != null) {
stack.push(curNode);
curNode = curNode.left;
}
curNode = stack.pop();
System.out.print(curNode.data + " ");
curNode = curNode.right;
}
}
/**
* 后序遍历(递归)
* 后序遍历的顺序是:先依次访问每颗子树的左孩子节点,然后访问子树的右孩子节点,最后访问根节点。
* 即:左-右-根。
*/
public <E> void postOrderTraverse(Node<E> root) {
if (root == null) return;
postOrderTraverse(root.left);// 遍历左子树
postOrderTraverse(root.right);// 遍历右子树
System.out.print(root.data + " ");// 先根节点
}
// 后序遍历(非递归,使用栈)
public <E> void postOrderTraverseUseStack(Node<E> root) {
Stack<Node<E>> stack = new Stack<>();
Node<E> curNode = root;
Node<E> rightNode = null;
while (curNode != null || !stack.isEmpty()) {
// 一直循环到二叉排序树最左端的叶子结点(curNode是null)
while (curNode != null) {
stack.push(curNode);
curNode = curNode.left;
}
curNode = stack.pop();
// 当前结点没有右结点或上一个结点(已经输出的结点)是当前结点的右结点,则输出当前结点
while (curNode.right == null || curNode.right == rightNode) {
System.out.print(curNode.data + " ");
rightNode = curNode;
if (stack.isEmpty()) {
return; // root已输出,则遍历结束
}
curNode = stack.pop();
}
stack.push(curNode);// 还有节点没有遍历
curNode = curNode.right;
}
}
/**
* 广度优先遍历(层次遍历)
* 遍历顺序是:先从树的根节点开始,依次访问根节点的每一个直接子节点,然后再访问这些子节点的直接子节点,
* 以此类推,直到到达叶节点。
*/
public <E> void bfTraversal(Node<E> root) {
if (root == null) return;
LinkedList<Node<E>> queue = new LinkedList<>();// 创建一个队列来存放要访问的节点
queue.offer(root);// 将根节点加入队列中
while (!queue.isEmpty()) {
Node<E> curNode = queue.poll();// 从队列头部取出一个节点进行处理
System.out.print(curNode.data + " ");
if (curNode.left != null) {
queue.offer(curNode.left);// 左子节点不为空时将其加入队列尾部
}
if (curNode.right != null) {
queue.offer(curNode.right);// 右子节点不为空时将其加入队列尾部
}
}
}
public static void main(String[] args) {
MyTree tree = new MyTree();
Node<Integer> root = tree.buildTree(3, 1);
System.out.println("root:" + root.data);// 打印-->root:1
System.out.print("广度优先遍历(层次遍历):");// 打印-->广度优先遍历(层次遍历):1 2 3 4 5 6 7
tree.bfTraversal(root);
System.out.println();
System.out.print("前序遍历:");// 打印-->前序遍历:1 2 4 5 3 6 7
tree.preOrderTraverse(root);
System.out.println();
System.out.print("使用栈前序遍历:");// 打印-->使用栈前序遍历:1 2 4 5 3 6 7
tree.preOrderTraverseUseStack(root);
System.out.println();
System.out.print("中序遍历:");// 打印-->中序遍历:4 2 5 1 6 3 7
tree.inOrderTraverse(root);
System.out.println();
System.out.print("使用栈中序遍历:");// 打印-->使用栈中序遍历:4 2 5 1 6 3 7
tree.inOrderTraverseUseStack(root);
System.out.println();
System.out.print("后序遍历:");// 打印-->后序遍历:4 5 2 6 7 3 1
tree.postOrderTraverse(root);
System.out.println();
System.out.print("使用栈后序遍历:");// 打印-->使用栈后序遍历:4 5 2 6 7 3 1
tree.postOrderTraverseUseStack(root);
System.out.println();
}
}