后序遍历的逻辑: 左节点------右节点------根节点
递归方式
/**
* 递归的后序遍历: 左节点-右节点-跟节点
*/
public static void postTraversing(Tree root) {
// 1.判空
if (root == null) {
return;
}
// 2.递归左节点
preTraversing(root.left);
// 3. 递归右节点
preTraversing(root.right);
// 4.输出根节点
System.out.print(root.val + " ");
}
非递归方式
后序遍历的非递归方式有两种方式实现,分别是单栈方法和双栈方法,这里介绍的是双栈的两种实现方式;
法一:
思路: 利用两个栈来实现,一个栈保存是当前处理的节点,另外一个栈则是用来保存最后输出的结果,并且递归的过程其实转为非递归就是一个循环的过程,这里的循环结束条件为:栈1元素为空或者当前节点为空。
这里用图解方式进行讲解:
1.分别将根节点以及右子树压入栈1和栈2中,栈1是当前结果集,栈2是最终遍历的栈
2.弹出栈1中的元素,记住我们栈2是用来保存结果集,最后弹出结果集来遍历的。结果为(8,9)此时弹出的元素为9,则继续找9元素是否有左子树,此时9没有左子树,则继续弹出栈11
3. 栈中元素还不为空,则继续弹出栈中元素,此时弹出的栈中元素为8,栈1已经为空了。寻找元素8是否有左孩子,此时8有左孩子,则将左孩子入栈,并且遍历该左孩子的右子树也入栈,如图,则结果集为(8,9,6,7)
4. 继续弹出栈1元素,此时为7,则此时7没有左孩子,即继续弹出栈
5. 弹出栈中元素为6,则元素6有左孩子,则将左孩子5入栈1和栈2,此时为(8,9,6,7,5)
- 继续遍历,弹出栈1元素5,此时栈为空,且5没有左孩子,则退出循环
- 弹出栈2的元素,此时为(5,7, 6, 9, 8)
/**
* 非递归的后序遍历: 左节点-右节点-跟节点
* 双栈法的思路:
* 1. 一个栈用来表示当前处理的节点,另外一个栈用来输出
* 2. 在处理两个栈的时候,将根节点同时压入两个栈中,然后压入右子树的根节点,再压入右子树
* 3. 直到压入的右子树没有元素压栈,弹出栈1的元素,把它的左子树压入栈1和栈2里面,直到它的右子树压完。
*/
public static void postTraversing1(Tree root) {
// 1.判空
if (root == null) {
return;
}
// 2.栈1用来保存临时处理,栈2输出
LinkedList<Tree> stack1 = new LinkedList<Tree> ();
LinkedList<Tree> stack2 = new LinkedList<Tree> ();
Tree currentNode = root;
// 3.当前节点不为空或者栈1不为空
while (currentNode!=null || !stack1.isEmpty()) {
// 4.将右子树都压入栈1和栈2中
while (currentNode != null) {
stack1.push(currentNode);
stack2.push(currentNode);
currentNode = currentNode.right;
}
// 5.弹出栈1的元素,即右节点,然后赋值去找它的左节点
currentNode = stack1.pop();
currentNode = currentNode.left;
}
// 6.将存在栈2的元素进行遍历
while (!stack2.isEmpty()) {
System.out.print(stack2.pop().val+" ");
}
}
法二:
思路: 第二种思路也是使用两个栈,它也是使用一个栈来保存当前的指向,用一个栈来 保存结果集,只不过这个结果集是和后序遍历是反着的,比如后序遍历最后保存根节点,它就先存储根节点,后序遍历倒数第二个元素是根节点的右子树的元素,它就第二个保存根节点的右子树的元素。如果把树做一个镜像,也就是所有的节点的左右子树都进行一次交换的话
注意:这里循环结束的条件是栈1中的元素为空,则跳出循环
步骤:
1.将根节点压入栈1中
2.弹出栈1的元素8,将弹出的元素8压入栈2,然后将弹出的元素8的左孩子6压入栈1中,右孩子9压栈1中
3.弹出栈1的元素9,将弹出的元素压入栈2,然后将弹出的元素9的左右孩子压入栈1,这里元素9没有左右孩子
4.继续弹出栈1的元素6,将弹出的元素压入栈2,然后将弹出的元素6的左右孩子压入栈1,这里元素6的左孩子为元素5,右孩子为元素7,则如图
5.弹出栈1的元素7,将弹出的元素7压入栈2中,则弹出的元素7没有左右孩子,继续弹出栈1
6. 弹出栈1的元素5,将弹出的元素压入栈2中,则弹出的元素5没有左右孩子,继续弹出栈1
7.此时栈1元素为空,跳出循环
8.弹出栈2的所有元素
/**
*
* 后序遍历双栈法:
* 第二种思路也是使用两个栈,它也是使用一个栈来保存当前的指向,用一个栈来 保存结果集,只不过这个结果集是和后序遍历是反着的,比如后序遍历最后保存根节点,它就先存储根节点,后序遍历倒数第二个元素是根节点的右子树的元素,它就第二个保存根节点的右子树的元素。如果把树做一个镜像,也就是所有的节点的左右子树都进行一次交换的话
* @param root
*/
public static void postTraversing2(Tree root) {
// 1.判空
if (root == null) {
return;
}
// 2.栈1用来保存临时处理,栈2输出
LinkedList<Tree> stack1 = new LinkedList<Tree> ();
LinkedList<Tree> stack2 = new LinkedList<Tree> ();
stack1.push(root);
// 3.弹出栈1的元素
while (!stack1.isEmpty()) {
root = stack1.pop();
if (root == null) {
continue;
}
// 4.压入栈2元素
stack2.push(root);
stack1.push(root.left);
stack1.push(root.right);
}
// 5.弹出栈2的元素作为结果
while (!stack2.isEmpty()) {
System.out.print(stack2.pop().val+" ");
}
}