目录
- 定义与解释
- 递归代码实现
- 非递归代码实现
定义与解释
前序遍历:
前序遍历(VLR), [1] 是二叉树遍历的一种,也叫做先根遍历、先序遍历、前序周游,可记做根左右。前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。
中序遍历:
中序遍历(LDR)是二叉树遍历的一种,也叫做中根遍历、中序周游。在二叉树中,中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。
后序遍历:
后序遍历(LRD)是二叉树遍历的一种,也叫做后根遍历、后序周游,可记做左右根。后序遍历有递归算法和非递归算法两种。在二叉树中,先左后右再根,即首先遍历左子树,然后遍历右子树,最后访问根结点。
这里我们使用一个 二叉搜索树 来解释这三种遍历。
首先怎么来看这个图呢?
1.图中1一个圆圈代表一个树节点,里面的数组代表值。
2.图中红色的箭头线,代表遍历时的路径,编号代表先后顺序,从1开始一直往后。
3.实际叶子节点下面也可以看做有两个null的子节点,这里只画了叶子节点2下面的null子节点,其他叶子节点4,6,8也是一样可以看成有的。每次要走到null才会返回。
4.图中每一个节点都有4个红色箭头线连接,我们可以将最左边的看成第一次经过该节点,下面两条看成第二次经过该节点,最右边的看成第三次经过该节点。所以每个非null节点,会被经过3次。
5.为方便表示,使用线1表示红色箭头线1,点5表示节点5 。
前序遍历:
从根节点进来,第一次经过点5,打印5
线1,第一次经过点3,打印3;
线2,第一次经过点2,打印2;
线3,遇到null,往回走;
线4,第二次经过点2;
线5,遇到null,往回走;
线6,第三次经过点2;
线7,第二次经过点3;
线8,第一次经过点4,打印4;
。。。
最终结果为:5,3,2,4,7,6,8
总结:第一次经过非null节点,就打印该节点。
中序遍历:
从根节点进来,第一次经过点5;
线1,第一次经过点3;
线2,第一次经过点2;
线3,遇到null,往回走;
线4,第二次经过点2,打印2;
线5,遇到null,往回走;
线6,第三次经过点2;
线7,第二次经过点3,打印3;
线8,第一次经过点4;
。。。
最终结果为:2,3,4,5,6,7,8
总结:第二次经过非null节点,就打印该节点。
后序遍历:
从根节点进来,第一次经过点5;
线1,第一次经过点3;
线2,第一次经过点2;
线3,遇到null,往回走;
线4,第二次经过点2;
线5,遇到null,往回走;
线6,第三次经过点2,打印2;
线7,第二次经过点3;
线8,第一次经过点4;
。。。
最终结果为:2,4,3,6,8,7,5
总结:第三次经过非null节点,就打印该节点。
递归代码实现
使用Java,jdk1.8 。三种遍历用递归实现都非常简单。
// 前序遍历
private void DLR(BSTNode<T> node){
if(node==null){
return;
}
System.out.println(node.val);
DLR(node.leftNode);
DLR(node.rightNode);
}
public void DLR(){
DLR(root);
}
// 中序遍历
private void LDR(BSTNode<T> node){
if(node==null){
return;
}
LDR(node.leftNode);
System.out.println(node.val);
LDR(node.rightNode);
}
public void LDR(){
LDR(root);
}
// 后续遍历
private void LRD(BSTNode<T> node){
if(node==null){
return;
}
LRD(node.leftNode);
LRD(node.rightNode);
System.out.println(node.val);
}
public void LRD(){
LRD(root);
}
非递归代码实现
使用Java,jdk1.8 。用循环实现会比递归要复杂些,需要借助一个栈。
前序遍历:
(1)第一次经过节点,然后将其入栈,并且先不去往下经过其子节点。(从根节点进来,第一次经过点5;)
(2)从栈顶弹出打印,此时的节点,其子节点都还没有被经过,相当于其只被经过过一次。
(3)先将其右子节点入栈,再将其左子节点入栈,此时相当于第一次经过了其子节点。(线11,第一次经过点7;线1,第一次经过点3;)
// 1.先把根节点入栈
// 循环(栈不为空){
// 2.弹出节点打印
// 3.把右子节点入栈
// 4.把左子节点入栈
// }
// 实际上,第一次入栈时,栈顶节点的两个子节点都还没有走过,就相当于经过1次,然后弹出打印。
public void DLR2(){
Stack<BSTNode<T>> stack = new Stack<>();
stack.push(root);
while (!stack.empty()){
BSTNode<T> node = stack.pop();
System.out.println(node.val);
if(node.rightNode!=null)stack.push(node.rightNode);
if(node.leftNode!=null)stack.push(node.leftNode);
}
}
中序遍历:
(1)第一次经过节点,入栈,然后继续往下经过其左子节点,再入栈,一直持续到遇见null,线路往回走。(从根节点进来,第一次经过点5;线1->线2->线3->线4;)
(2)从栈顶弹出打印,此时的节点,其左子节点已经被经过,前面已经是经过了其左子节点null,然后线路往回走了,所以此时的节点已经被经过了第二次。
(3)先将其右子节点入栈,然后从右子节点开始,一直经过左子节点,并将它们入栈。(线5,不过此时为null,不会入栈;)
// 1.先从根节点开始,一路把左子节点入栈
// 循环(栈不为空){
// 2.弹出节点打印
// 3.将当前节点的右子节点中的左节点一路入栈
// }
// 实际上,第一次入栈时,就已经把栈顶节点的左子节点走过一次,然后弹出栈时,相当于是第2次经过,然后弹出打印。
public void LDR2(){
Stack<BSTNode<T>> stack = new Stack<>();
BSTNode<T> node = root;
while (node!=null){
stack.push(node);
node = node.leftNode;
}
while (!stack.empty()){
node = stack.pop();
System.out.println(node.val);
node = node.rightNode;
while (node!=null){
stack.push(node);
node = node.leftNode;
}
}
}
后序遍历:
(1)第一次经过节点,入栈,然后继续往下经过左子节点,如果没有左子节点,就去经过右子节点。一直到左右子节点都为null。(从根节点进来,第一次经过点5;线1->线2->线3->线4->线5->线6;)
(2)从栈顶弹出打印,此时的节点,其左子节点和右子节点都已经被经过,所以此时的节点是第三次被经过了。
(3)先获取当前节点的父节点,在将父节点的右子节点入栈,然后从右子节点开始,一直经过左子节点,并将它们入栈,如果没有左子节点,就经过右子节点。(线7->线8;)
// 1.先从根节点开始,一路把左子节点入栈,如果左子节点没有,就挑右子节点
// 循环(栈不为空){
// 2.弹出节点打印
// 3.如果当前节点是其父节点的左子节点,
// 则从其父节点的右子节点开始,一路把左子节点入栈,如果左子节点没有,就挑右子节点
// }
// 实际上,第一次入栈时,就已经把栈顶节点的两个子节点都走过一次了,所以弹出栈时,是第3次经过,然后弹出打印。
public void LRD2(){
Stack<BSTNode<T>> stack = new Stack<>();
BSTNode<T> node = root;
while (node!=null){
stack.push(node);
if(node.leftNode!=null)node=node.leftNode;
else node=node.rightNode;
}
while (!stack.empty()){
node = stack.pop();
System.out.println(node.val);
if(!stack.empty()&&stack.peek().leftNode==node){
node = stack.peek().rightNode;
while (node!=null){
stack.push(node);
if(node.leftNode!=null)node=node.leftNode;
else node=node.rightNode;
}
}
}
}