递归法与迭代法实现树的遍历
递归法
首先树的遍历分为,前序遍历,中序遍历,后序遍历。
前序遍历: 对于当前结点,先对该结点进行操作,然后对他的左孩子进行操作,最后对他的右孩子进行操作。
中序遍历: 对于当前结点,先对该结点的左孩子进行操作,然后对该结点进行操作,最后对他的右孩子进行操作。
后序遍历: 对于当前结点,先对该结点的左结点进行操作,然后对右孩子进行操作,最后多该结点进行操作。
递归序:
其实对于树的遍历,递归法实现是比较简单的,只需要调整递归函数调用的位置即可,可是为什么递归能实现前序遍历,中序遍历和后序遍历呢?实际上是由递归时访问结点的顺序决定的。
public void recur(Node head){
if(head == null) return;
// 1
recur(head.left);
// 2
recur(head.right);
// 3
}
以上为递归遍历树时的通用代码,我们发现,对于当前结点head,在当前这个recur中,其实有三次都会返回到该结点,如注释1,这时当前结点为head结点;对于注释2,由于调用recur(head.left)结束,会再次返回到该结点;对于注释3,由于调用recur(head.right)结束,会再次返回到该结点,因此总共有三次会访问同一结点。
对于该树,递归序为,1,2,4,4,4,2,5,5,5,2,1,3,6,6,6,3,7,7,7,3,1。
因此前序中序后序遍历,就是在分别在第一次第二次第三次到达该结点时进行操作,例如,前序遍历则是在1时,此时是第一次访问到该结点,因此先对该结点进行操作,就可以实现先序遍历。知道了递归序,递归实现树的三种遍历就很容易了,代码如下:
先序遍历:
public void preOrderRecur(Node head){
if(head == null) return;
System.out.print(head.val);
recur(head.left);
recur(head.right);
}
中序遍历:
public void inOrderRecur(Node head){
if(head == null) return;
recur(head.left);
System.out.print(head.val);
recur(head.right);
}
后序遍历:
public void posOrderRecur(Node head){
if(head == null) return;
recur(head.left);
recur(head.right);
System.out.print(head.val);
}
迭代法:
两种方式是等价的,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同。
先序遍历迭代法
首先我们可以先定义一个栈,然后重复进行一下操作:
- 从栈中弹出当前结点
- 对当前结点进行处理
- 如果该结点有右结点或左结点,按先右后左的顺序压入栈中。
- 循环上述操作。
// 先序遍历!
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if (root == null){
return res;
}
Deque<TreeNode> stack = new LinkedList<TreeNode>();
if(root != null){
stack.push(root);
}
while(root != null && !stack.isEmpty() ){
root = stack.pop(); //要注意弹出的不能为null,因此循环中需要判断stack不为空
res.add(root.val); // 弹出后进行操作。
if(root.right != null){
stack.push(root.right);
}
if(root.left != null){
stack.push(root.left);
}
}
return res;
}
}
中序遍历迭代法:
- 首先定义一个栈,将每棵子树的左边依次进栈
- 依次弹出结点,对该结点进行操作
- 对于弹出结点的右子树,重复上述操作。
即一直向左遍历,并且在遍历途中先将根结点和压入栈中,然后弹出左叶子结点后先判断是否其存在右结点(左结点在循环中已经判断),如果不存在则将其根结点进行弹出并输出,若存在,则继续向该右结点重复上述向左遍历的操作。
public static void inOrderUnRecur(Node root) {
System.out.print("in-order: ");
if (root != null) {
Deque<TreeNode> stack = new LinkedList<TreeNode>();
while (!stack.isEmpty() || root != null) {
if (root != null) {
stack.push(root);
root = root.left;
} else {
root = stack.pop();
System.out.print(root.value + " ");
root = root.right;
}
}
}
System.out.println();
}
后序遍历迭代法
对于后序遍历,在树的知识点与算法中给出了一种遍历方法,但是可能会比较难理解,因此这里给出第二种方法。
首先由于根节点需要最后访问,而找到它的左右结点一定要先经过根结点,因此需要两个栈来维护访问顺序。栈1为访问所有结点的中介栈,栈2为最终所有结点的收集栈。我们要通过栈1实现压入栈2的顺序为头右左,这样在栈2中打印出来的顺序则可以变为左右头,实现后序遍历。
- 从栈1弹出当前结点,放入栈2
- 如果有左右结点,按先左后右的方式放入栈1中。
- 循环上述操作直至所有结点压入栈2
- 依次对栈2的结点进行操作。
public void posOrderUnrecur(TreeNode root){
if(root!=null){
Deque<TreeNode> s1 = new LinkedList<TreeNode>(); // 中介栈
Deque<TreeNode> s1 = new LinkedList<TreeNode>(); // 收集栈
s1.push(root);
while(!s1.isEmpty()){
root = s1.pop();
s2.push(root);
if(root.left != null){
s1.push(root.left); // 先放左
}
if(root.right != null){
s1.push(root.right); // 后方右
}
}
while(!s2.isEmpty()){
System.out.print(s2.pop().val);
}
}
}
如有错误,请大家指出,大家一起进步。