今天令我欣慰的是我终于弄懂了一个难点:
用堆栈实现二叉树的后序遍历!! ^_^
技术在于积累,积累在于坚持,每天进步一点点!
基于此,把二叉树的遍历总结一下,这里二叉树的层序遍历先不考虑,
主要是总结关于二叉树的前序、中序和后序遍历,提供了两种思路,递归和堆栈,
堆栈思路中用到了树的高度,因此要会求树的高度。
1、理解二叉树的前序、中序、后序的概念:
假设有一颗二叉树如下图所示:
A
/ \
B C
/ \ /
D E F
前序: ABDECF
首先,访问根节点
然后,访问左子树
最后,访问右子树
中序: DBEAFC
首先,访问左子树
然后,访问根节点
最后,访问右子树
后序: DEBFCA
首先,访问左子树
然后,访问右子树
最后,访问根节点
2、二叉树的节点
public class BTNode
{
public int value; // 关键值 根节点
BTNode left; // 左子树
BTNode right; // 右子树
}
3、二叉树的递归遍历
前序遍历:
public void printPreOrder(BTNode root)
{
if (root != null)
{
System.out.print(root.value + " ");
printPreOrder(root.left);
printPreOrder(root.right);
}
}
中序遍历:
public void printInOrder(BTNode root)
{
if (root != null)
{
printInOrder(root.left);
System.out.print(root.value + " ");
printInOrder(root.right);
}
}
后序遍历:
public void printPostOrder(BTNode root)
{
if (root != null)
{
printPostOrder(root.left);
printPostOrder(root.right);
System.out.print(root.value + " ");
}
}
4、二叉树的高度 (递归思想)
public int height(BTNode root)
{
int leftHeight = 0,rightHeight = 0;
if (root.left != null)
leftHeight = height(root.left);
if (root.right != null)
rightHeight = height(root.right);
return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}
5、二叉树的堆栈遍历
前序遍历:
public void printInOrder2(BTNode root)
{
if (root == null)
return;
ArrayStack<BTNode> stack = new ArrayStack<>(height(root) + 1); // 其实不用加1也行
BTNode current = root,node = null;
while (current != null || !stack.isEmpty())
{
while (current != null) // 三序遍历永远都是将连续的左子树压栈!
{
System.out.print(current.value + " "); // 前序遍历首先在压栈前打印关键字
stack.push(current);
current = current.left;
}
node = stack.pop(); // 前序、中序可以直接弹出,后序不可以,得先窥探。
if (node.right != null) // 如果某个节点有右子树,那么就将该右子树压栈。
current = node.right;
}
System.out.println(); // 打印换行
}
中序遍历:
public void printPreOrder2(BTNode root)
{
if (root == null)
return;
ArrayStack<BTNode> stack = new ArrayStack<>(height(root) + 1);
BTNode current = root,node = null;
while (current != null || !stack.isEmpty()) // 最终的退出条件为 current == null 和 栈为空
{
while (current != null) // 三序遍历永远都是将连续的左子树压栈!
{
stack.push(current);
current = current.left;
}
node = stack.pop(); // 弹出,打印
System.out.print(node.value + " "); // 前序、中序只是打印函数的位置不同,其他都相同,后序的相对复杂
if (node.right != null) // 如果某个节点的有右子树,就将该右子树压栈。
current = node.right;
}
System.out.println(); // 打印换行
}
后序遍历(较复杂):
public void printPostOrder2(BTNode root)
{
if (root == null)
return;
int h = height(root); // 同上,先求出树的高度
ArrayStack<BTNode> stack = new ArrayStack<>(h + 1); // 其实 h 个空间就够,自己实现的 stack
BTNode current = root,father = null,node = null;
while (current != null || !stack.isEmpty())
{
while (current != null)
{
stack.push(current);
current = current.left;
}
father = stack.peer(); // 先别出栈,先窥探父节点有没有右子树
if (father.right != null && father.right != node) // 括号中的 father.right != node 是关键的条件,
current = father.right; // 没有这个条件,会造成程序进入死循环!
else
{
node = stack.pop();
System.out.print(node.value + " ");
}
}
System.out.println();
}
用堆栈进行二叉树的遍历,前序遍历和中序遍历特别特别相似,只有一句话的位置不同,剩下的都相同,区别二者的不同谨记:
前序遍历: 中->左->右,应该在节点压入栈之前就要打印出来!
中序遍历: 左->中->右,应该在节点被栈弹出后再打印出来!
最不好实现的要数后序遍历了,因为后序遍历遵循 左->右->中 的思路,因此根节点最后输出,在输出打印完左子树的节点
后,如果有右子树,那么就要首先把右子树再压入栈中,而不是急着把根节点弹出来打印,这是其一。
其二,后序遍历中的辅助变量中比前序和中序多了一个 father,这是很有意义的,因为在右子树中的节点被栈弹出后,此时
窥探到栈中右是刚刚判断的是否有右子树的父节点,此时 如果不加以下限定条件,则就又把右子树的节点压入栈中,从而
造成程序进入死循环状态,该限定条件为:
father.right != node;
即栈中栈顶的节点和刚刚弹出的节点存在着父子关系,确切说是右孩子的关系时,那么程序就不会再次进入 第二个 if
语句,也就不会再次压右子树入栈,造成死循环了!
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
最重要的一点就是 堆栈遍历拥有比递归遍历一大优点就是:
堆栈遍历的效率要高于递归遍历的效率,原因就是递归造成的重复性太多 !
因此,二叉树的深度遍历最好选择 堆栈法遍历!!
明日新晨
2017.7.20.
用堆栈实现二叉树的后序遍历!! ^_^
技术在于积累,积累在于坚持,每天进步一点点!
基于此,把二叉树的遍历总结一下,这里二叉树的层序遍历先不考虑,
主要是总结关于二叉树的前序、中序和后序遍历,提供了两种思路,递归和堆栈,
堆栈思路中用到了树的高度,因此要会求树的高度。
1、理解二叉树的前序、中序、后序的概念:
假设有一颗二叉树如下图所示:
A
/ \
B C
/ \ /
D E F
前序: ABDECF
首先,访问根节点
然后,访问左子树
最后,访问右子树
中序: DBEAFC
首先,访问左子树
然后,访问根节点
最后,访问右子树
后序: DEBFCA
首先,访问左子树
然后,访问右子树
最后,访问根节点
2、二叉树的节点
public class BTNode
{
public int value; // 关键值 根节点
BTNode left; // 左子树
BTNode right; // 右子树
}
3、二叉树的递归遍历
前序遍历:
public void printPreOrder(BTNode root)
{
if (root != null)
{
System.out.print(root.value + " ");
printPreOrder(root.left);
printPreOrder(root.right);
}
}
中序遍历:
public void printInOrder(BTNode root)
{
if (root != null)
{
printInOrder(root.left);
System.out.print(root.value + " ");
printInOrder(root.right);
}
}
后序遍历:
public void printPostOrder(BTNode root)
{
if (root != null)
{
printPostOrder(root.left);
printPostOrder(root.right);
System.out.print(root.value + " ");
}
}
4、二叉树的高度 (递归思想)
public int height(BTNode root)
{
int leftHeight = 0,rightHeight = 0;
if (root.left != null)
leftHeight = height(root.left);
if (root.right != null)
rightHeight = height(root.right);
return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}
5、二叉树的堆栈遍历
前序遍历:
public void printInOrder2(BTNode root)
{
if (root == null)
return;
ArrayStack<BTNode> stack = new ArrayStack<>(height(root) + 1); // 其实不用加1也行
BTNode current = root,node = null;
while (current != null || !stack.isEmpty())
{
while (current != null) // 三序遍历永远都是将连续的左子树压栈!
{
System.out.print(current.value + " "); // 前序遍历首先在压栈前打印关键字
stack.push(current);
current = current.left;
}
node = stack.pop(); // 前序、中序可以直接弹出,后序不可以,得先窥探。
if (node.right != null) // 如果某个节点有右子树,那么就将该右子树压栈。
current = node.right;
}
System.out.println(); // 打印换行
}
中序遍历:
public void printPreOrder2(BTNode root)
{
if (root == null)
return;
ArrayStack<BTNode> stack = new ArrayStack<>(height(root) + 1);
BTNode current = root,node = null;
while (current != null || !stack.isEmpty()) // 最终的退出条件为 current == null 和 栈为空
{
while (current != null) // 三序遍历永远都是将连续的左子树压栈!
{
stack.push(current);
current = current.left;
}
node = stack.pop(); // 弹出,打印
System.out.print(node.value + " "); // 前序、中序只是打印函数的位置不同,其他都相同,后序的相对复杂
if (node.right != null) // 如果某个节点的有右子树,就将该右子树压栈。
current = node.right;
}
System.out.println(); // 打印换行
}
后序遍历(较复杂):
public void printPostOrder2(BTNode root)
{
if (root == null)
return;
int h = height(root); // 同上,先求出树的高度
ArrayStack<BTNode> stack = new ArrayStack<>(h + 1); // 其实 h 个空间就够,自己实现的 stack
BTNode current = root,father = null,node = null;
while (current != null || !stack.isEmpty())
{
while (current != null)
{
stack.push(current);
current = current.left;
}
father = stack.peer(); // 先别出栈,先窥探父节点有没有右子树
if (father.right != null && father.right != node) // 括号中的 father.right != node 是关键的条件,
current = father.right; // 没有这个条件,会造成程序进入死循环!
else
{
node = stack.pop();
System.out.print(node.value + " ");
}
}
System.out.println();
}
用堆栈进行二叉树的遍历,前序遍历和中序遍历特别特别相似,只有一句话的位置不同,剩下的都相同,区别二者的不同谨记:
前序遍历: 中->左->右,应该在节点压入栈之前就要打印出来!
中序遍历: 左->中->右,应该在节点被栈弹出后再打印出来!
最不好实现的要数后序遍历了,因为后序遍历遵循 左->右->中 的思路,因此根节点最后输出,在输出打印完左子树的节点
后,如果有右子树,那么就要首先把右子树再压入栈中,而不是急着把根节点弹出来打印,这是其一。
其二,后序遍历中的辅助变量中比前序和中序多了一个 father,这是很有意义的,因为在右子树中的节点被栈弹出后,此时
窥探到栈中右是刚刚判断的是否有右子树的父节点,此时 如果不加以下限定条件,则就又把右子树的节点压入栈中,从而
造成程序进入死循环状态,该限定条件为:
father.right != node;
即栈中栈顶的节点和刚刚弹出的节点存在着父子关系,确切说是右孩子的关系时,那么程序就不会再次进入 第二个 if
语句,也就不会再次压右子树入栈,造成死循环了!
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
最重要的一点就是 堆栈遍历拥有比递归遍历一大优点就是:
堆栈遍历的效率要高于递归遍历的效率,原因就是递归造成的重复性太多 !
因此,二叉树的深度遍历最好选择 堆栈法遍历!!
明日新晨
2017.7.20.