二叉树个各种遍历(递归加非递归)
先中后序遍历的操作定义
首先明确前中后序遍历二叉树时,结点、左子树、右子树的先后次序。
+ 先序遍历,遍历顺序是:父结点、左子树(先序遍历左子树,顺序同理,左子树父节点、该父节点的左子树,该父节点的右子树)、右子树(同左子树)。
中序遍历,遍历顺序是:左子树(中序遍历左子树,顺序同理,左子树父节点、该父节点的左子树,该父节点的右子树)、父结点、右子树(同左子树)。
后序遍历,遍历顺序是:左子树(后序遍历左子树,顺序同理,左子树父节点、该父节点的左子树,该父节点的右子树)、右子树(同左子树)、父节点。
递归遍历方法
按照上面的遍历顺序,可以很轻松的用递归实现二叉树的遍历。
//是哪个顺序,就把其他顺序的visit方法调用注释掉即可
void BTreeTraverse(TreeNode node){
if(nede == null)
return;
//先序遍历
visit(node);
BTreeTraverse(node.leftChild);
//中序遍历
visit(node);
BTreeTraverse(node.rightChild);
//后序遍历
visit(node);
return;
}
非递归遍历方法
前序非递归遍历
void preOrderTraverse(BTreeNode node){
Stack<BTreeNode> stack = new Stack<BTreeNode>();
BTreeNode temp;
if(node == null)
return;
//根结点入栈
stack.push(node);
while(!stack.empty()){
//向左走到尽头,压入结点前访问结点元素
while( (temp =stack.peek()) != null ){
//访问结点元素
visit(temp);
//压入左孩子
stack.push(temp.leftChild);
}
//最左叶子结点的左孩子为null,需要将null弹出
stack.pop();
//判断当前栈是否为空,不为空则弹出栈顶元素,压入该元素的右孩子
if(!stack.empty()){
temp = stack.pop();
stack.push(temp.rightChild);
}//接下来的循环会继续从这个右孩子开始向左走
}
}
中序非递归遍历
void inOrderTraverse(BTreeNode node){
Stack<BTreeNode> stack = new Stack<BTreeNode>();
BTreeNode temp;
if(node == null)
return;
//根结点入栈
stack.push(node);
while(!stack.empty()){
//向左走到尽头
while( (temp =stack.peek()) != null ){
//压入左孩子
stack.push(temp.leftChild);
}
//最左叶子结点的左孩子为null,需要将null弹出
stack.pop();
//判断当前栈是否为空,不为空则弹出栈顶元素,访问元素并压入该元素的右孩子
if(!stack.empty()){
temp = stack.pop();
visit(temp);
stack.push(temp.rightChild);
}//接下来的循环会继续从这个右孩子开始向左走
}
}
前序和中序非递归遍历的区别仅仅在于visit()
函数被调用的时间不同。
后序非递归遍历
在进行后序非递归遍历时,如果再像考虑前中序非递归遍历一样,只想移动visit()
方法的调用位置,就会陷入一个误区——在后序中,后序遍历是有条件的,即父节点是在子结点全部被遍历完后才被遍历。这样就需要判断一下当前最后一次访问的结点是否是当前节点的右子树,如果是,则遍历当前节点,否则,将该结点的右子树压入栈中。
这里有两种实现
void postOrderTraverse(BTreeNode node){
Stack<BTreeNode> stack = new Stack<BTreeNode>();
BTreeNode temp = node;
BTreeNode preNode = null;//该变量存储上一次访问的结点
while(temp != null || !stack.empty()){
//寻找temp的最左叶子结点
while(temp != null){
stack.push(temp);
temp = temp.leftChild;
}
//获取当前栈顶元素
temp = stack.peek();
//如果当前节点没有右子树或者右子树是上一次遍历的结点,访问当前节点
if(temp.rightChild == null || temp.rightChild == preNode){
visit(temp);
temp = stack.pop();
preNode = temp;
temp = null;//遍历完当前节点后,就应该再从栈中pop了
}else{
//否则说明这个右子树没有被遍历过,将这个右子树压入栈中
temp = temp.rightChild;
}
}
}
我比较喜欢双栈法,比较巧妙
void postOrderTraverse(BTreeNode node){
Stack<BTreeNode> stack_temp = new Stack<BTreeNode>();
Stack<BTreeNode> stack_result = new Stack<BTreeNode>();
BTreeNode temp = node;
stack_temp.push(temp);
//先将树中的结点按照下面的方式从一个栈里拿出来放到结果栈里
while(!stack_temp.empty()){
temp = stack_temp.push();
stack_result.push(temp);
//把非空在孩子结点放到temp栈中
if(temp.leftChild != null)
stack_temp.push(temp.leftChild);
if(temp.rightChild != null)
stack_temp.push(temp.rightChild);
temp = temp.leftChild;
}
//访问结果栈中的结点
while(!stack_result.empty()){
temp = stack_result.pop();
visit(temp);
}
}