二叉树的各种遍历
1. 先序/后序/中序遍历
递归版本的就不谈了,主要看非递归版本。
首先定义一个术语 左路径,它指的是从某个节点开始(包括该节点),沿着左孩子向下走直到叶子节点,所形成的路径。
3个顺序的遍历都遵循以下代码框架:
Node root = ... ;
while(root != null){ // 1. 用以 root 为起点的左路径初始化栈
stack.push(root);
root = root.left;
}
while(stack不为空){ // 当栈不为空,循环:
Node n = stack.pop(); // 2. 从栈中 pop 一个节点出来
Node tmp = n.right; // 3. 将该节点 *右孩子* 的左路径入栈
while(tmp != null){
stack.push(tmp);
tmp = tmp.left;
}
}
简单来说就是下面两步:
- 用根的左路径初始化栈;
- 循环从栈中 pop 节点,并将该节点右孩子的左路径入栈
其实就是 深度优先遍历 的思路,只在一些细节地方不同。是不是很简单呢?
先序和中序
先序遍历的过程中,每个节点的访问时机是在节点 入栈 时,因此对上述代码框架,在两处 push 处加上对节点的访问逻辑即可。
Stack<TreeNode> s = new Stack<TreeNode>();
while(root != null){
visit(root); // <-- 1 visit when push
s.push(root);
root = root.left;
}
while(!s.isEmpty()){
TreeNode tmp = s.pop();
tmp = tmp.right;
while(tmp != null){
visit(tmp); // <-- 2 visit when push
s.push(tmp);
tmp = tmp.left;
}
}
对于中序,每个节点的访问时机是在节点 出栈 时,因此应当在 while 循环内每次 pop 时对节点进行访问。
Stack<TreeNode> s = new Stack<TreeNode>();
while(root != null){
s.push(root);
root = root.left;
}
while(!s.isEmpty()){
TreeNode tmp = s.pop();
visit(tmp); // <-- visit when pop
tmp = tmp.right;
while(tmp != null){
s.push(tmp);
tmp = tmp.left;
}
}
后序
后序稍微复杂一些,当一个节点从 stack pop 出来时,意味着 它的左子树已经访问完毕 了,先序和中序此时都可以直接去处理它的右子树;但是后序遍历要求访问完它的右子树后,再访问该节点。
因此对于后序遍历,节点第一次出栈时,必须将其重新入栈,再走后续流程(将右孩子的左路径入栈);当节点第二次出栈,说明 它的右子树也访问完了,这时才可以访问该节点。所以,对每个节点都必须用一个额外的标志位,记录它是初次还是第二次出栈。
代码如下:
Stack<TreeNode> s = new Stack<TreeNode>();
while(root != null){
s.push(root);
root = root.left;
}
while(!s.isEmpty()){
TreeNode tmp = s.pop();
if(tmp.poped){ // 1. 如果是第二次出栈,访问它
visit(tmp);
}else{
tmp.poped = true; // 2. 如果是第一次出栈,标记并重新入栈
s.push(tmp);
tmp = tmp.right;
while(tmp != null){
s.push(tmp);
tmp = tmp.left;
}
}
}
2. 层序遍历
基本的层序遍历用一个队列,循环出队并将其左右孩子依次入队,比较简单。
如果要区分每一行,则可以用一个标记节点标记一行的结束。遍历的过程中,如果出队的是标记节点,说明已经访问完了一行;此外也意味着这一行的孩子都已入队,标记节点应当重新入队。队列为空时遍历完成。
TreeNode mark = new TreeNode(0); // 行末标记
LinkedList<TreeNode> q = new LinkedList<TreeNode>();
q.add(root);
q.add(mark);
List<Integer> row = new LinkedList<Integer>();
List<List<Integer>> rows = new LinkedList<List<Integer>> ();
while(!q.isEmpty()){
TreeNode n = q.removeFirst();
if(n == mark){ // 碰到行末标记了
rows.add(row); // 说明访问完了一行
if(q.isEmpty()) break; // 队列为空则遍历完成
q.add(mark); // 这一行的全部孩子也已入队,将标记节点重新入队
row = new LinkedList<Integer>();
}else{
row.add(n.val);
if(n.left != null) q.add(n.left);
if(n.right != null) q.add(n.right);
}
}