今天总结一下二叉树的各种遍历方式。首先,创建二叉树节点类如下:
static class BinaryTreeNode{
private BinaryTreeNode m_pleft = null;
private BinaryTreeNode m_pright = null;
private int m_value = 0;
public BinaryTreeNode getM_pleft() {
return m_pleft;
}
public void setM_pleft(BinaryTreeNode m_pleft) {
this.m_pleft = m_pleft;
}
public BinaryTreeNode getM_pright() {
return m_pright;
}
public void setM_pright(BinaryTreeNode m_pright) {
this.m_pright = m_pright;
}
public int getM_value() {
return m_value;
}
public void setM_value(int m_value) {
this.m_value = m_value;
}
@Override
public String toString() {
return "BinaryTreeNode [ m_value=" + m_value + "]";
}
}
之后,利用之前做的一个题创建一棵二叉树,题目大概就是利用一棵树的前序和中序遍历来创建一棵二叉树这里直接贴出代码,有兴趣的同学可参见:重建二叉树
代码如下:
public static BinaryTreeNode findTree(int[] front , int[] middle){
if (front == null || middle == null) {
return null;
}
if (front.length == 0) {
//当长度为1时直接返回不用再分了
BinaryTreeNode newNode = null;//注意:这里不能写成 = new BinaTreeNode! 否则遍历此树时无法结束
return newNode;
}
if (front.length == 1) {
BinaryTreeNode newNode = new BinaryTreeNode();
newNode.setM_value(front[0]);
return newNode;
}
BinaryTreeNode root = new BinaryTreeNode(); //注意,必须直接把内部类写成静态的,否则必须要先实例化外部类才可以。
root.setM_value(front[0]);//前序第一个数为这个树的根
//寻找树的根在中序遍历中的位置
int rootNumberIndex = 0;
for (int i = 0; i < middle.length; i++) {
if (middle[i] == front[0]) {
rootNumberIndex = i;
}
}
//新的左边的中序
int[] newLeftMiddle = null;
newLeftMiddle = Arrays.copyOf(middle, rootNumberIndex );
//新的右边的中序
int[] newRightMiddle = null;
newRightMiddle = Arrays.copyOfRange(middle, rootNumberIndex + 1, middle.length);
//新的左边的前序遍历
int[] newLeftFront = new int[newLeftMiddle.length];
//这块有错!!!
//这里是要找到front里面找到newLeftMiddle的顺序组成newLeftFront
int k = 0;
for (int j = 0; j < front.length; j++) {
if(judgeContain(newLeftMiddle,front[j])){
newLeftFront[k] = front[j];
k++;
}
}
//新的右边的前序遍历
int[] newRightFront = new int[newRightMiddle.length];
int k2 = 0;
for (int j = 0; j < front.length; j++) {
if (judgeContain(newRightMiddle , front[j])) {
newRightFront[k2] = front[j];
k2++;
}
}
root.setM_pleft(findTree(newLeftFront , newLeftMiddle));
root.setM_pright(findTree(newRightFront , newRightMiddle));
return root;
}
private static boolean judgeContain(int[] intIndex, int number) {
// TODO to judge the intIndex if have the number
for (int i : intIndex) {
if( i == number){
return true;
}
}
return false;
}
现在,通过这种方式我们成功的建立了一棵二叉树,接下来进入正题,如何去遍历他们,首先进行二叉树前序中序后序递归遍历,这三种遍历方式是很简单的,我们只需要将他们的遍历方法记住,就很容易写出来,即:
前根序: 根——>左——>右
中根序:左——>根——>右
后根序:左——>右——>根
与之相对应,三种递归遍历方法如下:
前根序:
private static void preOrderRecursionDisPlay(BinaryTreeNode root){
//前序遍历递归版
if (root == null) {
throw new RuntimeException("此树为空!");
}
System.out.print(root.getM_value()+" ");
if (root.getM_pleft() != null) {
preOrderRecursionDisPlay(root.getM_pleft());
}
if (root.getM_pright() != null) {
preOrderRecursionDisPlay(root.getM_pright());
}
}
中根序:
private static void inOrderRootRecursionDisplay(BinaryTreeNode root) {
// TODO Auto-generated method stub
//中序遍历递归版
if (root == null) {
throw new RuntimeException("此树为空");
}
if (root.getM_pleft() != null) {
inOrderRootRecursionDisplay(root.getM_pleft());
}
System.out.print(root.getM_value() + " ");
if (root.getM_pright() != null) {
inOrderRootRecursionDisplay(root.getM_pright());
}
}
后根序:
private static void postOrderRecursionDisplay(BinaryTreeNode root) {
// TODO Auto-generated method stub
//后序遍历递归版
if (root == null) {
throw new RuntimeException("此树为空");
}
if (root.getM_pleft() != null) {
postOrderRecursionDisplay(root.getM_pleft());
}
if (root.getM_pright() != null) {
postOrderRecursionDisplay(root.getM_pright());
}
System.out.print(root.getM_value() + " ");
}
接下来,就是比较复杂的非递归遍历了,非递归遍历其实使用栈(层次遍历用队列)来模拟递归遍历的方法,首先来看前序的非递归遍历。
前序遍历,依据其规则,在判断其根不为空且初始化完栈之后
首先,我们访问其根,并将根入栈,以便后面访问他的右子树,令根为其做孩子,一直循环到左孩子为空为止
之后,我们将栈中的节点弹出,弹出之后,令根为其右孩子再次进入之前的循环。再从上面的一步开始按照之前的方法访问左孩子和右孩子。
代码如下:
private static void preOrderRecursionNotDisplay(BinaryTreeNode root) {
//前序遍历非递归版
// TODO Auto-generated method stub
if (root == null) {
throw new RuntimeException("此树为空");
}
ArrayDeque<BinaryTreeNode> stack = new ArrayDeque<BinaryTreeNode>();
while(root != null || !stack.isEmpty()){
//这里的!isEmpty()使得程序允许以下这种情况进入while循环:当root成为叶子结点的左孩子(空节点)
while(root != null){
System.out.print(root.getM_value() + " ");
//访根
stack.push(root);
//入栈,以便于以后访问它的右孩子
root = root.getM_pleft();
}
if (!stack.isEmpty()) {
root = stack.pop().getM_pright();
//当有右孩子时,访问右孩子,进入下一次循环,
//无右孩子时,因为isEmpty()的存在,允许进入循环但不进入访问左孩子循环(因为栈里的对象左孩子之前已经访问过了)
}
}
}
之后是中序非递归遍历,中序遍历和前序遍历基本相同,只不过改变一下访问节点数据时的位置即可。代码如下:
private static void inOrderRecursionNotDisplay(BinaryTreeNode root) {
// TODO Auto-generated method stub
//中序遍历非递归版
if (root == null) {
throw new RuntimeException("此树为空");
}
ArrayDeque<BinaryTreeNode> stack = new ArrayDeque<BinaryTreeNode>();
while(root != null || !stack.isEmpty()){
while(root != null){
stack.push(root);
root = root.getM_pleft();
}
if(!stack.isEmpty()){
root = stack.pop();
System.out.print(root.getM_value() + " ");
root = root.getM_pright();
}
}
}
最后是非递归后序遍历,这个是最复杂的,原因是我们需要判断某一时刻我们是否已经访问过他的左孩子和右孩子。只有访问完他的左孩子和右孩子之后才能访问这个节点(后序遍历是左右根的顺序),所以我们需要给每个节点一个标志,标志其是否可以进行访问操作,这里我使用HashMap来做,给每一个节点配一个boolean变量。后序非递归遍历代码如下:
private static void postOrderRecursionNotDisplay(BinaryTreeNode root) {
// TODO Auto-generated method stub
//后序遍历非递归版
if (root == null) {
throw new RuntimeException("此树为空");
}
ArrayDeque<BinaryTreeNode> stack = new ArrayDeque<BinaryTreeNode>();
HashMap<BinaryTreeNode , Boolean> judgeVisit = new HashMap<BinaryTreeNode , Boolean>();
while(root != null || !stack.isEmpty()){
while(root != null){
stack.push(root);
root = root.getM_pleft();
}
if (!stack.isEmpty()) {
root = stack.pop();
if (root.getM_pright() != null && !judgeVisit.containsKey(root)) {
// !judgeVisit.containsKey(root)防止访问完右孩子后回到根节点的root陷入循环!
stack.push(root);
judgeVisit.put(root, true);
root = root.getM_pright();
}else{
//root的右孩子为空,或者此时根节点已经做过了访右孩子处理!
judgeVisit.put(root, true);
}
}
if (judgeVisit.containsKey(root)) {
System.out.print(root.getM_value() + " ");
root = null;
//令root = null 是为了防止访问过右孩子后再次进入访左孩子循环。
}
}
}
最后是层次遍历,层次遍历要求我们先输出第一层的所有节点(第一层只有一个根节点),接着为第二层,第三层......一直到所有节点输出完毕为止。每一层的节点唯一来源就是上一层节点的左孩子和右孩子,因此这里和前面的前中后序遍历不同的是,前中后序遍历首先处理叶子结点,在处理上一层,依次循环,所以它需要后进先出(LIFO)的栈结构,而这里的层次遍历则需要的是先处理每一层的所有节点,再往下处理,因此这里需要的是先进先出(FIFO)的队列结构。我们每到一个节点时,访问其节点数据,然后将其左孩子和右孩子进队列,然后在访问下一个从队列里弹出来的节点,一直循环到队列为空即可。代码如下:
private static void levelRecursion(BinaryTreeNode root) {
// TODO Auto-generated method stub
//层次遍历
if (root == null) {
throw new RuntimeException("此树为空!");
}
ArrayDeque<BinaryTreeNode> array = new ArrayDeque<BinaryTreeNode>();
while (root != null) {
System.out.print(root.getM_value() + " ");
if (root.getM_pleft() != null) {
array.add(root.getM_pleft());
}
if (root.getM_pright() != null) {
array.add(root.getM_pright());
}
if (!array.isEmpty()) {
root = array.remove();
}else{
root = null;
//遍历结束令其为null使循环结束
}
}
}
到这里,基本上所有的访问二叉树的方法就结束了。下面是测试的结果和所有的源代码:
package com.small;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.HashMap;
import com.small.BinaryTree.BinaryTreeNode;
/*
* 利用一棵树的前序遍历和中序遍历形成一颗二叉树,
* 并对该二叉树进行递归和非递归版的前根序,中根序,后根序遍历以及层次遍历
*
*/
public class BinaryTree {
static class BinaryTreeNode{
private BinaryTreeNode m_pleft = null;
private BinaryTreeNode m_pright = null;
private int m_value = 0;
public BinaryTreeNode getM_pleft() {
return m_pleft;
}
public void setM_pleft(BinaryTreeNode m_pleft) {
this.m_pleft = m_pleft;
}
public BinaryTreeNode getM_pright() {
return m_pright;
}
public void setM_pright(BinaryTreeNode m_pright) {
this.m_pright = m_pright;
}
public int getM_value() {
return m_value;
}
public void setM_value(int m_value) {
this.m_value = m_value;
}
@Override
public String toString() {
return "BinaryTreeNode [ m_value=" + m_value + "]";
}
}
public static void main(String[] args) {
int[] front = {1,2,4,7,3,5,6,8};
int[] middle = {4,7,2,1,5,3,8,6};
BinaryTreeNode node = findTree(front , middle);
System.out.println("前序遍历(递归):");
preOrderRecursionDisPlay(node);
System.out.println();
System.out.println("中序遍历(递归):");
inOrderRootRecursionDisplay(node);
System.out.println();
System.out.println("后序遍历(递归):");
postOrderRecursionDisplay(node);
System.out.println();
System.out.println("前序遍历(非递归):");
preOrderRecursionNotDisplay(node);
System.out.println();
System.out.println("中序遍历(非递归):");
inOrderRecursionNotDisplay(node);
System.out.println();
System.out.println("后序遍历(非递归):");
postOrderRecursionNotDisplay(node);
System.out.println();
System.out.println("层次遍历:");
levelRecursion(node);
System.out.println();
}
private static void levelRecursion(BinaryTreeNode root) {
// TODO Auto-generated method stub
//层次遍历
if (root == null) {
throw new RuntimeException("此树为空!");
}
ArrayDeque<BinaryTreeNode> array = new ArrayDeque<BinaryTreeNode>();
while (root != null) {
System.out.print(root.getM_value() + " ");
if (root.getM_pleft() != null) {
array.add(root.getM_pleft());
}
if (root.getM_pright() != null) {
array.add(root.getM_pright());
}
if (!array.isEmpty()) {
root = array.remove();
}else{
root = null;
//遍历结束令其为null使循环结束
}
}
}
private static void preOrderRecursionDisPlay(BinaryTreeNode root){
//前序遍历递归版
if (root == null) {
throw new RuntimeException("此树为空!");
}
System.out.print(root.getM_value()+" ");
if (root.getM_pleft() != null) {
preOrderRecursionDisPlay(root.getM_pleft());
}
if (root.getM_pright() != null) {
preOrderRecursionDisPlay(root.getM_pright());
}
}
private static void preOrderRecursionNotDisplay(BinaryTreeNode root) {
//前序遍历非递归版
// TODO Auto-generated method stub
if (root == null) {
throw new RuntimeException("此树为空");
}
ArrayDeque<BinaryTreeNode> stack = new ArrayDeque<BinaryTreeNode>();
while(root != null || !stack.isEmpty()){
//这里的!isEmpty()使得程序允许以下这种情况进入while循环:当root成为叶子结点的左孩子(空节点)
while(root != null){
System.out.print(root.getM_value() + " ");
//访根
stack.push(root);
//入栈,以便于以后访问它的右孩子
root = root.getM_pleft();
}
if (!stack.isEmpty()) {
root = stack.pop().getM_pright();
//当有右孩子时,访问右孩子,进入下一次循环,
//无右孩子时,因为isEmpty()的存在,允许进入循环但不进入访问左孩子循环(因为栈里的对象左孩子之前已经访问过了)
}
}
}
private static void inOrderRootRecursionDisplay(BinaryTreeNode root) {
// TODO Auto-generated method stub
//中序遍历递归版
if (root == null) {
throw new RuntimeException("此树为空");
}
if (root.getM_pleft() != null) {
inOrderRootRecursionDisplay(root.getM_pleft());
}
System.out.print(root.getM_value() + " ");
if (root.getM_pright() != null) {
inOrderRootRecursionDisplay(root.getM_pright());
}
}
private static void inOrderRecursionNotDisplay(BinaryTreeNode root) {
// TODO Auto-generated method stub
//中序遍历非递归版
if (root == null) {
throw new RuntimeException("此树为空");
}
ArrayDeque<BinaryTreeNode> stack = new ArrayDeque<BinaryTreeNode>();
while(root != null || !stack.isEmpty()){
while(root != null){
stack.push(root);
root = root.getM_pleft();
}
if(!stack.isEmpty()){
root = stack.pop();
System.out.print(root.getM_value() + " ");
root = root.getM_pright();
}
}
}
private static void postOrderRecursionDisplay(BinaryTreeNode root) {
// TODO Auto-generated method stub
//后序遍历递归版
if (root == null) {
throw new RuntimeException("此树为空");
}
if (root.getM_pleft() != null) {
postOrderRecursionDisplay(root.getM_pleft());
}
if (root.getM_pright() != null) {
postOrderRecursionDisplay(root.getM_pright());
}
System.out.print(root.getM_value() + " ");
}
private static void postOrderRecursionNotDisplay(BinaryTreeNode root) {
// TODO Auto-generated method stub
//后序遍历非递归版
if (root == null) {
throw new RuntimeException("此树为空");
}
ArrayDeque<BinaryTreeNode> stack = new ArrayDeque<BinaryTreeNode>();
HashMap<BinaryTreeNode , Boolean> judgeVisit = new HashMap<BinaryTreeNode , Boolean>();
while(root != null || !stack.isEmpty()){
while(root != null){
stack.push(root);
root = root.getM_pleft();
}
if (!stack.isEmpty()) {
root = stack.pop();
if (root.getM_pright() != null && !judgeVisit.containsKey(root)) {
// !judgeVisit.containsKey(root)防止访问完右孩子后回到根节点的root陷入循环!
stack.push(root);
judgeVisit.put(root, true);
root = root.getM_pright();
}else{
//root的右孩子为空,或者此时根节点已经做过了访右孩子处理!
judgeVisit.put(root, true);
}
}
if (judgeVisit.containsKey(root)) {
System.out.print(root.getM_value() + " ");
root = null;
//令root = null 是为了防止访问过右孩子后再次进入访左孩子循环。
}
}
}
public static BinaryTreeNode findTree(int[] front , int[] middle){
if (front == null || middle == null) {
return null;
}
if (front.length == 0) {
//当长度为1时直接返回不用再分了
BinaryTreeNode newNode = null;//注意:这里不能写成 = new BinaTreeNode! 否则遍历此树时无法结束
return newNode;
}
if (front.length == 1) {
BinaryTreeNode newNode = new BinaryTreeNode();
newNode.setM_value(front[0]);
return newNode;
}
BinaryTreeNode root = new BinaryTreeNode(); //注意,必须直接把内部类写成静态的,否则必须要先实例化外部类才可以。
root.setM_value(front[0]);//前序第一个数为这个树的根
//寻找树的根在中序遍历中的位置
int rootNumberIndex = 0;
for (int i = 0; i < middle.length; i++) {
if (middle[i] == front[0]) {
rootNumberIndex = i;
}
}
//新的左边的中序
int[] newLeftMiddle = null;
newLeftMiddle = Arrays.copyOf(middle, rootNumberIndex );
//新的右边的中序
int[] newRightMiddle = null;
newRightMiddle = Arrays.copyOfRange(middle, rootNumberIndex + 1, middle.length);
//新的左边的前序遍历
int[] newLeftFront = new int[newLeftMiddle.length];
//这块有错!!!
//这里是要找到front里面找到newLeftMiddle的顺序组成newLeftFront
int k = 0;
for (int j = 0; j < front.length; j++) {
if(judgeContain(newLeftMiddle,front[j])){
newLeftFront[k] = front[j];
k++;
}
}
//新的右边的前序遍历
int[] newRightFront = new int[newRightMiddle.length];
int k2 = 0;
for (int j = 0; j < front.length; j++) {
if (judgeContain(newRightMiddle , front[j])) {
newRightFront[k2] = front[j];
k2++;
}
}
root.setM_pleft(findTree(newLeftFront , newLeftMiddle));
root.setM_pright(findTree(newRightFront , newRightMiddle));
return root;
}
private static boolean judgeContain(int[] intIndex, int number) {
// TODO to judge the intIndex if have the number
for (int i : intIndex) {
if( i == number){
return true;
}
}
return false;
}
}