首先要知道,递归遍历时系统会自动帮我们做入栈出栈操作,那么非递归时无非就是我们自己去调用栈。
一.先序遍历最好理解:
1.头节点入栈
2.弹栈,保存弹出的结果
3.把刚刚弹出的节点的右节点和左节点压栈(先压右节点)
4.重复2-3步直到栈空了
var preorderTraversal = function (root) {
let arr=[],node=null,res=[]
if(root==null) return res //空树
arr.push(root)
while(arr.length!=0){
node=arr.pop() //原树或其子树根节点弹栈
res.push(node.val) //弹出的值保存到结果
if(node.right!=null) arr.push(node.right) //先压栈右子树则其后出栈
if(node.left!=null) arr.push(node.left) //后压栈左子树则其先出栈
}
return res
};
对于大树及其任何一个子树,都遵循先输出头节点,再压入右孩子,再压入左孩子的规则。这里就保证了先序遍历中的头节点先输出。在弹栈时,由于任何两个左右子树都是左子树后进栈,所以左子树必然先出栈,这就实现了“头左右”的顺序。
二.后序
后序是由先序的方法改进而来。改动有两点:1.保存出栈后的值不用push而是unshift,这样实现了输出结果数组时头节点的值总是在其子节点的后面。2.左右孩子入栈时先入左后入右,这样实现了在出栈时右孩子先被遍历,即右孩子在结果数组里的位置在左孩子后面。
var postorderTraversal = function(root) {
let arr=[],node=null,res=[]
if(root==null) return res
arr.push(root)
while(arr.length!=0){
node=arr.pop() //根节点出栈
res.unshift(node.val) //出栈的根节点放结果数组最后
if(node.left!=null) arr.push(node.left) //先压栈左子树,则左子树后unshift进结果数组
if(node.right!=null) arr.push(node.right) //先压栈右子树,则右子树先unshift进结果数组
}
return res
};
三.中序
中序的思路较前两个复杂一点:
1.左边界上的全部节点入栈
2.弹栈,保存弹出的结果
3.刚刚弹出的节点若存在右子树,在该子树上执行1.,无右子树则执行2.
var inorderTraversal = function(root) {
let arr=[],res=[],node=null
if(root==null) return res
while(root!=null||arr.length!=0){
if(root!=null){ //分支一:每遇到一个节点先把其左边界压栈
arr.push(root)
root=root.left
}else{ //分支二:左边界已压栈完,弹栈,每弹一个节点,就去保存值并处理该节点的右子树
root=arr.pop()
res.push(root.val)
root=root.right //存在右子树则进入分支一把左边界压栈,无右子树则进入分支二继续出栈
}
}
return res
};
他是怎么实现中序遍历的:while里的事情可总结为:从左边界开始遍历(发生在弹栈时,此时保存的结果是“左头右”中的左),发现某节点有右子树后(此时该节点值已保存进结果,即“左头右”中的头),则转移到该右子树上,重复上述操作。直到遍历完某个右子树后继续遍历之前栈里的左边界。