原理
二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
二叉树的遍历次序大多是从头至尾、循环、双向等简单的遍历方式。
树的结点之间不存在唯一的前驱和后继关系,在访问一个结点后,下一个被访问的结点面临着不同的选择。
方法
二叉树的遍历方法
1、前序遍历
规则是若二叉树为空,则空操作返回,
否则先访问根结点,然后前序遍历左子树,在前序遍历右子树。
如下图所示:
- ✅前序遍历结果:ABDECF
遍历算法:
(1)递归方式:
递归的输出当前结点,然后递归的输出左子结点,最后递归的输出右子结点。
(2)非递归方式:
用栈结构,把遍历到的结点押进栈,没子结点时再出栈
(用栈来保存先前走过的路径,以便可以在访问完子树后,可以利用栈中的信息,回退到当前节点的双亲结点,进行下一步操作。)
package tree;
import java.util.Stack;
/**
* 前序遍历的递归实现与非递归实现
*前序遍历首先访问根节点 然后遍历左子树,最后遍历右子树。
* 在遍历左右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。
* 若二叉树为空则结束返回否则
* (1)访问根结点
* (2)前序遍历左子树
* (3)前序遍历右子树
*
*/
public class Test {
public static void main(String[] args) {
//以数组形式生成一颗完全二叉树
TreeNode[] node = new TreeNode[10];
//比如有10个结点
for(int i = 0;i < 10;i++){
node[i] = new TreeNode(i);//对结点实例化
if(2*i+1 < 10){
node[i].left= node[2*i+1];
}
if(2*i+2 <10){
node[i].right = node[2*i+2];
}
}
preOrderRe(node[0]);
System.out.println();
}
static void preOrderRe(TreeNode root){
//递归实现
System.out.println(root.value);
if(root.left != null){
preOrderRe(root.left);
}
if(root.right != null){
preOrderRe(root.right);
}
}
/**
* 非递归实现:
* 用栈结构,把遍历到的结点押进栈,没子结点时再出栈
* @param root
*/
public static void preOrder(TreeNode root){
Stack<TreeNode> stack = new Stack<TreeNode>();
while (root != null || !stack.isEmpty()){
if(root != null){
System.out.println(root.value);
stack.push(root);
root = root.left;
}
if(!stack.isEmpty()){
root = stack.pop();
root = root.right;
}
}
}
}
class TreeNode{
int value;
TreeNode left;
TreeNode right;
TreeNode(int value){
this.value = value;
}
}
2、中序遍历
规则是若二叉树为空,则空操作返回,
否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
如下图所示:
- ✅中序遍历结果:DBEAFC
遍历算法:
(1)递归方式:
递归的输出左子结点,然后递归的输出当前结点,最后递归的输出右子结点。
(2)非递归方式:
用栈结构,把遍历到的结点押进栈,没子结点时再出栈
(用栈来保存先前走过的路径,以便可以在访问完子树后,可以利用栈中的信息,回退到当前结点的双亲结点,进行下一步操作。)
package tree;
import java.util.Stack;
/**
* 前序遍历的递归实现与非递归实现
*前序遍历首先访问根节点 然后遍历左子树,最后遍历右子树。
* 在遍历左右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。
* 若二叉树为空则结束返回否则
* (1)访问根结点
* (2)前序遍历左子树
* (3)前序遍历右子树
*
*/
public class Test {
public static void main(String[] args) {
//以数组形式生成一颗完全二叉树
TreeNode[] node = new TreeNode[10];
//比如有10个结点
for(int i = 0;i < 10;i++){
node[i] = new TreeNode(i);//对结点实例化
if(2*i+1 < 10){
node[i].left= node[2*i+1];
}
if(2*i+2 <10){
node[i].right = node[2*i+2];
}
}
midOrderRe(node[0]);
System.out.println();
midOrder(node[0]);
}
static void midOrderRe(TreeNode root){
//递归实现
if(root == null){
return;
}else {
midOrderRe(root.left);
System.out.println(root.value);
midOrderRe(root.right);
}
}
/**
* 非递归实现:
* 用栈结构,把遍历到的结点押进栈,没子结点时再出栈
* @param root
*/
public static void midOrderOrder(TreeNode root){
Stack<TreeNode> stack = new Stack<TreeNode>();
while (root != null || !stack.isEmpty()){
if(root != null){
stack.push(root);
root = root.left;
}
if(!stack.isEmpty()){
root = stack.pop();
System.out.println(root.value);
root = root.right;
}
}
}
}
class TreeNode{//结点结构
int value;
TreeNode left;
TreeNode right;
TreeNode(int value){
this.value = value;
}
}
3、后序遍历
规则是若二叉树为空,则空操作返回,
否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。
如下图所示:
- ✅后序遍历结果:DEBFCA
遍历算法:
(1)递归方式:
递归的输出左结节点,递归的输出右子结点,最后再输出根值。
(2)非递归方式:
用栈结构,把遍历到的结点押进栈,没子结点时再出栈
具体操作:
利用栈回退到根结点时,
- 如果从左子树回退到根结点,此时就应该去访问右子树,
- 而如果从右子树回退到根节点,此时就应该访问根结点。
- 在压栈时添加信息,以便在退栈时可以知道是从左子树返回,还是从右子树返回进而决定下一步的操作。
import java.util.LinkedList;
import java.util.Stack;
public class houxu {
public static void main(String[] args) {
TreeNode[] node = new TreeNode[10];
for (int i = 0;i <10;i++){
node[i] = new TreeNode(i);
}
for (int i =0;i <10;i++){
if(i*2+1<10){
node[i].left = node[i*2+1];
}
if(i*2+2 <10){
node[i].right = node[i*2+2];
}
}
postOrderRe(node[0]);
System.out.println("***");
postOrder(node[0]);
}
static void postOrderRe(TreeNode root){
//递归实现
if(root == null){
return;
}else {
postOrderRe(root.left);
postOrderRe(root.right);
System.out.println(root.value);
}
}
static void postOrder(TreeNode root){
//后序遍历非递归实现
int left = 1; //在辅助栈里表示左结点
int right = 2;//在辅助栈里表示右结点
Stack<TreeNode> stack = new Stack<TreeNode>();
Stack<Integer> stack2 = new Stack<Integer>();
//辅助栈,用来判断子结点返回父节点时处于左结点还是右结点。
while (root != null || !stack.empty()){
//将结点压入栈1,并在栈2将结点标记为左结点
stack.push(root);
stack2.push(left);
root = root.left;
}
while (!stack.empty() && stack2.peek()== right){
//如果从右子结点返回父结点,则任务完成,将两个栈的栈顶弹出
stack2.pop();
System.out.println(stack.pop().value);
}
while (!stack.empty() && stack2.peek()== left){
//如果从左子结点返回父节点,则将标记改为右子结点
stack2.pop();
stack2.push(right);
root = stack.peek().right;
}
}
4、层次遍历
规则是若二叉树为空,则空操作返回,
否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,
在同一层中按从左到右的顺序对结点逐个访问
如下图所示:
- ✅后序遍历结果:ABCDEF
遍历算法操作:
- 对于不为空的结点,先把该结点加入到队列中
- 从队中拿出结点,如果该结点的左右结点不为空,就分别把左右结点加入到队列中
- 重复以上操作直到队列为空
/**
* 层次遍历
* BFS思想,用队列去实现
* 步骤:
* 1、对于不为空的结点,先把该结点加入到队列中去
* 2、从队中拿出结点,如果该结点的左右结点不为空,就分别把左右结点加入到队列中去
* 3、重复以上操作直到队列为空
*/
public static void levelOrder(TreeNode root){
if(root == null){
return;
}
LinkedList<TreeNode> list = new LinkedList<TreeNode>();
list.add(root);
TreeNode currentNode;
while (!list.isEmpty()){
currentNode = list.poll();
System.out.println(currentNode.value);
if(currentNode.left != null){
list.add(currentNode.left);
}
if(currentNode.right != null){
list.add(currentNode.right);
}
}
}
附加
-
✅ 已知前序遍历序列和中序遍历序列,可以唯一确定一棵二叉树
-
✅ 已知后序遍历序列和中序遍历序列,可以唯一确定一棵二叉树
-
✅ 但是已知前序遍历序列和后序遍历序列,是不能确定一棵二叉树的
-
即:没有中序遍历序列的情况下是无法确定一颗二叉树的